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.Collection;
014import java.util.Iterator;
015import java.util.List;
016import java.util.LinkedList;
017import java.util.Objects;
018
019/**
020 * Represents a lazy list object.
021 * <p>
022 * A lazy list behaves like a linked list, but each next element
023 * is represented by a closure that is evaluated only if needed.
024 * The value is cached, so that the closure representing the tail
025 * is evaluated only once.
026 *
027 * Since the tail closure will be called at most once, and we can't
028 * guarantee when, or even if, it will be called, this closure must be
029 * a pure, side-effect free, function.
030 */
031public class LazyList implements Collection<Object>, HeadTail<Object> {
032
033  /**
034   * Represents the empty list.
035   */
036  public static final LazyList EMPTY = new LazyList(null, null) {
037    @Override
038    public boolean equals(Object other) {
039      return other == this;
040    }
041
042    @Override
043    public int hashCode() {
044      return Objects.hash(null, null);
045    }
046
047    @Override
048    public boolean isEmpty() {
049      return true;
050    }
051
052    @Override
053    public int size() {
054      return 0;
055    }
056
057    @Override
058    public LazyList tail() {
059      return this;
060    }
061
062    @Override
063    public String toString() {
064      return "LazyList.EMPTY";
065    }
066  };
067
068  private final Object head;
069  private final FunctionReference tail;
070  private LazyList memoTail = null;
071
072  /**
073   * Create a new list from the head and tail values.
074   *
075   * @param head the first value of the list.
076   * @param tail a {@code FunctionReference} that returns a LazyList when invoked.
077   * @return a new {@code LazyList}
078   */
079  public static LazyList cons(Object head, FunctionReference tail) {
080    if (tail == null) {
081      throw new IllegalArgumentException("Use the empty list instead of null as the last element of a LazyList");
082    }
083    return new LazyList(head, tail);
084  }
085
086  private LazyList(Object head, FunctionReference tail) {
087    this.head = head;
088    this.tail = tail;
089  }
090
091  /**
092   * Gets the first element of the list (its head).
093   *
094   * @return an {@code Object}, or {@code null} if the list is empty.
095   */
096  public Object head() {
097    return this.head;
098  }
099
100  /**
101   * Gets the rest of the list (its tail).
102   *
103   * @return a {@code LazyList}, or {@code EMPTY} if the list is empty,
104   * contains only one value, or if the closure failed.
105   */
106  public LazyList tail() {
107    if (memoTail == null) {
108      try {
109        memoTail = (LazyList) (this.tail.invoke());
110      } catch (Throwable e) {
111        memoTail = EMPTY;
112      }
113    }
114    return memoTail;
115  }
116
117  /**
118   * Checks whether the list is empty or not.
119   *
120   * @return {@code true} if the list is EMPTY, {@code false} otherwise.
121   */
122  @Override
123  public boolean isEmpty() {
124    return false;
125  }
126
127  /**
128   * Creates an iterator over the list.
129   * <p>The iterator does not support removal.
130   *
131   * @return an iterator.
132   */
133  @Override
134  public Iterator<Object> iterator() {
135    return new HeadTailIterator<>(this);
136  }
137
138  /**
139   * Convert the lazy list into a regular list.
140   * <p>
141   * Note that it evaluates the whole list. Take care to
142   * <b>not use</b> this method on infinite lists, since
143   * no check is done.
144   *
145   * @return a new {@code LinkedList}
146   */
147  public List<Object> asList() {
148    List<Object> lst = new LinkedList<>();
149    for (Object o : this) {
150      lst.add(o);
151    }
152    return lst;
153  }
154
155  /**
156   * Returns the number of elements in this list.
157   * <p>
158   * Note that it evaluates the whole list. Take care to
159   * <b>not use</b> this method on infinite lists, since
160   * no check is done.
161   *
162   * @return the number of elements in this list.
163   */
164  @Override
165  public int size() {
166    return 1 + this.tail().size();
167  }
168
169  /**
170   * Compares the specified object with this list.
171   * <p>
172   * This is a value comparison.
173   * Note that it may evaluate the whole list. Take care to
174   * <b>not use</b> this method on infinite lists, since
175   * no check is done.
176   *
177   * @param o the object to be compared for equality with this list
178   * @return {@code true} if the specified object is equal to this list.
179   */
180  @Override
181  public boolean equals(Object o) {
182    if (o == this) { return true; }
183    if (o == null) { return false; }
184    if (!(o instanceof LazyList)) { return false; }
185    LazyList other = (LazyList) o;
186    if (this.isEmpty() && other.isEmpty()) { return true; }
187    if (!this.head.equals(other.head)) { return false; }
188    if (this.tail.equals(other.tail)) { return true; }
189    return this.tail().equals(other.tail());
190  }
191
192  /**
193   * Compute the hashCode of this list.
194   * <p>
195   * Note that it evaluates the whole list. Take care to
196   * <b>not use</b> this method on infinite lists, since
197   * no check is done.
198   *
199   * @return the {@code hashCode}.
200   */
201  @Override
202  public int hashCode() {
203    return Objects.hash(this.head, this.tail());
204  }
205
206  /**
207   * Returns an array containing all of the elements in this list.
208   * <p>
209   * Note that it evaluates the whole list. Take care to
210   * <b>not use</b> this method on infinite lists, since
211   * no check is done.
212   *
213   * @return an array containing all of the elements in this list
214   */
215  @Override
216  public Object[] toArray() {
217    return this.asList().toArray();
218  }
219
220  /**
221   * Returns an array containing all of the elements in this list with a type
222   * of the given array.
223   * <p>
224   * Note that it evaluates the whole list. Take care to
225   * <b>not use</b> this method on infinite lists, since
226   * no check is done.
227   *
228   * @return an array containing all of the elements in this list
229   */
230  @Override
231  public <T> T[] toArray(T[] a) {
232    return this.asList().toArray(a);
233  }
234
235
236  /**
237   * Destructuration helper.
238   *
239   * @return a tuple of head and tail
240   */
241  public Tuple destruct() {
242    return new Tuple(head(), tail());
243  }
244
245  /**
246   * Returns the element at the specified position in this list.
247   * <p>
248   * Note that it evaluates the list up to the required element.
249   *
250   * @param index index of the element to return
251   * @return the element at the specified position in this list
252   */
253  public Object get(int index) {
254    if (index < 0 || this.isEmpty()) { throw new IndexOutOfBoundsException(); }
255    if (index == 0) { return this.head(); }
256    return this.tail().get(index - 1);
257  }
258
259  /**
260   * Returns the position of the first occurrence of the given element in the
261   * list.
262   * <p>
263   * Note that it evaluates the list up to the given element. Take care to
264   * <b>not use</b> this method on infinite lists, since
265   * no check is done.
266   *
267   * @param o element to search for
268   * @return the index of the first occurrence, or -1 if not present
269   */
270  public int indexOf(Object o) {
271    int idx = 0;
272    for (Object elt : this) {
273      if (elt.equals(o)) { return idx; }
274      idx++;
275    }
276    return -1;
277  }
278
279  /**
280   * Check if the list contains the given object.
281   * <p>
282   * Note that it evaluates the list up to the given element. Take care to
283   * <b>not use</b> this method on infinite lists, since
284   * no check is done.
285   *
286   * @param o element to search for
287   * @return {@code true} if the element is in the list, {@code false}
288   * otherwise.
289   */
290  @Override
291  public boolean contains(Object o) {
292    return this.indexOf(o) != -1;
293  }
294
295  /**
296   * Check if the list contains all the objects in the given collection.
297   * <p>
298   * Note that it evaluates the list up to the given element, *for each*
299   * element in the collection (at worse). This implementation is highly inefficient.
300   * <p>
301   * Take care to
302   * <b>not use</b> this method on infinite lists, since
303   * no check is done.
304   *
305   * @param c collection of elements to search for
306   * @return {@code true} if all the elements are in the list, {@code false}
307   * otherwise.
308   */
309  @Override
310  public boolean containsAll(Collection<?> c) {
311    for (Object elt : c) {
312      if (!this.contains(elt)) { return false; }
313    }
314    return true;
315  }
316
317  @Override
318  public String toString() {
319    return String.format("LazyList<head=%s, tail=%s>", head, tail);
320  }
321
322
323  @Override
324  public boolean add(Object e) {
325    throw new UnsupportedOperationException("a LazyList is immutable");
326  }
327
328  public void add(int index, Object element) {
329    throw new UnsupportedOperationException("a LazyList is immutable");
330  }
331
332  @Override
333  public boolean addAll(Collection<?> c) {
334    throw new UnsupportedOperationException("a LazyList is immutable");
335  }
336
337  public boolean addAll(int index, Collection<?> c) {
338    throw new UnsupportedOperationException("a LazyList is immutable");
339  }
340
341  @Override
342  public boolean remove(Object e) {
343    throw new UnsupportedOperationException("a LazyList is immutable");
344  }
345
346  public Object remove(int index) {
347    throw new UnsupportedOperationException("a LazyList is immutable");
348  }
349
350  @Override
351  public boolean removeAll(Collection<?> c) {
352    throw new UnsupportedOperationException("a LazyList is immutable");
353  }
354
355  @Override
356  public boolean retainAll(Collection<?> c) {
357    throw new UnsupportedOperationException("a LazyList is immutable");
358  }
359
360  @Override
361  public void clear() {
362    throw new UnsupportedOperationException("a LazyList is immutable");
363  }
364
365  public Object set(int index, Object element) {
366    throw new UnsupportedOperationException("a LazyList is immutable");
367  }
368
369}