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}