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