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

Last change on this file since 919 was 919, checked in by jts21, 10 years ago

Added license headers to all files, added full GPL3 license file, moved license header generator script to dev/bin/scripts

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