Coverage Report - org.argouml.persistence.UmlFilePersister
 
Classes in this File Line Coverage Branch Coverage Complexity
UmlFilePersister
2%
6/233
0%
0/56
4.769
UmlFilePersister$XmlFilterOutputStream
0%
0/65
0%
0/22
4.769
 
 1  
 /* $Id: UmlFilePersister.java 17962 2010-02-04 12:51:29Z thn $
 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  
  *    Bob Tarling
 11  
  *    Thomas Neustupny
 12  
  *****************************************************************************
 13  
  *
 14  
  * Some portions of this file was previously release using the BSD License:
 15  
  */
 16  
 
 17  
 // Copyright (c) 1996-2009 The Regents of the University of California. All
 18  
 // Rights Reserved. Permission to use, copy, modify, and distribute this
 19  
 // software and its documentation without fee, and without a written
 20  
 // agreement is hereby granted, provided that the above copyright notice
 21  
 // and this paragraph appear in all copies.  This software program and
 22  
 // documentation are copyrighted by The Regents of the University of
 23  
 // California. The software program and documentation are supplied "AS
 24  
 // IS", without any accompanying services from The Regents. The Regents
 25  
 // does not warrant that the operation of the program will be
 26  
 // uninterrupted or error-free. The end-user understands that the program
 27  
 // was developed for research purposes and is advised not to rely
 28  
 // exclusively on the program for any reason.  IN NO EVENT SHALL THE
 29  
 // UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
 30  
 // SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
 31  
 // ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
 32  
 // THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
 33  
 // SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
 34  
 // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 35  
 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
 36  
 // PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
 37  
 // CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
 38  
 // UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 39  
 package org.argouml.persistence;
 40  
 
 41  
 import java.io.BufferedInputStream;
 42  
 import java.io.BufferedReader;
 43  
 import java.io.BufferedWriter;
 44  
 import java.io.File;
 45  
 import java.io.FileNotFoundException;
 46  
 import java.io.FileOutputStream;
 47  
 import java.io.FilterOutputStream;
 48  
 import java.io.IOException;
 49  
 import java.io.InputStream;
 50  
 import java.io.InputStreamReader;
 51  
 import java.io.OutputStream;
 52  
 import java.io.OutputStreamWriter;
 53  
 import java.io.PrintWriter;
 54  
 import java.io.Reader;
 55  
 import java.io.UnsupportedEncodingException;
 56  
 import java.io.Writer;
 57  
 import java.net.MalformedURLException;
 58  
 import java.net.URL;
 59  
 import java.nio.ByteBuffer;
 60  
 import java.nio.CharBuffer;
 61  
 import java.nio.charset.Charset;
 62  
 import java.nio.charset.CharsetDecoder;
 63  
 import java.nio.charset.CoderResult;
 64  
 import java.nio.charset.CodingErrorAction;
 65  
 import java.util.Hashtable;
 66  
 import java.util.List;
 67  
 import java.util.regex.Matcher;
 68  
 import java.util.regex.Pattern;
 69  
 
 70  
 import javax.xml.transform.Result;
 71  
 import javax.xml.transform.Transformer;
 72  
 import javax.xml.transform.TransformerException;
 73  
 import javax.xml.transform.TransformerFactory;
 74  
 import javax.xml.transform.stream.StreamResult;
 75  
 import javax.xml.transform.stream.StreamSource;
 76  
 
 77  
 import org.apache.log4j.Logger;
 78  
 import org.argouml.application.api.Argo;
 79  
 import org.argouml.application.helpers.ApplicationVersion;
 80  
 import org.argouml.i18n.Translator;
 81  
 import org.argouml.kernel.Project;
 82  
 import org.argouml.kernel.ProjectFactory;
 83  
 import org.argouml.kernel.ProjectMember;
 84  
 import org.argouml.model.UmlException;
 85  
 import org.argouml.util.ThreadUtils;
 86  
 import org.tigris.gef.ocl.ExpansionException;
 87  
 import org.tigris.gef.ocl.OCLExpander;
 88  
 import org.tigris.gef.ocl.TemplateReader;
 89  
 import org.xml.sax.InputSource;
 90  
 import org.xml.sax.SAXException;
 91  
 
 92  
 /**
 93  
  * To persist to and from argo (xml file) storage.
 94  
  * 
 95  
  * @author Bob Tarling
 96  
  */
 97  
 public class UmlFilePersister extends AbstractFilePersister {
 98  
 
 99  
     /**
 100  
      * The PERSISTENCE_VERSION is increased every time the persistence format
 101  
      * changes. This controls conversion of old persistence version files to be
 102  
      * converted to the current one, keeping ArgoUML backwards compatible.
 103  
      */
 104  
     public static final int PERSISTENCE_VERSION = 6;
 105  
 
 106  
     /**
 107  
      * The TOTAL_PHASES_LOAD constant is the number of phases used by the load
 108  
      * process.
 109  
      */
 110  
     protected static final int UML_PHASES_LOAD = 2;
 111  
 
 112  
     /**
 113  
      * Logger.
 114  
      */
 115  900
     private static final Logger LOG = Logger.getLogger(UmlFilePersister.class);
 116  
 
 117  
     private static final String ARGO_TEE = "/org/argouml/persistence/argo.tee";
 118  
 
 119  
     /**
 120  
      * The constructor.
 121  
      */
 122  2700
     public UmlFilePersister() {
 123  2700
     }
 124  
 
 125  
     /*
 126  
      * @see org.argouml.persistence.AbstractFilePersister#getExtension()
 127  
      */
 128  
     public String getExtension() {
 129  9875
         return "uml";
 130  
     }
 131  
 
 132  
     /*
 133  
      * @see org.argouml.persistence.AbstractFilePersister#getDesc()
 134  
      */
 135  
     protected String getDesc() {
 136  168
         return Translator.localize("combobox.filefilter.uml");
 137  
     }
 138  
 
 139  
     /**
 140  
      * It is being considered to save out individual xmi's from individuals
 141  
      * diagrams to make it easier to modularize the output of Argo.
 142  
      * 
 143  
      * @param file The file to write.
 144  
      * @param project the project to save
 145  
      * @throws SaveException when anything goes wrong
 146  
      * @throws InterruptedException if the thread is interrupted
 147  
      * 
 148  
      * @see org.argouml.persistence.ProjectFilePersister#save(org.argouml.kernel.Project,
 149  
      *      java.io.File)
 150  
      */
 151  
     public void doSave(Project project, File file) throws SaveException,
 152  
         InterruptedException {
 153  
 
 154  0
         ProgressMgr progressMgr = new ProgressMgr();
 155  0
         progressMgr.setNumberOfPhases(4);
 156  0
         progressMgr.nextPhase();
 157  
 
 158  0
         File lastArchiveFile = new File(file.getAbsolutePath() + "~");
 159  0
         File tempFile = null;
 160  
 
 161  
         try {
 162  0
             tempFile = createTempFile(file);
 163  0
         } catch (FileNotFoundException e) {
 164  0
             throw new SaveException(
 165  
                     "Failed to archive the previous file version", e);
 166  0
         } catch (IOException e) {
 167  0
             throw new SaveException(
 168  
                     "Failed to archive the previous file version", e);
 169  0
         }
 170  
 
 171  
         try {
 172  0
             project.setFile(file);
 173  0
             project.setVersion(ApplicationVersion.getVersion());
 174  0
             project.setPersistenceVersion(PERSISTENCE_VERSION);
 175  
 
 176  0
             OutputStream stream = new FileOutputStream(file);
 177  
 
 178  0
             writeProject(project, stream, progressMgr);
 179  
 
 180  0
             stream.close();
 181  
 
 182  0
             progressMgr.nextPhase();
 183  
 
 184  0
             String path = file.getParent();
 185  0
             if (LOG.isInfoEnabled()) {
 186  0
                 LOG.info("Dir ==" + path);
 187  
             }
 188  
 
 189  
             // if save did not raise an exception
 190  
             // and name+"#" exists move name+"#" to name+"~"
 191  
             // this is the correct backup file
 192  0
             if (lastArchiveFile.exists()) {
 193  0
                 lastArchiveFile.delete();
 194  
             }
 195  0
             if (tempFile.exists() && !lastArchiveFile.exists()) {
 196  0
                 tempFile.renameTo(lastArchiveFile);
 197  
             }
 198  0
             if (tempFile.exists()) {
 199  0
                 tempFile.delete();
 200  
             }
 201  
 
 202  0
             progressMgr.nextPhase();
 203  
 
 204  0
         } catch (Exception e) {
 205  0
             LOG.error("Exception occured during save attempt", e);
 206  
 
 207  
             // frank: in case of exception
 208  
             // delete name and mv name+"#" back to name if name+"#" exists
 209  
             // this is the "rollback" to old file
 210  0
             file.delete();
 211  0
             tempFile.renameTo(file);
 212  0
             if (e instanceof InterruptedException) {
 213  0
                 throw (InterruptedException) e;
 214  
             } else {
 215  
                 // we have to give a message to user and set the system
 216  
                 // to unsaved!
 217  0
                 throw new SaveException(e);
 218  
             }
 219  0
         }
 220  0
     }
 221  
 
 222  
     /**
 223  
      * The .uml save format is no longer available to save.
 224  
      * 
 225  
      * {@inheritDoc}
 226  
      */
 227  
     @Override
 228  
     public boolean isSaveEnabled() {
 229  5
         return true;
 230  
     }
 231  
 
 232  
     /**
 233  
      * Write the output for a project on the given stream.
 234  
      * 
 235  
      * @param project The project to output.
 236  
      * @param stream The stream to write to.
 237  
      * @throws SaveException If something goes wrong.
 238  
      * @throws InterruptedException if the thread is interrupted
 239  
      */
 240  
     void writeProject(Project project, OutputStream oStream,
 241  
             ProgressMgr progressMgr) throws SaveException, InterruptedException {
 242  
         OutputStreamWriter outputStreamWriter;
 243  
         try {
 244  0
             outputStreamWriter = new OutputStreamWriter(oStream, Argo
 245  
                     .getEncoding());
 246  0
         } catch (UnsupportedEncodingException e) {
 247  0
             throw new SaveException(e);
 248  0
         }
 249  0
         PrintWriter writer = new PrintWriter(new BufferedWriter(
 250  
                 outputStreamWriter));
 251  
 
 252  0
         XmlFilterOutputStream filteredStream = new XmlFilterOutputStream(
 253  
                 oStream, Argo.getEncoding());
 254  
         try {
 255  0
             writer.println("<?xml version = \"1.0\" " + "encoding = \""
 256  
                     + Argo.getEncoding() + "\" ?>");
 257  0
             writer.println("<uml version=\"" + PERSISTENCE_VERSION + "\">");
 258  
             // Write out header section
 259  
             try {
 260  0
                 Hashtable templates = TemplateReader.getInstance().read(
 261  
                         ARGO_TEE);
 262  0
                 OCLExpander expander = new OCLExpander(templates);
 263  0
                 expander.expand(writer, project, "  ");
 264  0
             } catch (ExpansionException e) {
 265  0
                 throw new SaveException(e);
 266  0
             }
 267  0
             writer.flush();
 268  
 
 269  0
             if (progressMgr != null) {
 270  0
                 progressMgr.nextPhase();
 271  
             }
 272  
 
 273  
             // Note we assume members are ordered correctly already
 274  0
             for (ProjectMember projectMember : project.getMembers()) {
 275  0
                 if (LOG.isInfoEnabled()) {
 276  0
                     LOG.info("Saving member : " + projectMember);
 277  
                 }
 278  0
                 MemberFilePersister persister = getMemberFilePersister(projectMember);
 279  0
                 filteredStream.startEntry();
 280  0
                 persister.save(projectMember, filteredStream);
 281  
                 try {
 282  0
                     filteredStream.flush();
 283  0
                 } catch (IOException e) {
 284  0
                     throw new SaveException(e);
 285  0
                 }
 286  0
             }
 287  
 
 288  0
             writer.println("</uml>");
 289  
 
 290  0
             writer.flush();
 291  
         } finally {
 292  0
             writer.close();
 293  
             try {
 294  0
                 filteredStream.reallyClose();
 295  0
             } catch (IOException e) {
 296  0
                 throw new SaveException(e);
 297  0
             }
 298  
         }
 299  0
     }
 300  
 
 301  
     /*
 302  
      * @see org.argouml.persistence.ProjectFilePersister#doLoad(java.io.File)
 303  
      */
 304  
     public Project doLoad(File file) throws OpenException, InterruptedException {
 305  
         // let's initialize the progressMgr
 306  0
         ProgressMgr progressMgr = new ProgressMgr();
 307  0
         progressMgr.setNumberOfPhases(UML_PHASES_LOAD);
 308  
 
 309  0
         ThreadUtils.checkIfInterrupted();
 310  0
         return doLoad(file, file, progressMgr);
 311  
     }
 312  
 
 313  
     protected Project doLoad(File originalFile, File file,
 314  
             ProgressMgr progressMgr) throws OpenException, InterruptedException {
 315  
 
 316  0
         XmlInputStream inputStream = null;
 317  
         try {
 318  0
             Project p = ProjectFactory.getInstance()
 319  
                     .createProject(file.toURI());
 320  
 
 321  
             // Run through any stylesheet upgrades
 322  0
             int fileVersion = getPersistenceVersionFromFile(file);
 323  
 
 324  0
             LOG.info("Loading uml file of version " + fileVersion);
 325  0
             if (!checkVersion(fileVersion, getReleaseVersionFromFile(file))) {
 326  
                 // If we're about to upgrade the file lets take an archive
 327  
                 // of it first.
 328  0
                 String release = getReleaseVersionFromFile(file);
 329  0
                 copyFile(originalFile, new File(originalFile.getAbsolutePath()
 330  
                         + '~' + release));
 331  
 
 332  0
                 progressMgr.setNumberOfPhases(progressMgr.getNumberOfPhases()
 333  
                         + (PERSISTENCE_VERSION - fileVersion));
 334  
 
 335  0
                 while (fileVersion < PERSISTENCE_VERSION) {
 336  0
                     ++fileVersion;
 337  0
                     LOG.info("Upgrading to version " + fileVersion);
 338  0
                     long startTime = System.currentTimeMillis();
 339  0
                     file = transform(file, fileVersion);
 340  0
                     long endTime = System.currentTimeMillis();
 341  0
                     LOG.info("Upgrading took " + ((endTime - startTime) / 1000)
 342  
                             + " seconds");
 343  0
                     progressMgr.nextPhase();
 344  0
                 }
 345  
             }
 346  
 
 347  0
             progressMgr.nextPhase();
 348  
 
 349  0
             inputStream = new XmlInputStream(file.toURI().toURL().openStream(),
 350  
                     "argo", file.length(), 100000);
 351  
 
 352  0
             ArgoParser parser = new ArgoParser();
 353  0
             Reader reader = new InputStreamReader(inputStream, Argo
 354  
                     .getEncoding());
 355  0
             parser.readProject(p, reader);
 356  
 
 357  0
             List memberList = parser.getMemberList();
 358  
 
 359  0
             LOG.info(memberList.size() + " members");
 360  
 
 361  0
             for (int i = 0; i < memberList.size(); ++i) {
 362  0
                 MemberFilePersister persister = getMemberFilePersister((String) memberList
 363  
                         .get(i));
 364  0
                 LOG.info("Loading member with "
 365  
                         + persister.getClass().getName());
 366  0
                 inputStream.reopen(persister.getMainTag());
 367  
                 // TODO: Do we need to set the input encoding here? It was
 368  
                 // done for ToDo parsing, but none of the other member types
 369  
                 // InputSource inputSource = new InputSource(
 370  
                 // new InputStreamReader(inputStream, Argo
 371  
                 // .getEncoding()));
 372  0
                 InputSource inputSource = new InputSource(inputStream);
 373  
                 // Don't use systemId here or it will get opened in preference
 374  
                 // to inputStream.
 375  0
                 inputSource.setPublicId(originalFile.toURI().toURL()
 376  
                         .toExternalForm());
 377  
                 try {
 378  0
                     persister.load(p, inputSource);
 379  0
                 } catch (OpenException e) {
 380  
                     // UML 2.x files could also contain a profile model.
 381  
                     // Try again with uml:Profile as main tag.
 382  0
                     if ("uml:Model".equals(persister.getMainTag())
 383  
                             && e.getCause() instanceof UmlException
 384  
                             && e.getCause().getCause() instanceof IOException) {
 385  0
                         inputStream.reopen("uml:Profile");
 386  0
                         persister.load(p, inputSource);
 387  0
                         p.setProjectType(Project.PROFILE_PROJECT);
 388  
                     } else {
 389  0
                         throw e;
 390  
                     }
 391  0
                 }
 392  
             }
 393  
 
 394  
             // let's update the progress
 395  0
             progressMgr.nextPhase();
 396  0
             ThreadUtils.checkIfInterrupted();
 397  0
             inputStream.realClose();
 398  0
             p.postLoad();
 399  0
             return p;
 400  0
         } catch (InterruptedException e) {
 401  0
             throw e;
 402  0
         } catch (OpenException e) {
 403  0
             throw e;
 404  0
         } catch (IOException e) {
 405  0
             throw new OpenException(e);
 406  0
         } catch (SAXException e) {
 407  0
             throw new OpenException(e);
 408  
         }
 409  
     }
 410  
 
 411  
     protected boolean checkVersion(int fileVersion, String releaseVersion)
 412  
         throws OpenException, VersionException {
 413  
         // If we're trying to load a file from a future version
 414  
         // complain and refuse.
 415  0
         if (fileVersion > PERSISTENCE_VERSION) {
 416  0
             throw new VersionException(
 417  
                     "The file selected is from a more up to date version of "
 418  
                             + "ArgoUML. It has been saved with ArgoUML version "
 419  
                             + releaseVersion
 420  
                             + ". Please load with that or a more up to date"
 421  
                             + "release of ArgoUML");
 422  
         }
 423  0
         return fileVersion >= PERSISTENCE_VERSION;
 424  
     }
 425  
 
 426  
     /**
 427  
      * Transform a string of XML data according to the service required.
 428  
      * 
 429  
      * @param file The XML file to be transformed
 430  
      * @param version the version of the persistence format the XML is to be
 431  
      *            transformed to.
 432  
      * @return the transformed XML file
 433  
      * @throws OpenException on XSLT transformation error or file read
 434  
      */
 435  
     public final File transform(File file, int version) throws OpenException {
 436  
 
 437  
         try {
 438  0
             String upgradeFilesPath = "/org/argouml/persistence/upgrades/";
 439  0
             String upgradeFile = "upgrade" + version + ".xsl";
 440  
 
 441  0
             String xsltFileName = upgradeFilesPath + upgradeFile;
 442  0
             URL xsltUrl = UmlFilePersister.class.getResource(xsltFileName);
 443  0
             LOG.info("Resource is " + xsltUrl);
 444  
 
 445  
             // Read xsltStream into a temporary file
 446  
             // Get url for temp file.
 447  
             // openStream from url and wrap in StreamSource
 448  0
             StreamSource xsltStreamSource = new StreamSource(xsltUrl
 449  
                     .openStream());
 450  0
             xsltStreamSource.setSystemId(xsltUrl.toExternalForm());
 451  
 
 452  0
             TransformerFactory factory = TransformerFactory.newInstance();
 453  0
             Transformer transformer = factory.newTransformer(xsltStreamSource);
 454  
 
 455  0
             File transformedFile = File.createTempFile("upgrade_" + version
 456  
                     + "_", ".uml");
 457  0
             transformedFile.deleteOnExit();
 458  
 
 459  0
             FileOutputStream stream = new FileOutputStream(transformedFile);
 460  0
             Writer writer = new BufferedWriter(new OutputStreamWriter(stream,
 461  
                     Argo.getEncoding()));
 462  0
             Result result = new StreamResult(writer);
 463  
 
 464  0
             StreamSource inputStreamSource = new StreamSource(file);
 465  0
             inputStreamSource.setSystemId(file);
 466  0
             transformer.transform(inputStreamSource, result);
 467  
 
 468  0
             writer.close();
 469  0
             return transformedFile;
 470  0
         } catch (IOException e) {
 471  0
             throw new OpenException(e);
 472  0
         } catch (TransformerException e) {
 473  0
             throw new OpenException(e);
 474  
         }
 475  
     }
 476  
 
 477  
     /**
 478  
      * Read stream in .argo format and extracts the persistence version number
 479  
      * from the root tag.
 480  
      * 
 481  
      * @param file the XML file
 482  
      * @return The version number
 483  
      * @throws OpenException on any error
 484  
      */
 485  
     private int getPersistenceVersionFromFile(File file) throws OpenException {
 486  0
         InputStream stream = null;
 487  
         try {
 488  0
             stream = new BufferedInputStream(file.toURI().toURL().openStream());
 489  0
             int version = getPersistenceVersion(stream);
 490  0
             stream.close();
 491  0
             return version;
 492  0
         } catch (MalformedURLException e) {
 493  0
             throw new OpenException(e);
 494  0
         } catch (IOException e) {
 495  0
             throw new OpenException(e);
 496  
         } finally {
 497  0
             if (stream != null) {
 498  
                 try {
 499  0
                     stream.close();
 500  0
                 } catch (IOException e) {
 501  
                     // ignore
 502  0
                 }
 503  
             }
 504  
         }
 505  
     }
 506  
 
 507  
     /**
 508  
      * Reads an XML file of uml format and extracts the persistence version
 509  
      * number from the root tag.
 510  
      * 
 511  
      * @param inputStream stream pointing to file to read.
 512  
      * @return The version number
 513  
      * @throws OpenException on any error
 514  
      */
 515  
     protected int getPersistenceVersion(InputStream inputStream)
 516  
         throws OpenException {
 517  
 
 518  0
         BufferedReader reader = null;
 519  
         try {
 520  0
             reader = new BufferedReader(new InputStreamReader(inputStream, Argo
 521  
                     .getEncoding()));
 522  0
             String rootLine = reader.readLine();
 523  0
             while (rootLine != null && !rootLine.trim().startsWith("<argo ")) {
 524  0
                 rootLine = reader.readLine();
 525  
             }
 526  0
             if (rootLine == null) {
 527  0
                 return 1;
 528  
             }
 529  0
             return Integer.parseInt(getVersion(rootLine));
 530  0
         } catch (IOException e) {
 531  0
             throw new OpenException(e);
 532  0
         } catch (NumberFormatException e) {
 533  0
             throw new OpenException(e);
 534  
         } finally {
 535  0
             try {
 536  0
                 if (reader != null) {
 537  0
                     reader.close();
 538  
                 }
 539  0
             } catch (IOException e) {
 540  
                 // No more we can do here on failure
 541  0
             }
 542  
         }
 543  
     }
 544  
 
 545  
     /**
 546  
      * Reads an XML file of uml format and extracts the persistence version
 547  
      * number from the root tag.
 548  
      * 
 549  
      * @param file the XML file
 550  
      * @return The ArgoUML release number
 551  
      * @throws OpenException on any error
 552  
      */
 553  
     private String getReleaseVersionFromFile(File file) throws OpenException {
 554  0
         InputStream stream = null;
 555  
         try {
 556  0
             stream = new BufferedInputStream(file.toURI().toURL().openStream());
 557  0
             String version = getReleaseVersion(stream);
 558  0
             stream.close();
 559  0
             return version;
 560  0
         } catch (MalformedURLException e) {
 561  0
             throw new OpenException(e);
 562  0
         } catch (IOException e) {
 563  0
             throw new OpenException(e);
 564  
         } finally {
 565  0
             if (stream != null) {
 566  
                 try {
 567  0
                     stream.close();
 568  0
                 } catch (IOException e) {
 569  
                     // ignore
 570  0
                 }
 571  
             }
 572  
         }
 573  
     }
 574  
 
 575  
     /**
 576  
      * Reads an XML file of uml format and extracts the persistence version
 577  
      * number from the root tag.
 578  
      * 
 579  
      * @param inputStream the stream point to the XML file
 580  
      * @return The ArgoUML release number
 581  
      * @throws OpenException on any error
 582  
      */
 583  
     protected String getReleaseVersion(InputStream inputStream)
 584  
         throws OpenException {
 585  
 
 586  0
         BufferedReader reader = null;
 587  
         try {
 588  0
             reader = new BufferedReader(new InputStreamReader(inputStream, Argo
 589  
                     .getEncoding()));
 590  0
             String versionLine = reader.readLine();
 591  0
             while (!versionLine.trim().startsWith("<version>")) {
 592  0
                 versionLine = reader.readLine();
 593  0
                 if (versionLine == null) {
 594  0
                     throw new OpenException(
 595  
                             "Failed to find the release <version> tag");
 596  
                 }
 597  
             }
 598  0
             versionLine = versionLine.trim();
 599  0
             int end = versionLine.lastIndexOf("</version>");
 600  0
             return versionLine.trim().substring(9, end);
 601  0
         } catch (IOException e) {
 602  0
             throw new OpenException(e);
 603  0
         } catch (NumberFormatException e) {
 604  0
             throw new OpenException(e);
 605  
         } finally {
 606  0
             try {
 607  0
                 if (inputStream != null) {
 608  0
                     inputStream.close();
 609  
                 }
 610  0
                 if (reader != null) {
 611  0
                     reader.close();
 612  
                 }
 613  0
             } catch (IOException e) {
 614  
                 // No more we can do here on failure
 615  0
             }
 616  
         }
 617  
     }
 618  
 
 619  
     /**
 620  
      * Get the version attribute value from a string of XML.
 621  
      * 
 622  
      * @param rootLine the line
 623  
      * @return the version
 624  
      */
 625  
     protected String getVersion(String rootLine) {
 626  
         String version;
 627  0
         int versionPos = rootLine.indexOf("version=\"");
 628  0
         if (versionPos > 0) {
 629  0
             int startPos = versionPos + 9;
 630  0
             int endPos = rootLine.indexOf("\"", startPos);
 631  0
             version = rootLine.substring(startPos, endPos);
 632  0
         } else {
 633  0
             version = "1";
 634  
         }
 635  0
         return version;
 636  
     }
 637  
 
 638  
     /**
 639  
      * Returns true. All Argo specific files have an icon.
 640  
      * 
 641  
      * {@inheritDoc}
 642  
      */
 643  
     public boolean hasAnIcon() {
 644  0
         return true;
 645  
     }
 646  
 
 647  
     /**
 648  
      * Class to filter XML declaration and DOCTYPE declaration from an output
 649  
      * stream to allow use as nested XML files.
 650  
      * 
 651  
      * @author Tom Morris
 652  
      */
 653  
     class XmlFilterOutputStream extends FilterOutputStream {
 654  
 
 655  
         private CharsetDecoder decoder;
 656  
 
 657  0
         private boolean headerProcessed = false;
 658  
 
 659  
         private static final int BUFFER_SIZE = 120;
 660  
 
 661  
         /**
 662  
          * The following three fields make up a doubly mapped buffer with two
 663  
          * sets of pointers (the ByteBuffer objects), one for input and one for
 664  
          * output. Bytes are written into the buffer using outBB which advances
 665  
          * the current position. The following ASCII art shows what the buffer
 666  
          * looks like.<code>
 667  
          *           vp       vl
 668  
          * xxxxxx++++..........
 669  
          *       ^p  ^l
 670  
          * </code> vp & vl are the output (buffer writing) position and limit,
 671  
          * respectively and show the free space that can receive bytes. The ^p
 672  
          * and ^l pointers show the input (buffer reading) position and limit.
 673  
          * They point to valid bytes which have not yet been converted to
 674  
          * characters. The xxx bytes have been both written in and read back
 675  
          * out. The +++ bytes have been written into the buffer, but not read
 676  
          * out yet.
 677  
          */
 678  0
         private byte[] bytes = new byte[BUFFER_SIZE * 2];
 679  
 
 680  0
         private ByteBuffer outBB = ByteBuffer.wrap(bytes);
 681  
 
 682  0
         private ByteBuffer inBB = ByteBuffer.wrap(bytes);
 683  
 
 684  
         // Buffer containing characters which have been decoded from the bytes
 685  
         // in inBB.
 686  0
         private CharBuffer outCB = CharBuffer.allocate(BUFFER_SIZE);
 687  
 
 688  
         // RegEx pattern for XML declaration and, optionally, DOCTYPE
 689  
         // Backslashes are doubled up - one for Java, one for Regex
 690  0
         private final Pattern xmlDeclarationPattern = Pattern
 691  
                 .compile("\\s*<\\?xml.*\\?>\\s*(<!DOCTYPE.*>\\s*)?");
 692  
 
 693  
         /**
 694  
          * Construct a filtered output stream using the given character set
 695  
          * name.
 696  
          * 
 697  
          * @param outputStream source output stream to filter
 698  
          * @param charsetName name of character set to use for encoding
 699  
          */
 700  
         public XmlFilterOutputStream(OutputStream outputStream,
 701  
                 String charsetName) {
 702  0
             this(outputStream, Charset.forName(charsetName));
 703  0
         }
 704  
 
 705  
         /**
 706  
          * Construct a filtered output stream using the given character set.
 707  
          * 
 708  
          * @param outputStream source output stream to filter
 709  
          * @param charset character set to use for encoding
 710  
          */
 711  0
         public XmlFilterOutputStream(OutputStream outputStream, Charset charset) {
 712  0
             super(outputStream);
 713  0
             decoder = charset.newDecoder();
 714  0
             decoder.onMalformedInput(CodingErrorAction.REPORT);
 715  0
             decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
 716  0
             startEntry();
 717  0
         }
 718  
 
 719  
         /**
 720  
          * Reset processing for the beginning of an entry.
 721  
          */
 722  
         public void startEntry() {
 723  0
             headerProcessed = false;
 724  0
             resetBuffers();
 725  0
         }
 726  
 
 727  
         private void resetBuffers() {
 728  0
             inBB.limit(0);
 729  0
             outBB.position(0);
 730  0
             outCB.position(0);
 731  0
         }
 732  
 
 733  
         @Override
 734  
         public void write(byte[] b, int off, int len) throws IOException {
 735  0
             if ((off | len | (b.length - (len + off)) | (off + len)) < 0) {
 736  0
                 throw new IndexOutOfBoundsException();
 737  
             }
 738  
 
 739  0
             if (headerProcessed) {
 740  0
                 out.write(b, off, len);
 741  
             } else {
 742  
                 // TODO: Make this more efficient for large I/Os
 743  0
                 for (int i = 0; i < len; i++) {
 744  0
                     write(b[off + i]);
 745  
                 }
 746  
             }
 747  
 
 748  0
         }
 749  
 
 750  
         @Override
 751  
         public void write(int b) throws IOException {
 752  
 
 753  0
             if (headerProcessed) {
 754  0
                 out.write(b);
 755  
             } else {
 756  0
                 outBB.put((byte) b);
 757  0
                 inBB.limit(outBB.position());
 758  
                 // Convert from bytes back to characters
 759  0
                 CoderResult result = decoder.decode(inBB, outCB, false);
 760  0
                 if (result.isError()) {
 761  0
                     throw new RuntimeException(
 762  
                             "Unknown character decoding error");
 763  
                 }
 764  
 
 765  
                 // This will have problems if the smallest possible
 766  
                 // data segment is smaller than the size of the buffer
 767  
                 // needed for regex matching
 768  0
                 if (outCB.position() == outCB.limit()) {
 769  0
                     processHeader();
 770  
                 }
 771  
 
 772  
             }
 773  0
         }
 774  
 
 775  
         private void processHeader() throws IOException {
 776  0
             headerProcessed = true;
 777  0
             outCB.position(0); // rewind our character buffer
 778  
 
 779  0
             Matcher matcher = xmlDeclarationPattern.matcher(outCB);
 780  
             // Remove anything that matches our pattern
 781  0
             String headerRemainder = matcher.replaceAll("");
 782  0
             int index = headerRemainder.length() - 1;
 783  0
             if (headerRemainder.charAt(index) == '\0') {
 784  
                 // Remove null characters at the end
 785  
                 do {
 786  0
                     index--;
 787  0
                 } while (index >= 0 && headerRemainder.charAt(index) == '\0');
 788  0
                 headerRemainder = headerRemainder.substring(0, index + 1);
 789  
             }
 790  
 
 791  
             // Reencode the remaining characters as bytes again
 792  0
             ByteBuffer bb = decoder.charset().encode(headerRemainder);
 793  
 
 794  
             // and write them to our output stream
 795  0
             byte[] outBytes = new byte[bb.limit()];
 796  0
             bb.get(outBytes);
 797  0
             out.write(outBytes, 0, outBytes.length);
 798  
 
 799  
             // Write any left over bytes in the input buffer
 800  
             // (perhaps from a partially decoded character)
 801  0
             if (inBB.remaining() > 0) {
 802  0
                 out.write(inBB.array(), inBB.position(), inBB.remaining());
 803  0
                 inBB.position(0);
 804  0
                 inBB.limit(0);
 805  
             }
 806  0
         }
 807  
 
 808  
         /**
 809  
          * This method has no effect to keep sub-writers from closing it
 810  
          * accidently. The master can use the method {@link #reallyClose()} to
 811  
          * actually close the underlying stream.
 812  
          */
 813  
         @Override
 814  
         public void close() throws IOException {
 815  0
             flush();
 816  0
         }
 817  
 
 818  
         /**
 819  
          * Close the stream.
 820  
          * 
 821  
          * @throws IOException
 822  
          */
 823  
         public void reallyClose() throws IOException {
 824  0
             out.close();
 825  0
         }
 826  
 
 827  
         /**
 828  
          * Flush the stream. If the stream is flushed before the header is
 829  
          * completely processed, whatever has been written so far will get
 830  
          * processed before the flush.
 831  
          */
 832  
         @Override
 833  
         public void flush() throws IOException {
 834  0
             if (!headerProcessed) {
 835  0
                 processHeader();
 836  
             }
 837  0
             out.flush();
 838  0
         }
 839  
 
 840  
     }
 841  
 }