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.Arrays;
014import java.util.Iterator;
015import java.util.NoSuchElementException;
016import org.eclipse.golo.runtime.InvalidDestructuringException;
017import org.eclipse.golo.runtime.ArrayHelper;
018
019/**
020 * Represents an tuple object.
021 * <p>
022 * A tuple essentially behaves like an immutable array. In Golo, tuples can be created as follows:
023 * <pre class="listing"><code class="lang-golo" data-lang="golo">
024 * # Short syntax
025 * let t1 = [1, 2, 3]
026 *
027 * # Complete collection literal syntax
028 * let t2 = tuple[1, 2, 3]
029 * </code></pre>
030 */
031public final class Tuple implements HeadTail<Object>, Comparable<Tuple> {
032
033  private static final Tuple EMPTY = new Tuple();
034
035  private final Object[] data;
036
037  /**
038   * Creates a new tuple from values.
039   *
040   * @param values the tuple values.
041   */
042  public Tuple(Object... values) {
043    data = Arrays.copyOf(values, values.length);
044  }
045
046  /**
047   * Helper factory method.
048   *
049   * @param values the values as an array.
050   * @return a tuple from the array values.
051   */
052  public static Tuple fromArray(Object[] values) {
053    if (values.length == 0) { return EMPTY; }
054    return new Tuple(values);
055  }
056
057  /**
058   * Gives the number of elements in this tuple.
059   *
060   * @return the tuple size.
061   */
062  public int size() {
063    return data.length;
064  }
065
066  /**
067   * Checks whether the tuple is empty or not.
068   *
069   * @return {@code true} if the tuple has no element, {@code false} otherwise.
070   */
071  @Override
072  public boolean isEmpty() {
073    return data.length == 0;
074  }
075
076  /**
077   * Gets the element at a specified index.
078   *
079   * @param index the element index.
080   * @return the element at index {@code index}.
081   * @throws IndexOutOfBoundsException if the specified {@code index} is not valid (negative value or above the size).
082   */
083  public Object get(int index) {
084    if (index < 0 || index >= data.length) {
085      throw new IndexOutOfBoundsException(index + " is outside the bounds of a " + data.length + "-tuple");
086    }
087    return data[index];
088  }
089
090  /**
091   * Creates an iterator over the tuple.
092   * <p>The iterator does not support removal.
093   *
094   * @return an iterator.
095   */
096  @Override
097  public Iterator<Object> iterator() {
098    return new Iterator<Object>() {
099
100      private int i = 0;
101
102      @Override
103      public boolean hasNext() {
104        return i < data.length;
105      }
106
107      @Override
108      public Object next() {
109        if (i >= data.length) {
110          throw new NoSuchElementException();
111        }
112        Object result = data[i];
113        i++;
114        return result;
115      }
116
117      @Override
118      public void remove() {
119        throw new UnsupportedOperationException("Tuples are immutable");
120      }
121    };
122  }
123
124  @Override
125  public boolean equals(Object o) {
126    if (this == o) { return true; }
127    if (o == null || getClass() != o.getClass()) { return false; }
128
129    Tuple tuple = (Tuple) o;
130    return Arrays.equals(data, tuple.data);
131  }
132
133  /**
134   * Compares this tuple with the specified tuple for order.
135   * <p>Returns a negative integer, zero, or a positive integer as this tuple is less than, equal to, or greater than the specified tuple.
136   * <p>Two tuples are compared using the lexicographical (dictionary) order, that is:
137   * {@code [1, 2] < [1, 3]} and {@code [2, 5] < [3, 1]}.
138   * <p> Two tuples are comparable if they have the same size and their elements are pairwise comparable.
139   *
140   * @param other the tuple to be compared.
141   * @return a negative integer, zero, or a positive integer as this tuple is less than, equal to, or greater than the specified tuple.
142   * @throws NullPointerException if the specified tuple is null.
143   * @throws ClassCastException  if the type of the elements in the specified tuple prevent them from being compared to this tuple elements.
144   * @throws IllegalArgumentException if the specified tuple has a different size than this tuple.
145   */
146  @Override
147  public int compareTo(Tuple other) {
148    if (this.equals(other)) {
149      return 0;
150    }
151    if (this.size() != other.size()) {
152      throw new IllegalArgumentException(String.format(
153            "%s and %s can't be compared since of different size", this, other));
154    }
155    for (int i = 0; i < size(); i++) {
156      if (!this.get(i).equals(other.get(i))) {
157        @SuppressWarnings("unchecked")
158        Comparable<Object> current = (Comparable<Object>) this.get(i);
159        return current.compareTo(other.get(i));
160      }
161    }
162    return 0;
163  }
164
165  @Override
166  public int hashCode() {
167    return Arrays.hashCode(data);
168  }
169
170  @Override
171  public String toString() {
172    return "tuple" + Arrays.toString(data);
173  }
174
175  /**
176   * Returns the first element of the tuple.
177   *
178   * @return the first element.
179   */
180  @Override
181  public Object head() {
182    if (this.isEmpty()) {
183      return null;
184    }
185    return this.get(0);
186  }
187
188  /**
189   * Returns a new tuple containing the remaining elements.
190   *
191   * @return a tuple.
192   */
193  @Override
194  public Tuple tail() {
195    return this.subTuple(1);
196  }
197
198  /**
199   * Helper for destructuring.
200   *
201   * @return the tuple itself
202   * @deprecated This method should not be called directly and is no more used by new style destructuring.
203   */
204  @Deprecated
205  public Tuple destruct() { return this; }
206
207  /**
208   * New style destructuring helper.
209   *
210   * <p>If a remainer if included, it will be a new tuple of the remaining values.
211   *
212   * @param number number of variable that will be affected.
213   * @param substruct whether the destructuring is complete or should contains a sub structure.
214   * @param toSkip a boolean array indicating the elements to skip.
215   * @return an array containing the values to assign.
216   */
217  public Object[] __$$_destruct(int number, boolean substruct, Object[] toSkip) {
218    Object[] destruct = ArrayHelper.newStyleDestruct(this.data, number, substruct, toSkip);
219    if (number <= this.data.length + 1 && substruct && destruct[number - 1] != null) {
220      destruct[number - 1] = fromArray((Object[]) destruct[number - 1]);
221    }
222    return destruct;
223  }
224
225  /**
226   * Extract a sub-tuple.
227   *
228   * @param start the index of the first element.
229   * @return a new tuple containing the elements from {@code start} to the end.
230   */
231  public Tuple subTuple(int start) {
232    return this.subTuple(start, data.length);
233  }
234
235  /**
236   * Extract a sub-tuple.
237   *
238   * @param start the index of the first element (inclusive).
239   * @param end the index of the last element (exclusive).
240   * @return a new tuple containing the elements between indices {@code start} inclusive and {@code end}
241   * exclusive.
242   */
243  public Tuple subTuple(int start, int end) {
244    if (this.isEmpty()) {
245      return this;
246    }
247    return fromArray(Arrays.copyOfRange(data, start, end));
248  }
249
250  /**
251   * Returns an array containing all of the elements in this tuple.
252   *
253   * @return an array of values
254   */
255  public Object[] toArray() {
256    return Arrays.copyOf(data, data.length);
257  }
258
259  /**
260   * Returns a new Tuple extended with the given values.
261   *
262   * @return an extended {@link Tuple}, or this one if no values are given.
263   */
264  public Tuple extend(Object... values) {
265    if (values.length == 0) {
266      return this;
267    }
268    Object[] newdata = Arrays.copyOf(data, data.length + values.length);
269    for (int i = 0; i < values.length; i++) {
270      newdata[data.length + i] = values[i];
271    }
272    return new Tuple(newdata);
273  }
274
275  /**
276   * Returns a new Tuple extended with the given Tuple.
277   *
278   * @return an extended Tuple, or this one if the given tuple is empty.
279   */
280  public Tuple extend(Tuple tuple) {
281    return this.extend(tuple.data);
282  }
283}