source: trunk/src/org/expeditee/taskmanagement/EntitySaveManager.java@ 1102

Last change on this file since 1102 was 1102, checked in by davidb, 6 years ago

Reworking of the code-base to separate logic from graphics. This version of Expeditee now supports a JFX graphics as an alternative to SWING

File size: 7.5 KB
Line 
1/**
2 * EntitySaveManager.java
3 * Copyright (C) 2010 New Zealand Digital Library, http://expeditee.org
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19package org.expeditee.taskmanagement;
20
21import java.util.Collection;
22import java.util.HashSet;
23import java.util.LinkedList;
24import java.util.Set;
25
26
27/**
28 * Manages time/resource-consuming saving procedures. For entities in an expeditee Frame that
29 * requires heavy-duty saving, implement the entity as a SaveableEntity.
30 *
31 * The convention used:
32 *
33 * Register a SaveableEntity only if there is a possibility that it might need to be saved
34 * at the next save point.
35 *
36 * There are two save points:
37 * <ul>
38 * <li>Everytime the current frame is changed.
39 * <li>When the application exits.
40 * </ul>
41 *
42 * @author Brook Novak
43 */
44public class EntitySaveManager {
45
46 /**
47 * Singleton design pattern
48 */
49 private static EntitySaveManager _instance = new EntitySaveManager();
50 public static EntitySaveManager getInstance() {
51 return _instance;
52 }
53
54 private Set<SaveableEntity> entitiesToSave = new HashSet<SaveableEntity>();
55 private SaveThread saveThread = null;
56 private Set<SaveStateChangedEventListener> listeners = new HashSet<SaveStateChangedEventListener>();
57
58 /**
59 * Adds a SaveStateChangedEventListener.
60 *
61 * @see notes in removeSaveStateChangedEventListener
62 *
63 * @param listener
64 * The listener to add
65 *
66 * @throws NullPointerException
67 * If listener is null
68 */
69 public void addSaveStateChangedEventListener(SaveStateChangedEventListener listener) {
70 if (listener == null) throw new NullPointerException("listener");
71 synchronized(listeners) {
72 listeners.add(listener);
73 }
74 }
75
76 /**
77 * Removes a SaveStateChangedEventListener.
78 *
79 * This is important if your SaveStateChangedEventListener is temporarily used -
80 * as this is a singleton and will thus keep the SaveStateChangedEventListener
81 * in memory until it is explicity removed (due to java garbage collection).
82 *
83 * @param listener
84 * The listener to add
85 *
86 * @throws NullPointerException
87 * If listener is null
88 */
89 public void removeSaveStateChangedEventListener(SaveStateChangedEventListener listener) {
90 if (listener == null) throw new NullPointerException("listener");
91 synchronized(listeners) {
92 listeners.remove(listener);
93 }
94 }
95
96 /**
97 * Adds an entity for saving at the next save point.
98 *
99 * @param entity
100 * The entity to save
101 *
102 * @return
103 * True if added. False if already registered.
104 *
105 * @throws NullPointerException
106 * If entity is null
107 */
108 public boolean register(SaveableEntity entity) {
109 if (entity == null) throw new NullPointerException("entity");
110 return entitiesToSave.add(entity);
111 }
112
113 /**
114 * Removes an entity from saving at the next save point.
115 * Note that if in the process of saving or is about to save, then it will not be removed.
116 * In such cases it is up to the entity to ignore the save command.
117 *
118 * @param entity
119 * The entity to cancel save
120 *
121 * @return
122 * True if added. False if already registered.
123 *
124 * @throws NullPointerException
125 * If entity is null
126 */
127 public boolean unregister(SaveableEntity entity) {
128 if (entity == null) throw new NullPointerException("entity");
129 return entitiesToSave.remove(entity);
130 }
131
132 /**
133 * Saves all current registered entities and clears registrations.
134 *
135 * Returns immediately.
136 */
137 public synchronized void saveAll() {
138 if (entitiesToSave.isEmpty()) return;
139
140 // Get list of entities that needs sacing
141 LinkedList<SaveableEntity> toSave = new LinkedList<SaveableEntity>();
142 for (SaveableEntity entity : entitiesToSave) {
143 if (entity.doesNeedSaving()) toSave.add(entity);
144 }
145 entitiesToSave.clear();
146
147 if (toSave.isEmpty()) return;
148
149 // If already saving
150 if (saveThread != null && saveThread.isAlive()) {
151 // Add the extras
152 saveThread.addMoreToSave(toSave);
153
154 // If did not finish while adding extras then return...
155 if (!saveThread.isFinished) {
156 return;
157 }
158 }
159
160 // Start saving
161 saveThread = new SaveThread(toSave);
162 saveThread.start();
163 }
164
165 /**
166 * If something is saving, this will block the thread until all entities that
167 * have been requested to be saved has finished their saving processes.
168 *
169 * @throws InterruptedException
170 * If any thread has interrupted the current thread.
171 * Note: The interrupted status of the current thread is cleared when this exception is thrown
172 */
173 public void waitUntilAllSavingFinished() throws InterruptedException {
174 if (saveThread != null && saveThread.isAlive()) {
175 saveThread.join();
176 }
177 }
178
179 /**
180 * Non-daemon thread for asynchronously saving heavy duty save procs.
181 *
182 * @author Brook Novak
183 */
184 private class SaveThread extends Thread {
185
186 private LinkedList<SaveableEntity> toSave = new LinkedList<SaveableEntity>();
187 private boolean isFinished = false;
188
189 SaveThread(Collection<SaveableEntity> toSave) {
190 super("SaveThread");
191
192 this.toSave.addAll(toSave);
193 }
194
195 /**
196 * Adds more entities to save.
197 *
198 * Note: due to my limited knowledge of java threading I have assumed that
199 * it is possible for the thread to end after the call without having saved
200 * the given extras. Thus check the isFinished - where if true then it indicates
201 * that the extras may not have saved.
202 *
203 * @param extra Extra entities to save.
204 */
205 public void addMoreToSave(Collection<SaveableEntity> extra) {
206 assert(extra != null);
207
208 synchronized(toSave) {
209 for (SaveableEntity entity : extra) {
210 if (!toSave.contains(entity)) {
211 toSave.add(entity);
212 }
213 }
214 }
215 }
216
217 @Override
218 public void run() {
219
220 // Keep running until saved all entities.
221 while (true) {
222 SaveableEntity entity = null;
223 synchronized(toSave) {
224
225 if (toSave.isEmpty()) {
226 isFinished = true; // safety check
227 return;
228 }
229
230 entity = toSave.remove();
231 }
232
233 assert(entity != null);
234
235 if (entity.doesNeedSaving()) {
236
237 // notify observers that save has started
238 (new SafeFireEvent(entity, true)).run();
239
240 // Complete the save
241 try {
242 entity.performSave();
243 } catch (Exception e) { // safety
244 e.printStackTrace();
245 }
246
247 // notify observers that save has finished - on swing thread
248 (new SafeFireEvent(entity, false)).run();
249 }
250 }
251 }
252
253 /**
254 * Notifies all listeners of save start/completion event when run.
255 *
256 * @author Brook Novak
257 *
258 */
259 private class SafeFireEvent implements Runnable {
260
261 private SaveableEntity entity;
262 private boolean started;
263
264 SafeFireEvent(SaveableEntity entity, boolean started) {
265 this.entity = entity;
266 this.started = started;
267 }
268
269 public void run() {
270 synchronized(listeners) {
271 for (SaveStateChangedEventListener listener : listeners) {
272 if (started) listener.saveStarted(new SaveStateChangedEvent(this, entity));
273 else listener.saveCompleted(new SaveStateChangedEvent(this, entity));
274 }
275 }
276 }
277
278 }
279
280
281 }
282
283}
Note: See TracBrowser for help on using the repository browser.