Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
AbstractFilePersister |
|
| 2.1666666666666665;2.167 | ||||
AbstractFilePersister$ProgressMgr |
|
| 2.1666666666666665;2.167 |
1 | /* $Id: AbstractFilePersister.java 17832 2010-01-12 19:02:29Z linus $ | |
2 | ***************************************************************************** | |
3 | * Copyright (c) 2009 Contributors - see below | |
4 | * All rights reserved. This program and the accompanying materials | |
5 | * are made available under the terms of the Eclipse Public License v1.0 | |
6 | * which accompanies this distribution, and is available at | |
7 | * http://www.eclipse.org/legal/epl-v10.html | |
8 | * | |
9 | * Contributors: | |
10 | * tfmorris | |
11 | ***************************************************************************** | |
12 | * | |
13 | * Some portions of this file was previously release using the BSD License: | |
14 | */ | |
15 | ||
16 | // Copyright (c) 1996-2007 The Regents of the University of California. All | |
17 | // Rights Reserved. Permission to use, copy, modify, and distribute this | |
18 | // software and its documentation without fee, and without a written | |
19 | // agreement is hereby granted, provided that the above copyright notice | |
20 | // and this paragraph appear in all copies. This software program and | |
21 | // documentation are copyrighted by The Regents of the University of | |
22 | // California. The software program and documentation are supplied "AS | |
23 | // IS", without any accompanying services from The Regents. The Regents | |
24 | // does not warrant that the operation of the program will be | |
25 | // uninterrupted or error-free. The end-user understands that the program | |
26 | // was developed for research purposes and is advised not to rely | |
27 | // exclusively on the program for any reason. IN NO EVENT SHALL THE | |
28 | // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, | |
29 | // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, | |
30 | // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF | |
31 | // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF | |
32 | // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY | |
33 | // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF | |
34 | // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE | |
35 | // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF | |
36 | // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT, | |
37 | // UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | |
38 | ||
39 | package org.argouml.persistence; | |
40 | ||
41 | import java.io.File; | |
42 | import java.io.FileInputStream; | |
43 | import java.io.FileNotFoundException; | |
44 | import java.io.FileOutputStream; | |
45 | import java.io.IOException; | |
46 | import java.util.HashMap; | |
47 | import java.util.Map; | |
48 | ||
49 | import javax.swing.event.EventListenerList; | |
50 | import javax.swing.filechooser.FileFilter; | |
51 | ||
52 | import org.apache.log4j.Logger; | |
53 | import org.argouml.kernel.ProfileConfiguration; | |
54 | import org.argouml.kernel.Project; | |
55 | import org.argouml.kernel.ProjectMember; | |
56 | import org.argouml.taskmgmt.ProgressEvent; | |
57 | import org.argouml.taskmgmt.ProgressListener; | |
58 | import org.argouml.uml.ProjectMemberModel; | |
59 | import org.argouml.uml.cognitive.ProjectMemberTodoList; | |
60 | import org.argouml.uml.diagram.ProjectMemberDiagram; | |
61 | import org.argouml.util.ThreadUtils; | |
62 | ||
63 | /** | |
64 | * To persist to and from zargo (zipped file) storage. | |
65 | * | |
66 | * @author Bob Tarling | |
67 | */ | |
68 | 5400 | public abstract class AbstractFilePersister extends FileFilter |
69 | implements ProjectFilePersister { | |
70 | ||
71 | 900 | private static final Logger LOG = |
72 | Logger.getLogger(AbstractFilePersister.class); | |
73 | ||
74 | /** | |
75 | * Map of persisters by target class | |
76 | */ | |
77 | private static Map<Class, Class<? extends MemberFilePersister>> | |
78 | 900 | persistersByClass = |
79 | new HashMap<Class, Class<? extends MemberFilePersister>>(); | |
80 | ||
81 | /** | |
82 | * Map of persisters by tag / file extension | |
83 | */ | |
84 | private static Map<String, Class<? extends MemberFilePersister>> | |
85 | 900 | persistersByTag = |
86 | new HashMap<String, Class<? extends MemberFilePersister>>(); | |
87 | ||
88 | static { | |
89 | 900 | registerPersister(ProjectMemberDiagram.class, "pgml", |
90 | DiagramMemberFilePersister.class); | |
91 | 900 | registerPersister(ProfileConfiguration.class, "profile", |
92 | ProfileConfigurationFilePersister.class); | |
93 | 900 | registerPersister(ProjectMemberTodoList.class, "todo", |
94 | TodoListMemberFilePersister.class); | |
95 | 900 | registerPersister(ProjectMemberModel.class, "xmi", |
96 | ModelMemberFilePersister.class); | |
97 | 900 | } |
98 | ||
99 | 5400 | private EventListenerList listenerList = new EventListenerList(); |
100 | ||
101 | // This can be made public to allow others to extend their own persisters | |
102 | private static boolean registerPersister(Class target, String tag, | |
103 | Class<? extends MemberFilePersister> persister) { | |
104 | 3600 | persistersByClass.put(target, persister); |
105 | 3600 | persistersByTag.put(tag, persister); |
106 | 3600 | return true; |
107 | } | |
108 | ||
109 | /** | |
110 | * Create a temporary copy of the existing file. | |
111 | * | |
112 | * @param file the file to copy. | |
113 | * @return the temp file or null if none copied. | |
114 | * @throws FileNotFoundException if file not found | |
115 | * @throws IOException if error reading or writing | |
116 | */ | |
117 | protected File createTempFile(File file) | |
118 | throws FileNotFoundException, IOException { | |
119 | 0 | File tempFile = new File(file.getAbsolutePath() + "#"); |
120 | ||
121 | 0 | if (tempFile.exists()) { |
122 | 0 | tempFile.delete(); |
123 | } | |
124 | ||
125 | 0 | if (file.exists()) { |
126 | 0 | copyFile(file, tempFile); |
127 | } | |
128 | ||
129 | 0 | return tempFile; |
130 | } | |
131 | ||
132 | /** | |
133 | * Copies one file src to another, raising file exceptions | |
134 | * if there are some problems. | |
135 | * | |
136 | * @param dest The destination file. | |
137 | * @param src The source file. | |
138 | * @return The destination file after successful copying. | |
139 | * @throws IOException if there is some problems with the files. | |
140 | * @throws FileNotFoundException if any of the files cannot be found. | |
141 | */ | |
142 | protected File copyFile(File src, File dest) | |
143 | throws FileNotFoundException, IOException { | |
144 | ||
145 | 0 | FileInputStream fis = new FileInputStream(src); |
146 | 0 | FileOutputStream fos = new FileOutputStream(dest); |
147 | 0 | byte[] buf = new byte[1024]; |
148 | 0 | int i = 0; |
149 | 0 | while ((i = fis.read(buf)) != -1) { |
150 | 0 | fos.write(buf, 0, i); |
151 | } | |
152 | 0 | fis.close(); |
153 | 0 | fos.close(); |
154 | ||
155 | 0 | dest.setLastModified(src.lastModified()); |
156 | ||
157 | 0 | return dest; |
158 | } | |
159 | ||
160 | ||
161 | ||
162 | //////////////////////////////////////////////////////////////// | |
163 | // FileFilter API | |
164 | ||
165 | /* | |
166 | * @see javax.swing.filechooser.FileFilter#accept(java.io.File) | |
167 | */ | |
168 | public boolean accept(File f) { | |
169 | 1168 | if (f == null) { |
170 | 0 | return false; |
171 | } | |
172 | 1168 | if (f.isDirectory()) { |
173 | 448 | return true; |
174 | } | |
175 | 720 | String s = getExtension(f); |
176 | 720 | if (s != null) { |
177 | // this check for files without extension... | |
178 | 0 | if (s.equalsIgnoreCase(getExtension())) { |
179 | 0 | return true; |
180 | } | |
181 | } | |
182 | 720 | return false; |
183 | } | |
184 | ||
185 | /** | |
186 | * The extension valid for this type of file. | |
187 | * (Just the chars, not the dot: e.g. "zargo".) | |
188 | * | |
189 | * @return the extension valid for this type of file | |
190 | */ | |
191 | public abstract String getExtension(); | |
192 | ||
193 | /** | |
194 | * Just the description, not the extension between "()". | |
195 | * | |
196 | * @return the description valid for this type of file | |
197 | */ | |
198 | protected abstract String getDesc(); | |
199 | ||
200 | private static String getExtension(File f) { | |
201 | 720 | if (f == null) { |
202 | 0 | return null; |
203 | } | |
204 | 720 | return getExtension(f.getName()); |
205 | } | |
206 | ||
207 | private static String getExtension(String filename) { | |
208 | 720 | int i = filename.lastIndexOf('.'); |
209 | 720 | if (i > 0 && i < filename.length() - 1) { |
210 | 0 | return filename.substring(i + 1).toLowerCase(); |
211 | } | |
212 | 720 | return null; |
213 | } | |
214 | ||
215 | /** | |
216 | * Given the full filename this returns true if that filename contains the | |
217 | * expected extension for the is persister. | |
218 | * | |
219 | * @param filename The filename to test. | |
220 | * @return true if the filename is valid for this persister | |
221 | */ | |
222 | public boolean isFileExtensionApplicable(String filename) { | |
223 | 48165 | return filename.toLowerCase().endsWith("." + getExtension()); |
224 | } | |
225 | ||
226 | /* | |
227 | * @see javax.swing.filechooser.FileFilter#getDescription() | |
228 | */ | |
229 | public String getDescription() { | |
230 | 1165 | return getDesc() + " (*." + getExtension() + ")"; |
231 | } | |
232 | ||
233 | /** | |
234 | * Save a project to file.<p> | |
235 | * This first archives the existing file, then calls | |
236 | * doSave(...) to do the actual saving.<p> | |
237 | * Should doSave(...) throw an exception then it is | |
238 | * caught here and any rollback handled before rethrowing | |
239 | * the exception. | |
240 | * | |
241 | * @param project The project being saved. | |
242 | * @param file The file to which the save is taking place. | |
243 | * @throws SaveException when anything goes wrong | |
244 | * @throws InterruptedException if the thread is interrupted | |
245 | * | |
246 | * @see org.argouml.persistence.ProjectFilePersister#save( | |
247 | * org.argouml.kernel.Project, java.io.File) | |
248 | */ | |
249 | public final void save(Project project, File file) throws SaveException, | |
250 | InterruptedException { | |
251 | 0 | preSave(project, file); |
252 | 0 | doSave(project, file); |
253 | 0 | postSave(project, file); |
254 | 0 | } |
255 | ||
256 | /** | |
257 | * Handle archiving of previous file or any other common | |
258 | * requirements before saving a model to a file. | |
259 | * | |
260 | * @param project The project being saved. | |
261 | * @param file The file to which the save is taking place. | |
262 | * @throws SaveException when anything goes wrong | |
263 | */ | |
264 | private void preSave(Project project, File file) throws SaveException { | |
265 | 0 | if (project == null && file == null) { |
266 | 0 | throw new SaveException("No project nor file given"); |
267 | } | |
268 | 0 | } |
269 | ||
270 | /** | |
271 | * Handle archiving on completion of a save such as renaming | |
272 | * the temporary save file to the real filename. | |
273 | * | |
274 | * @param project The project being saved. | |
275 | * @param file The file to which the save is taking place. | |
276 | * @throws SaveException when anything goes wrong | |
277 | */ | |
278 | private void postSave(Project project, File file) throws SaveException { | |
279 | 0 | if (project == null && file == null) { |
280 | 0 | throw new SaveException("No project nor file given"); |
281 | } | |
282 | 0 | } |
283 | ||
284 | /** | |
285 | * Implement in your concrete class to save a project to a | |
286 | * file.<p> | |
287 | * There is no need to worry about archiving or restoring | |
288 | * archive on failure, that is handled by the rest of the | |
289 | * framework.<p> | |
290 | * | |
291 | * @param project the project to save | |
292 | * @param file The file to write. | |
293 | * @throws SaveException when anything goes wrong | |
294 | * @throws InterruptedException if the thread is interrupted | |
295 | * | |
296 | * @see org.argouml.persistence.AbstractFilePersister#save( | |
297 | * org.argouml.kernel.Project, java.io.File) | |
298 | */ | |
299 | protected abstract void doSave(Project project, File file) | |
300 | throws SaveException, InterruptedException; | |
301 | ||
302 | /** | |
303 | * Some persisters only provide load functionality for discontinued formats | |
304 | * but no save. | |
305 | * This method returns true by default. Those Peristers that do not provide | |
306 | * save must override this. | |
307 | * @return true if this persister is able to save | |
308 | */ | |
309 | public boolean isSaveEnabled() { | |
310 | 0 | return true; |
311 | } | |
312 | ||
313 | /** | |
314 | * Some persisters only provide save functionality for deprecated formats. | |
315 | * Other persisters with the same extension will manage loading. | |
316 | * This method returns true by default. Those Peristers that do not provide | |
317 | * load must override this. | |
318 | * @return true if this persister is able to load | |
319 | */ | |
320 | public boolean isLoadEnabled() { | |
321 | 148 | return true; |
322 | } | |
323 | ||
324 | /* | |
325 | * @see org.argouml.persistence.ProjectFilePersister#doLoad(java.io.File) | |
326 | */ | |
327 | public abstract Project doLoad(File file) | |
328 | throws OpenException, InterruptedException; | |
329 | ||
330 | ||
331 | ||
332 | /** | |
333 | * Add any object interested in listening to persistence progress. | |
334 | * | |
335 | * @param listener the interested listener. | |
336 | */ | |
337 | public void addProgressListener(ProgressListener listener) { | |
338 | 0 | listenerList.add(ProgressListener.class, listener); |
339 | 0 | } |
340 | ||
341 | /** | |
342 | * Remove any object no longer interested in listening to persistence | |
343 | * progress. | |
344 | * | |
345 | * @param listener the listener to remove. | |
346 | */ | |
347 | public void removeProgressListener(ProgressListener listener) { | |
348 | 0 | listenerList.remove(ProgressListener.class, listener); |
349 | 0 | } |
350 | ||
351 | /** | |
352 | * Returns true if a FileChooser should visualize an icon for the | |
353 | * persister. | |
354 | * | |
355 | * @return true if the persister is associated to an icon | |
356 | */ | |
357 | public abstract boolean hasAnIcon(); | |
358 | ||
359 | ||
360 | /** | |
361 | * Get a MemberFilePersister based on a given ProjectMember. | |
362 | * | |
363 | * @param pm the project member | |
364 | * @return the persister | |
365 | */ | |
366 | protected MemberFilePersister getMemberFilePersister(ProjectMember pm) { | |
367 | 0 | Class<? extends MemberFilePersister> persister = null; |
368 | 0 | if (persistersByClass.containsKey(pm)) { |
369 | 0 | persister = persistersByClass.get(pm); |
370 | } else { | |
371 | /* | |
372 | * TODO: Not sure we need to do this, but just to be safe for now. | |
373 | */ | |
374 | 0 | for (Class clazz : persistersByClass.keySet()) { |
375 | 0 | if (clazz.isAssignableFrom(pm.getClass())) { |
376 | 0 | persister = persistersByClass.get(clazz); |
377 | 0 | break; |
378 | } | |
379 | } | |
380 | } | |
381 | 0 | if (persister != null) { |
382 | 0 | return newPersister(persister); |
383 | } | |
384 | 0 | return null; |
385 | } | |
386 | ||
387 | ||
388 | /** | |
389 | * Get a MemberFilePersister based on a given tag. | |
390 | * | |
391 | * @param tag The tag. | |
392 | * @return the persister | |
393 | */ | |
394 | protected MemberFilePersister getMemberFilePersister(String tag) { | |
395 | 0 | Class<? extends MemberFilePersister> persister = |
396 | persistersByTag.get(tag); | |
397 | 0 | if (persister != null) { |
398 | 0 | return newPersister(persister); |
399 | } | |
400 | 0 | return null; |
401 | } | |
402 | ||
403 | private static MemberFilePersister newPersister( | |
404 | Class<? extends MemberFilePersister> clazz) { | |
405 | try { | |
406 | 0 | return clazz.newInstance(); |
407 | 0 | } catch (InstantiationException e) { |
408 | 0 | LOG.error("Exception instantiating file persister " + clazz, e); |
409 | 0 | return null; |
410 | 0 | } catch (IllegalAccessException e) { |
411 | 0 | LOG.error("Exception instantiating file persister " + clazz, e); |
412 | 0 | return null; |
413 | } | |
414 | } | |
415 | ||
416 | // TODO: Document | |
417 | 0 | class ProgressMgr implements ProgressListener { |
418 | ||
419 | /** | |
420 | * The percentage completeness of phases complete. | |
421 | * Does not include part-completed phases. | |
422 | */ | |
423 | private int percentPhasesComplete; | |
424 | ||
425 | /** | |
426 | * The sections complete of a load or save. | |
427 | */ | |
428 | private int phasesCompleted; | |
429 | ||
430 | /** | |
431 | * The number of equals phases the progress will measure. | |
432 | * It is assumed each phase will be of equal time. | |
433 | * There is one phase for each upgrade from a previous | |
434 | * version and one pahse for the final load. | |
435 | */ | |
436 | private int numberOfPhases; | |
437 | ||
438 | public void setPercentPhasesComplete(int aPercentPhasesComplete) { | |
439 | 0 | this.percentPhasesComplete = aPercentPhasesComplete; |
440 | 0 | } |
441 | ||
442 | public void setPhasesCompleted(int aPhasesCompleted) { | |
443 | 0 | this.phasesCompleted = aPhasesCompleted; |
444 | 0 | } |
445 | ||
446 | public void setNumberOfPhases(int aNumberOfPhases) { | |
447 | 0 | this.numberOfPhases = aNumberOfPhases; |
448 | 0 | } |
449 | ||
450 | public int getNumberOfPhases() { | |
451 | 0 | return this.numberOfPhases; |
452 | } | |
453 | ||
454 | protected void nextPhase() throws InterruptedException { | |
455 | 0 | ThreadUtils.checkIfInterrupted(); |
456 | 0 | ++phasesCompleted; |
457 | 0 | percentPhasesComplete = |
458 | (phasesCompleted * 100) / numberOfPhases; | |
459 | 0 | fireProgressEvent(percentPhasesComplete); |
460 | 0 | } |
461 | ||
462 | /** | |
463 | * Called when a ProgressEvent is fired. | |
464 | * | |
465 | * @see org.argouml.taskmgmt.ProgressListener#progress(org.argouml.taskmgmt.ProgressEvent) | |
466 | * @throws InterruptedException if thread is interrupted | |
467 | */ | |
468 | public void progress(ProgressEvent event) throws InterruptedException { | |
469 | 0 | ThreadUtils.checkIfInterrupted(); |
470 | 0 | int percentPhasesLeft = 100 - percentPhasesComplete; |
471 | 0 | long position = event.getPosition(); |
472 | 0 | long length = event.getLength(); |
473 | 0 | long proportion = (position * percentPhasesLeft) / length; |
474 | 0 | fireProgressEvent(percentPhasesComplete + proportion); |
475 | 0 | } |
476 | ||
477 | /** | |
478 | * Inform listeners of any progress notifications. | |
479 | * @param percent the current percentage progress. | |
480 | * @throws InterruptedException if thread is interrupted | |
481 | */ | |
482 | protected void fireProgressEvent(long percent) | |
483 | throws InterruptedException { | |
484 | 0 | ProgressEvent event = null; |
485 | // Guaranteed to return a non-null array | |
486 | 0 | Object[] listeners = listenerList.getListenerList(); |
487 | // Process the listeners last to first, notifying | |
488 | // those that are interested in this event | |
489 | 0 | for (int i = listeners.length - 2; i >= 0; i -= 2) { |
490 | 0 | if (listeners[i] == ProgressListener.class) { |
491 | // Lazily create the event: | |
492 | 0 | if (event == null) { |
493 | 0 | event = new ProgressEvent(this, percent, 100); |
494 | } | |
495 | 0 | ((ProgressListener) listeners[i + 1]).progress(event); |
496 | } | |
497 | } | |
498 | 0 | } |
499 | } | |
500 | } |