| History.java |
1 /**
2 * Distribution License:
3 * JSword is free software; you can redistribute it and/or modify it under
4 * the terms of the GNU Lesser General Public License, version 2.1 or later
5 * as published by the Free Software Foundation. This program is distributed
6 * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8 * See the GNU Lesser General Public License for more details.
9 *
10 * The License is available on the internet at:
11 * http://www.gnu.org/copyleft/lgpl.html
12 * or by writing to:
13 * Free Software Foundation, Inc.
14 * 59 Temple Place - Suite 330
15 * Boston, MA 02111-1307, USA
16 *
17 * © CrossWire Bible Society, 2005 - 2016
18 *
19 */
20 package org.crosswire.common.history;
21
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.concurrent.CopyOnWriteArrayList;
28
29 /**
30 * Maintains a navigable history of objects. This maintains a dated list of
31 * objects and a current navigation list.
32 *
33 * @see gnu.lgpl.License The GNU Lesser General Public License for details.
34 * @author DM Smith
35 */
36 public class History {
37 /**
38 * Create an empty navigation and history list.
39 *
40 */
41 public History() {
42 nav = new ArrayList<Object>();
43 history = new HashMap<Object, Long>();
44 listeners = new CopyOnWriteArrayList<HistoryListener>();
45 }
46
47 /**
48 * Make a particular element in the navigation list the current item in
49 * history.
50 *
51 * @param index
52 * the index of item to make the last one in the back list, -1
53 * (or lower) will put everything in the forward list. Indexes
54 * beyond the end of the list will put everything in the back
55 * list.
56 * @return the specified item in history
57 */
58 public Object select(int index) {
59 int i = index;
60
61 // Adjust to be 1 based
62 int size = nav.size();
63
64 if (i > size) {
65 i = size;
66 } else if (i < 1) {
67 i = 1;
68 }
69
70 // Only fire history changes when there is a change.
71 if (i != backCount) {
72 backCount = i;
73 fireHistoryChanged();
74 }
75
76 return getCurrent();
77 }
78
79 /**
80 * Add an element to history. If the element is in the forward list, then it
81 * replaces everything in the forward list upto it. Otherwise, it replaces
82 * the forward list.
83 *
84 * @param obj
85 * the object to add
86 */
87 public void add(Object obj) {
88 Object current = getCurrent();
89
90 // Don't add null objects or the same object.
91 if (obj == null || obj.equals(current)) {
92 return;
93 }
94
95 // If we are adding the next element, then just advance
96 // otherwise ...
97 // Object next = peek(1);
98 // if (!obj.equals(next))
99 // {
100 int size = nav.size();
101 if (size > backCount) {
102 int pos = backCount;
103 while (pos < size && !obj.equals(nav.get(pos))) {
104 pos++;
105 }
106 // At this point pos either == size or the element at pos matches
107 // what we are navigating to.
108 nav.subList(backCount, Math.min(pos, size)).clear();
109 }
110
111 // If it matches, then we don't have to do anything more
112 if (!obj.equals(peek(1))) {
113 // then we add it
114 nav.add(backCount, obj);
115 }
116 // }
117
118 backCount++;
119
120 // and remember when we saw it
121 visit(obj);
122
123 fireHistoryChanged();
124 }
125
126 /**
127 * Get all the elements in "back" list.
128 *
129 * @return the elements in the back list.
130 */
131 public List<Object> getPreviousList() {
132 if (backCount > 0) {
133 return Collections.unmodifiableList(nav.subList(0, backCount));
134 }
135 return Collections.emptyList();
136 }
137
138 /**
139 * Get all the elements in the "forward" list.
140 *
141 * @return the elements in the forward list.
142 */
143 public List<Object> getNextList() {
144 if (backCount < nav.size()) {
145 return Collections.unmodifiableList(nav.subList(backCount, nav.size()));
146 }
147 return Collections.emptyList();
148 }
149
150 /**
151 * Increments the current history item by the given amount. Positive numbers
152 * are forward. Negative numbers are back.
153 *
154 * @param i
155 * the distance to travel
156 * @return the item at the requested location, or at the end of the list if
157 * i is too big, or at the beginning of the list if i is too small,
158 * otherwise null.
159 */
160 public Object go(int i) {
161 return select(backCount + i);
162 }
163
164 /**
165 * Get the current item in the "back" list
166 *
167 * @return the current item in the back list.
168 */
169 public Object getCurrent() {
170 if (!nav.isEmpty() && backCount > 0) {
171 return nav.get(backCount - 1);
172 }
173 return null;
174 }
175
176 /**
177 * Get the current item in the "back" list
178 *
179 * @param i
180 * the distance to travel
181 * @return the requested item in the navigation list.
182 */
183 private Object peek(int i) {
184 int size = nav.size();
185 if (size > 0 && backCount > 0 && backCount + i <= size) {
186 return nav.get(backCount + i - 1);
187 }
188 return null;
189 }
190
191 /**
192 * Add a listener for history events.
193 *
194 * @param li
195 * the interested listener
196 */
197 public void addHistoryListener(HistoryListener li) {
198 listeners.add(li);
199 }
200
201 /**
202 * Remove a listener of history events.
203 *
204 * @param li
205 * the disinterested listener
206 */
207 public void removeHistoryListener(HistoryListener li) {
208 listeners.remove(li);
209 }
210
211 /**
212 * Note that this object has been seen at this time.
213 *
214 * @param obj the object that has been seen
215 */
216 private void visit(Object obj) {
217 history.put(obj, Long.valueOf(System.currentTimeMillis()));
218 }
219
220 /**
221 * Kick of an event sequence
222 */
223 private void fireHistoryChanged() {
224 HistoryEvent ev = new HistoryEvent(this);
225 for (HistoryListener listener: listeners) {
226 listener.historyChanged(ev);
227 }
228 }
229
230 /**
231 * The elements that can be navigated.
232 */
233 private List<Object> nav;
234
235 /**
236 * A map of elements that have been seen so far to when they have been seen.
237 */
238 private Map<Object, Long> history;
239
240 /**
241 * The number of elements in the "back" list.
242 */
243 private int backCount;
244
245 /**
246 * Listeners that are interested when history has changed.
247 */
248 private List<HistoryListener> listeners;
249 }
250