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