/* Author: Jeff Dalton * Updated: Sun May 27 05:34:46 2001 by Jeff Dalton */ package ix.util.lisp; import java.io.Serializable; import java.util.*; import ix.util.*; /** * A Collector that uses an LList to hold the elements and that provides * a number of different ways to add elements.

* * As a Java Collection, it is sequential, variable-sized, and modifiable.

* * Unlike Java's LinkedList class, which uses a doubly-linked list * internally, this class uses a list that is only forward-linked; * but it maintains a pointer to the last cell in the list to allow * elements to be added efficiently at either the beginning or end. * However, deleting the last element takes time proportional to * the length of the list. LListCollectors can therefore be used * efficiently as stacks or queues, but not as double-ended queues * (deques).

* * The ListIterator for this class can efficiently add or remove * at any point in the list except for deleting the last element, * which again requires time proportional to the length. Note * that some of these operations may change a car as well as a cdr. */ public class LListCollector extends AbstractSequentialList implements Collector, Serializable { transient Cons head = new Cons(Lisp.NIL, Lisp.NIL); Cons tail = head; public LListCollector() { } // 2nd constructor expected for Collections public LListCollector(Collection initialContents) { addAll(initialContents); } // Methods needed to complete AbstractSequentialList public ListIterator listIterator(int index) { return new LListCollectorListIterator(index); } public int size() { return length(); } // Inherited methods we need to override because they're inefficient. // For public boolean isEmpty(), see below. public boolean add(Object o) { // Put at end. The inherited methods does it by add(size(), o). addElement(o); return true; } public void clear() { setContents(Lisp.NIL); } public int lastIndexOf(Object o) { // The inherited method iterates backwards over the list // which is very slow. return contents().lastIndexOf(o); } // Our own methods public void addElement(Object e) { tail.cdr = new Cons(e, Lisp.NIL); tail = (Cons)tail.cdr; } public void deleteElement(Object e) { setContents(head.cdr.delete(e)); } public void pushElement(Object e) { head.cdr = new Cons(e, head.cdr); if (tail == head) // was empty tail = (Cons)tail.cdr; } public Object popElement() { Debug.assert(head.cdr instanceof Cons, "popElement() when empty"); Object result = (head.cdr).car(); if (tail == head.cdr) { // popping final element Debug.assert(tail.cdr == Lisp.NIL, "bogus tail"); tail = head; } head.cdr = ((Cons)head.cdr).cdr; return result; } public void insertElement(Object e, Predicate2 lessp) { setContents(head.cdr.insert(e, lessp)); } public Enumeration elements() { return head.cdr.elements(); } public LList contents() { return head.cdr; } public Object result () { // for Collector interface return head.cdr; } public void setContents(LList newContents) { head.cdr = newContents; tail = head; if (newContents instanceof Cons) tail = tail.lastCons(); } public boolean isEmpty() { return (tail == head); } public int length() { return (head.cdr).length(); } public void concLList(LList l) { tail.cdr = l; if (l instanceof Cons) tail = tail.lastCons(); } public void appendLList(LList l) { concLList((LList)l.clone()); } public void pushLList(LList l) { // prefixLList()? /\/ setContents(l.append(head.cdr)); } public Function1 elementAdder() { return new adderFunction() { public Object funcall(Object e) { addElement(e); return null; } }; } public Function1 elementPusher() { return new adderFunction() { public Object funcall(Object e) { pushElement(e); return null; } }; } public Function1 llistAppender() { return new adderFunction() { public Object funcall(Object l) { appendLList((LList)l); return null; } }; } public Function1 llistConcer() { return new adderFunction() { public Object funcall(Object l) { concLList((LList)l); return null; } }; } protected static interface adderFunction extends Function1 { } // Serialization and deserialization methods that should be more // efficient than the default. private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException { // Write tail and any "hidden" stuff produced by serialization. out.defaultWriteObject(); // Write length. out.writeInt(length()); // Write out elements, ensuring that the last Cons is written // as a Cons so it will match tail. if (head != tail) { // not empty Cons at; for (at = (Cons)head.cdr; at.cdr != Lisp.NIL; at = (Cons)at.cdr) { out.writeObject(at.car); } Debug.assert(at instanceof Cons); out.writeObject(at); } } private void readObject(java.io.ObjectInputStream in) throws ClassNotFoundException, java.io.IOException { // Read tail and any "hidden" stuff in.defaultReadObject(); // Read length int len = in.readInt(); if (len == 0) head = tail; else { head = new Cons(Lisp.NIL, Lisp.NIL); Cons at = head; for (int i = 0; i < len-1; i++) { Cons e = new Cons(in.readObject(), Lisp.NIL); at.cdr = e; at = e; } Cons t = (Cons)in.readObject(); Debug.assert(t == tail); at.cdr = t; } } /** * ListIterator for ListCollectors */ class LListCollectorListIterator extends LListListIterator { public LListCollectorListIterator(int index) { super(contents(), index); } public void set(Object o) { // Different from the inherited method just to have a different // error message. if (setPoint == null) throw new IllegalStateException ("set without next or previous, " + "or after remove or add called"); else setPoint.setCar(o); } public void remove() { // Removes the last element returned by next or previous. if (setPoint == null) throw new IllegalStateException ("remove without next or previous, or after add"); // The list is not empty. Debug.assert(!isEmpty()); Debug.assert(setPoint instanceof Cons); // Three cases: if (setPoint == head.cdr) { // Deleting the first element (which might also be the last) Debug.assert(index == 0); popElement(); list = head.cdr; at = list; } if (setPoint == tail) { // Deleting the last element when it's not the first. // This is a pain. Debug.assert(setPoint.cdr == Lisp.NIL); Cons back = (Cons)list.drop(index - 1); // linear Debug.assert(back.cdr == setPoint); back.setCdr(Lisp.NIL); tail = back; Debug.assert(!isEmpty()); } else { // Deleting in the middle of the list. // This is the only case where we change a car. Cons nextCons = (Cons)setPoint.cdr; setPoint.setCar(nextCons.car); setPoint.setCdr(nextCons.cdr); if (nextCons == tail) tail = setPoint; Debug.assert(!isEmpty()); } Debug.assert(list == head.cdr); setPoint = null; } public void add(Object o) { // Inserts before the elt next would return. // This is annoyingly tricky. if (head == tail) { // Adding to empty list Debug.assert(index == 0); Debug.assert(head.cdr == Lisp.NIL); addElement(o); list = head.cdr; at = list.cdr(); } else if (at == Lisp.NIL) { // Adding at end of non-empty list addElement(o); at = at.cdr(); } else if (at == head.cdr) { // Adding at start of non-empty list Debug.assert(index == 0); pushElement(o); list = head.cdr; at = list.cdr(); } else { // Adding somewhere in the middle of a non-empty list // This is the only case where we change a car. Debug.assert(at instanceof Cons); Object movedElt = at.car(); Cons cat = (Cons)at; cat.setCar(o); cat.setCdr(new Cons(movedElt, cat.cdr())); at = cat.cdr(); } Debug.assert(list == head.cdr); setPoint = null; index++; } } }