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}