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