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