Coverage Report - org.argouml.persistence.AbstractFilePersister
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractFilePersister
32%
28/85
15%
6/38
2.167
AbstractFilePersister$ProgressMgr
0%
0/28
0%
0/6
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  
 }