Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
History |
|
| 2.8333333333333335;2.833 |
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 | 0 | public History() { |
42 | 0 | nav = new ArrayList<Object>(); |
43 | 0 | history = new HashMap<Object, Long>(); |
44 | 0 | listeners = new CopyOnWriteArrayList<HistoryListener>(); |
45 | 0 | } |
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 | 0 | int i = index; |
60 | ||
61 | // Adjust to be 1 based | |
62 | 0 | int size = nav.size(); |
63 | ||
64 | 0 | if (i > size) { |
65 | 0 | i = size; |
66 | 0 | } else if (i < 1) { |
67 | 0 | i = 1; |
68 | } | |
69 | ||
70 | // Only fire history changes when there is a change. | |
71 | 0 | if (i != backCount) { |
72 | 0 | backCount = i; |
73 | 0 | fireHistoryChanged(); |
74 | } | |
75 | ||
76 | 0 | 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 | 0 | Object current = getCurrent(); |
89 | ||
90 | // Don't add null objects or the same object. | |
91 | 0 | if (obj == null || obj.equals(current)) { |
92 | 0 | 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 | 0 | int size = nav.size(); |
101 | 0 | if (size > backCount) { |
102 | 0 | int pos = backCount; |
103 | 0 | while (pos < size && !obj.equals(nav.get(pos))) { |
104 | 0 | pos++; |
105 | } | |
106 | // At this point pos either == size or the element at pos matches | |
107 | // what we are navigating to. | |
108 | 0 | nav.subList(backCount, Math.min(pos, size)).clear(); |
109 | } | |
110 | ||
111 | // If it matches, then we don't have to do anything more | |
112 | 0 | if (!obj.equals(peek(1))) { |
113 | // then we add it | |
114 | 0 | nav.add(backCount, obj); |
115 | } | |
116 | // } | |
117 | ||
118 | 0 | backCount++; |
119 | ||
120 | // and remember when we saw it | |
121 | 0 | visit(obj); |
122 | ||
123 | 0 | fireHistoryChanged(); |
124 | 0 | } |
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 | 0 | if (backCount > 0) { |
133 | 0 | return Collections.unmodifiableList(nav.subList(0, backCount)); |
134 | } | |
135 | 0 | 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 | 0 | if (backCount < nav.size()) { |
145 | 0 | return Collections.unmodifiableList(nav.subList(backCount, nav.size())); |
146 | } | |
147 | 0 | 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 | 0 | 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 | 0 | if (!nav.isEmpty() && backCount > 0) { |
171 | 0 | return nav.get(backCount - 1); |
172 | } | |
173 | 0 | 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 | 0 | int size = nav.size(); |
185 | 0 | if (size > 0 && backCount > 0 && backCount + i <= size) { |
186 | 0 | return nav.get(backCount + i - 1); |
187 | } | |
188 | 0 | 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 | 0 | listeners.add(li); |
199 | 0 | } |
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 | 0 | listeners.remove(li); |
209 | 0 | } |
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 | 0 | history.put(obj, Long.valueOf(System.currentTimeMillis())); |
218 | 0 | } |
219 | ||
220 | /** | |
221 | * Kick of an event sequence | |
222 | */ | |
223 | private void fireHistoryChanged() { | |
224 | 0 | HistoryEvent ev = new HistoryEvent(this); |
225 | 0 | for (HistoryListener listener: listeners) { |
226 | 0 | listener.historyChanged(ev); |
227 | } | |
228 | 0 | } |
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 | } |