001/*
002 * Copyright (c) 2012-2021 Institut National des Sciences Appliquées de Lyon (INSA Lyon) and others
003 *
004 * This program and the accompanying materials are made available under the
005 * terms of the Eclipse Public License 2.0 which is available at
006 * http://www.eclipse.org/legal/epl-2.0.
007 *
008 * SPDX-License-Identifier: EPL-2.0
009 */
010
011package gololang;
012
013import java.util.Iterator;
014import org.eclipse.golo.runtime.InvalidDestructuringException;
015
016/**
017 * Base class for Golo structure objects.
018 * <p>
019 * This class defines common behavior. Golo structure classes are final subclasses of this one.
020 */
021public abstract class GoloStruct implements Iterable<Tuple>, Comparable<GoloStruct>  {
022
023  /**
024   * The array of member names, initialized in Golo structure classes constructors.
025   */
026  protected String[] members;
027
028  /**
029   * Constructor that does nothing beyond calling {@code super()}.
030   */
031  public GoloStruct() {
032    super();
033  }
034
035  /**
036   * Tells whether the instance is frozen or not.
037   *
038   * @return {@code true} if frozen, {@code false} otherwise.
039   */
040  public abstract boolean isFrozen();
041
042  /**
043   * Gets the member names as a tuple of strings.
044   *
045   * @return a tuple of member names.
046   */
047  public Tuple members() {
048    return Tuple.fromArray(members);
049  }
050
051  /**
052   * Gets the current values, in order of member declaration.
053   *
054   * @return a tuple with the current values.
055   */
056  public Tuple values() {
057    return Tuple.fromArray(toArray());
058  }
059
060  /**
061   * Destructuration helper.
062   *
063   * @return a tuple with the current values.
064   * @deprecated This method should not be called directly and is no more used by new style destructuring.
065   */
066  @Deprecated
067  public Tuple destruct() {
068    return Tuple.fromArray(toArray());
069  }
070
071  /**
072   * New style destructuring helper.
073   *
074   * <p>The number of variables to be affected must be the number of members.
075   * No remainer syntax is allowed.
076   *
077   * @param number number of variable that will be affected.
078   * @param substruct whether the destructuring is complete or should contains a sub structure.
079   * @param toSkip a boolean array indicating the elements to skip.
080   * @return an array containing the values to assign.
081   */
082  public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) {
083    if (number == this.members.length && !substruct) {
084      return toArray();
085    }
086    if (number <= this.members.length) {
087      throw InvalidDestructuringException.notEnoughValues(number, this.members.length, substruct);
088    }
089    throw InvalidDestructuringException.tooManyValues(number);
090  }
091
092  /**
093   * Array conversion.
094   *
095   * @return an array containing the values (in member orders)
096   */
097  public abstract Object[] toArray();
098
099  /**
100   * Compares this structure with the specified structure for order.
101   * <p>Returns a negative integer, zero, or a positive integer as this structure is less than,
102   * equal to, or greater than the specified structure.
103   * <p>Two structures are compared by comparing their {@link #values()}, thus the
104   * limitations of {@link gololang.Tuple#compareTo} also apply.
105   * <p>Moreover, two structures are only comparable if they have the same type. For instance,
106   * given
107   * <pre class="lisgin"><code class="lang-golo" data-lang="golo">
108   * struct StructA = {x, y}
109   * struct StructB = {a, b}
110   *
111   * let aStructA = StructA(1, 2)
112   * let aStructB = StructB(1, 3)
113   * </code></pre>
114   * while {@code aStructA: values() < aStructB: values()} is valid and true since we compare two
115   * 2-tuples, comparing directly the structures {@code aStructA < aStructB} throws a
116   * {@link java.lang.ClassCastException}.
117   *
118   * @param other the structure to be compared
119   * @return a negative integer, zero, or a positive integer as this structure is less than, equal to, or greater than the specified structure
120   * @throws NullPointerException if the specified structure is null
121   * @throws IllegalArgumentException  if the structure are of different type, of if the type of the members prevent them from being compared pairwise
122   * @since Golo3.1
123   */
124  @Override
125  public int compareTo(GoloStruct other) {
126    if (this.equals(other)) {
127      return 0;
128    }
129    if (getClass() != other.getClass()) {
130      throw new IllegalArgumentException(String.format(
131            "%s and %s can't be compared; try to compare their values", this, other));
132    }
133    return this.values().compareTo(other.values());
134  }
135
136  /**
137   * Gets a member value by name.
138   *
139   * @param member the member name.
140   * @return the member value.
141   * @throws IllegalArgumentException if there is no such member {@code member}.
142   */
143  public abstract Object get(String member);
144
145  /**
146   * Sets a member value by name.
147   *
148   * @param member the member name.
149   * @param value  the value.
150   * @return this instance.
151   * @throws IllegalArgumentException if there is no such member {@code member}.
152   */
153  public abstract GoloStruct set(String member, Object value);
154
155  /**
156   * Makes a shallow copy.
157   *
158   * @return a copy of this structure.
159   */
160  public abstract GoloStruct copy();
161
162  /**
163   * Makes a shallow frozen copy where any member value modification attempt will fail with an {@link IllegalStateException}.
164   *
165   * @return a copy of this structure.
166   */
167  public abstract GoloStruct frozenCopy();
168
169  /**
170   * Provides an iterator over the structure.
171   * <p>
172   * Each value is a 2-elements tuple {@code [member, value]}.
173   *
174   * @return an iterator.
175   */
176  @Override
177  public Iterator<Tuple> iterator() {
178    return new Iterator<Tuple>() {
179
180      final Iterator<?> memberIterator = members().iterator();
181      final Iterator<?> valuesIterator = values().iterator();
182
183      @Override
184      public boolean hasNext() {
185        return memberIterator.hasNext();
186      }
187
188      @Override
189      public Tuple next() {
190        return new Tuple(memberIterator.next(), valuesIterator.next());
191      }
192
193      @Override
194      public void remove() {
195        throw new UnsupportedOperationException();
196      }
197    };
198  }
199}