001    /*
002     * LAPIS lightweight structured text processing system
003     *
004     * Copyright (C) 1998-2002 Carnegie Mellon University,
005     * Copyright (C) 2003 Massachusetts Institute of Technology.
006     * All rights reserved.
007     *
008     * This library is free software; you can redistribute it
009     * and/or modify it under the terms of the GNU General
010     * Public License as published by the Free Software
011     * Foundation, version 2.
012     *
013     * LAPIS homepage: http://graphics.lcs.mit.edu/lapis/
014     */
015    
016    
017    package lapisx.util;
018    
019    import java.util.Enumeration;
020    import java.util.NoSuchElementException;
021    
022    /**
023     * A History is a list of objects with a current object.
024     * One application of History is a browser history, in which
025     * the objects are web pages and one page is currently displayed.
026     * <p>
027     * A History has a finite maximum size.  When adding an object would exceed
028     * the maximum size, the oldest object is removed.  The maximum size can
029     * be increased with {@link #expand}.
030     */
031    public class History {
032        /**
033         * Array of history objects, arranged as circular queue.
034         */
035        protected Object[] history;
036    
037        /**
038         * Index of oldest object in history.
039         */
040        protected int start;
041    
042        /**
043         * Index <i>after</i> newest object in history.
044         */
045        protected int end;
046    
047        /**
048         * Index of current object.  If history is empty, then
049         * curr == start == end.
050         */
051        protected int curr;
052    
053        /**
054         * Make a History.
055         * @param   max     Maximum length of history
056         */
057        public History (int max) {
058            history = new Object[max+1];
059            start = end = curr = 0;
060        }
061    
062        /**
063         * Make a duplicate of another History.
064         * @param   h History to copy
065         */
066        public History (History h) {
067            this.history = new Object[h.history.length];
068            System.arraycopy (h.history, 0, this.history, 0, h.history.length);
069            this.start = h.start;
070            this.end = h.end;
071            this.curr = h.curr;
072        }
073    
074        /**
075         * Clear the history.
076         */
077        public void clear () {
078            for (int i = 0; i < history.length; ++i)
079                history[i] = null;
080    
081            int s = start;
082            int e = end;
083            start = end = curr = 0;
084    
085            if (s != e)
086                fireRemoved (s, (e > 0) ? e-1 : history.length-1);
087        }
088    
089        /**
090         * Double the capacity of the history.
091         */
092        public void expand () {
093            Object[] newHistory = new Object[(history.length * 2) - 1];
094            int i = 0;
095            int newCurr = 0;
096            for (int j = start; j != end; j = (j+1) % history.length, ++i) {
097                newHistory[i] = history[j];
098                if (j == curr)
099                    newCurr = i;
100            }
101            history = newHistory;
102            start = 0;
103            end = i;
104            curr = newCurr;
105        }
106    
107        /**
108         * Add an object to the history after the current point (deleting all
109         * objects forward of this point).  If history overflows, the oldest
110         * object is thrown away.
111         * @param obj   Object to add to history
112         */
113        public void put (Object obj) {
114            if (!isEmpty ()) {
115                // throw away all objects after curr
116                int newEnd = (curr+1) % history.length;
117                if (newEnd != end) {
118                    int e = end;
119                    end = newEnd;
120                    fireRemoved (newEnd, (e > 0) ? e-1 : history.length-1);
121                }
122            }
123    
124            add (obj);
125        }
126    
127        /**
128         * Add an object to the end of the history, moving the current point to it.
129         * If history overflows, the oldest object is thrown away.
130         * @param obj   Object to add to history
131         */
132        public void add (Object obj) {
133            if (isFull ()) {
134                // throw away oldest object to make room
135                start = (start+1) % history.length;
136                fireRemoved (start, start);
137            }
138    
139            // insert the new object at end
140            history[end] = obj;
141            curr = end;
142            end = (end+1) % history.length;
143            fireAdded (curr, curr);
144        }
145    
146        /**
147         * Get the current object of the history.
148         * @return current object of history, or null if history is empty.
149         */
150        public Object get () {
151            return !isEmpty () ? history[curr] : null;
152        }
153    
154        /**
155         * Set the current object of the history. 
156         * @param obj Object that should be the new current object.
157         * @throws NoSuchElementException if this doesn't contain obj.
158         */
159        public void jumpTo (Object obj) {
160            for (int i = start; i != end; i = (i+1) % history.length)
161                if (history[i].equals (obj)) {
162                    curr = i;
163                    fireChanged (curr, curr);
164                    return;
165                }
166            throw new NoSuchElementException ();
167        }
168    
169        /**
170         * Get the object that would be returned by back(), without actually
171         * changing the current object.
172         * @return object before current object, or null if at beginning of 
173         * history or history is empty.
174         */
175        public Object peekBack () {
176            if (curr == start)
177                return null;
178            else {
179                int bk = (curr > 0) ? curr-1 : history.length-1;
180                return history[bk];
181            }
182        }
183    
184        /**
185         * Get the object that would be returned by forward(), without actually
186         * changing the current object.
187         * @return object after current object, or null if at end of 
188         * history or history is empty.
189         */
190        public Object peekForward () {
191            if (isEmpty ())
192                return null;
193    
194            int fw = (curr+1) % history.length;
195            if (fw == end)
196                return null;
197            else 
198                return history[fw];
199        }
200    
201        /**
202         * Replace the current object of the history. The rest of the history
203         * is unaffected, and the current pointer stays where it is.
204         * <P> If the history is empty,
205         * then this call is equivalent to put(obj).
206         * @param obj object to substitute
207         */
208        public void replace (Object obj) {
209            if (isEmpty ())
210                put (obj);
211            else {
212                history[curr] = obj;
213                fireChanged (curr, curr);
214            }
215        }
216    
217        /**
218         * Move back one object in the history, if possible.
219         * @return previous object in the history, or null if at start.
220         */
221        public Object back () {
222            if (curr == start)
223                return null;
224            else {
225                curr = (curr > 0) ? curr-1 : history.length-1;
226                fireChanged (curr, curr);
227                return history[curr];
228            }
229        }
230    
231        /**
232         * Move forward one object in the history, if possible.
233         * @return next object in the history, or null if at end of history.
234         */
235        public Object forward () {
236            if (isEmpty ())
237                return null;
238    
239            int newCurr = (curr+1) % history.length;
240            if (newCurr == end)
241                return null;
242            else {
243                curr = newCurr;
244                fireChanged (curr, curr);
245                return history[curr];
246            }
247        }
248    
249        /**
250         * Move to first (oldest) object in the history, if possible.
251         * @return first object in the history, or null if history empty.
252         */
253        public Object toStart () {
254            if (curr != start) {
255                curr = start;
256                fireChanged (curr, curr);
257            }
258            return history[curr];
259        }
260    
261        /**
262         * Move to last (newest) object in the history, if possible.
263         * @return last object in the history, or null if history empty.
264         */
265        public Object toEnd () {
266            if (isEmpty ())
267                return null;
268            int newCurr = (end > 0) ? end-1 : history.length-1;
269            if (curr != newCurr) {
270                curr = newCurr;
271                fireChanged (curr, curr);
272            }
273            return history[curr];
274        }
275    
276        /**
277         * Test whether back() will succeed.
278         * @return true if and only if there are objects before the current object
279         */
280        public boolean canBack () {
281            return (curr != start);
282        }
283    
284        /**
285         * Test whether forward() will succeed.
286         * @return true if and only if there are objects after the current object
287         */
288        public boolean canForward () {
289            return !isEmpty () && ((curr+1) % history.length != end);
290        }
291    
292        /**
293         * Get number of objects currently in history.
294         * @return number of objects that would be returned by elements().
295         */
296        public int size () {
297            return ((end + history.length) - start) % history.length;
298        }
299    
300        /**
301         * Test whether history is empty.
302         * @return true if and only if history contains no objects
303         */
304        public boolean isEmpty () {
305            return start == end;
306        }
307    
308        /**
309         * Test whether history is full.
310         * @return true if and only if history contains max objects
311         */
312        public boolean isFull () {
313            return start == (end+1) % history.length;
314        }
315    
316        /**
317         * Test whether history already contains an object.
318         * @param obj   Object to search for
319         * @return true if and only if history contains an object that equals() obj
320         */
321        public boolean contains (Object obj) {
322            for (int i = start; i != end; i = (i+1) % history.length)
323                if (history[i].equals (obj))
324                    return true;
325            return false;
326        }
327    
328        /**
329         * Get the objects in the history.
330         *  enumeration yielding the history objects in order from
331         * oldest to newest.
332         */
333        public Enumeration elements () {
334            return new HistoryEnumeration (start, end);
335        }
336    
337        /**
338         * Get the objects AFTER the current object.
339         *  enumeration yielding the history objects after current,
340         * in order from oldest to newest.
341         */
342        public Enumeration forwardElements () {
343            return 
344                (curr == end)
345                ? new HistoryEnumeration (curr, end)
346                : new HistoryEnumeration ((curr + 1) % history.length, end);
347        }
348    
349        /**
350         * Get the objects BEFORE the current object.
351         *  enumeration yielding the history objects before current,
352         * in order from oldest to newest.
353         */
354        public Enumeration backElements () {
355            return new HistoryEnumeration (start, curr);
356        }
357    
358        class HistoryEnumeration implements Enumeration {
359            int p;
360            int e;
361    
362            public HistoryEnumeration (int start, int end) {
363                p = start;
364                e = end;
365            }
366    
367            public boolean hasMoreElements () {
368                return p != e;
369            }
370    
371            public Object nextElement () {
372                Object obj = history[p];
373                p = (p+1) % history.length;
374                return obj;
375            }
376        }
377    
378        /**
379         * Called when history[i..j] is removed.  Used by subclasses
380         * that need to generate events.
381         * Default implementation does nothing.
382         * @param i index of first removed element
383         * @param j index of last removed element
384         */
385        protected void fireRemoved (int i, int j) {
386        }
387        
388        /**
389         * Called when new elements are added at history[i..j].  
390         * Used by subclasses that need to generate events.
391         * Default implementation does nothing.
392         * @param i index of first added element
393         * @param j index of last added element
394         */
395        protected void fireAdded (int i, int j) {
396        }
397        
398        /**
399         * Called when elements at history[i..j] are replaced.
400         * Also called when history[i] becomes the current element
401         * (in which case i == j).
402         * Used by subclasses that need to generate events.
403         * Default implementation does nothing.
404         * @param i index of first changed element
405         * @param j index of last changed element
406         */
407        protected void fireChanged (int i, int j) {
408        }
409    }