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}