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

Last change on this file since 140 was 140, checked in by bjn8, 16 years ago

Drag and Drop now supported.
Asynchronous saving and loading now supported

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