Java Source Code: edu.byu.deg.osmx.OSMXDocument


   1: /*
   2:  * OSMXDocument.java
   3:  *
   4:  * Created on May 31, 2003, 1:13 PM
   5:  */
   6: 
   7: package edu.byu.deg.osmx;
   8: 
   9: import java.util.*;
  10: import java.net.URL;
  11: import java.net.URI;
  12: import java.net.URISyntaxException;
  13: import java.io.*;
  14: import edu.byu.deg.osmx.binding.*;
  15: 
  16: import javax.xml.bind.*;
  17: import java.beans.PropertyChangeEvent;
  18: import java.beans.PropertyChangeListener;
  19: 
  20: import edu.byu.deg.framework.Ontology;
  21: 
  22: /** Encapsulates the behavior of an XML document that adheres to the OSMX schema.
  23:  * Features include creation of an OSMX document, opening an existing OSMX
  24:  * document, and saving changes.
  25:  * @author Alan Wessman
  26:  */
  27:	  public class OSMXDocument implements Ontology {
  28:    
  29:    // ctxt: used in JAXB marshalling/unmarshalling
  30:    private static JAXBContext ctxt;
  31:    // marshaller: used to serialize (marshal) Java objects to XML via JAXB
  32:    private static Marshaller marshaller;
  33:    // unmarshaller: used to de-serialize (unmarshal) Java objects from XML via JAXB
  34:    private static Unmarshaller unmarshaller;
  35:    // objectFactory: creates instances of OSMX elements
  36:    private ObjectFactory objectFactory;
  37:    // idGen: generates unique IDs (within this document) for new OSMX elements
  38:    private IDGenerator idGen;
  39:    // docURI: uri of this OSMX document
  40:    private URI docURI;
  41:    
  42:    // string constants used to create JAXB context
  43:    private static final String OSMX_BINDING_PKG = "edu.byu.deg.osmx.binding";
  44:    private static final String OSMX_SCHEMA_LOCATION = "http://www.deg.byu.edu/xml/osmx.xsd http://www.deg.byu.edu/xml/osmx.xsd";
  45:    
  46:    // counter for naming new documents: Untitled-1, Untitled-2, etc.
  47:    private static int newDocNumber = 0;
  48:    
  49:    /** The {@link edu.byu.deg.osmx.binding.OSMType OSM} element that serves as the root
  50:     * of the document hierarchy.
  51:     */
  52:    protected OSM modelRoot;
  53:    /** A mapping of element IDs to the actual elements.  Used as an index to locate
  54:     * elements by their IDs.
  55:     */
  56:    protected Map modelElements;
  57:    /** Flag indicating that a document's state has changed since it was last created,
  58:     * opened, or saved.
  59:     */
  60:    protected boolean modified;
  61:    /** Remembers the file name and path after document has been opened or saved so
  62:     * the document can be saved to the same location later.
  63:     */
  64:    protected String defaultOutputFilename;
  65:    
  66:    //  object registered to receive DocumentChangeEvents
  67:    private List docChangeListeners; 
  68:    // name or title of document (not filename)
  69:    private String title;
  70:    // controls whether to fire DocumentChangeEvent notifications
  71:    // when changes occur; useful when document is unmarshalling
  72:    // and in an unstable state
  73:    // REVISIT: there may be a better strategy for handling this problem
  74:    private boolean generateDocumentChangeEvents = true;
  75:    
  76:    // static initializer just guarantees the current context is null
  77:    // We instantiate the context in the constructor because the factory
  78:    // method requires a classloader, which we reference via the 'this' object.
  79:	      static {
  80:        ctxt = null;
  81:    }
  82:    
  83:    /** Creates a new instance of OSMXDocument */
  84:	      public OSMXDocument() {
  85:        // guarantee existence of the JAXB context object
  86:	          if(ctxt==null){
  87:	                try {
  88:                  ctxt = JAXBContext.newInstance(OSMX_BINDING_PKG, this.getClass().getClassLoader());
  89:                  marshaller = ctxt.createMarshaller();
  90:                  unmarshaller = ctxt.createUnmarshaller();
  91:                  //fix for classloader bug in jaxb see
  92:                  // http://forum.java.sun.com/thread.jsp?forum=34&thread=320813&start=45&range=15&hilite=false&q=
  93:              }
  94:	                catch (Exception e) {
  95:                  e.printStackTrace();
  96:                  // TODO: better error handling here (this should be a fatal error)
  97:              }
  98:        }
  99:        // create ID generator before doing any JAXB stuff
 100:        idGen = new IDGenerator();
 101:        
 102:        // note custom ObjectFactory subclass - it keeps track of created elements
 103:        // by adding them to the document's id/element index
 104:        objectFactory = new OSMXObjectFactory();
 105:        
 106:        // no content in the document at this point
 107:        modelRoot = null;
 108:        
 109:        // create the id/element index
 110:        modelElements = new HashMap();
 111:        
 112:        // we don't know where on the filesystem the document belongs yet
 113:        defaultOutputFilename = null;
 114:        
 115:        // use LinkedList: primary uses are iteration and adding to list;
 116:        // random access not as important here so LinkedList should perform better
 117:        docChangeListeners = new LinkedList();
 118:        
 119:        // never modified to begin with
 120:        setModified(false);
 121:    } // constructor
 122:    
 123:    /** Locates an {@link OSMXElement } given its internal ID.
 124:     * @param id The ID of the desired element.
 125:     * @return The <CODE>OSMXElement</CODE> with the given ID, or <CODE>null</CODE> if no such
 126:     * object exists in the document's element index.
 127:     */
 128:	      public OSMXElement getElement(String id) {
 129:        return (OSMXElement) modelElements.get(id);
 130:    } // method getElement
 131:    
 132:    /** Provides access to the document's index of elements.
 133:     * @return An unmodifiable {@link java.util.Map} of elements indexed by their internal ID.
 134:     */
 135:	      public Map getModelElements() {
 136:        return Collections.unmodifiableMap(modelElements);
 137:    } // method getModelElements
 138:    
 139:    /** Returns whether the document has been modified since it was last created,
 140:     * opened, or saved.
 141:     * @return <CODE>true</CODE> if the document has been modified; <CODE>false</CODE>
 142:     * otherwise.
 143:     */
 144:	      public boolean isModified() {
 145:        return modified;
 146:    } // method isModified
 147:    
 148:    /** Sets the "modified" flag for the document.
 149:     * @param doModify The intended value of the document modification state.
 150:     */
 151:	      protected void setModified(boolean doModify) {
 152:        modified = doModify;
 153:    } // method setModified
 154:    
 155:    /** Adds the specified {@link OSMXElement } to the document to allow later lookup by
 156:     * element ID as well as detection of element property changes and element deletion
 157:     * events.
 158:     * If <CODE>elem</CODE> is null, the method is a no-op.
 159:     * If <CODE>elem</CODE> has no internal ID, a unique one is generated.
 160:     * If another element with the same ID already belongs to the document, <CODE>elem</CODE>
 161:     * keeps its internal ID and a new ID is generated for the other element.
 162:     * The document is registered as an ID change listener, property change listener, and
 163:     * element deletion listener on the added element.
 164:     * @param elem The OSMXElement to add to the document.  The method will perform no action if
 165:     * <CODE>elem</CODE> is <CODE>null</CODE>.
 166:     */
 167:	      public void addElement(OSMXElement elem) {
 168:        // can't add a null element
 169:        if (elem == null) return;
 170:        
 171:        // set reference in element back to this document
 172:        if (elem.getParentDocument() != this) elem.setParentDocument(this);
 173:        
 174:        /* This next part is concerned with indexing the element by its ID.
 175:         * If another element already exists in the index under the ID of the
 176:         * added element, we have to give one of them a new ID.
 177:         */
 178:        // determine key for Map
 179:        String id = elem.getInternalID();
 180:        
 181:        // find any existing entry in the Map for the key
 182:        Object idOwner = modelElements.get(id);
 183:        
 184:        // how we place the element in the Map depends on any existing entry
 185:	          if (idOwner == elem) {
 186:            // early exit if the element already exists for that ID
 187:            return;
 188:        } else if (idOwner == null) {
 189:            // no entry exists in the map for the given key
 190:            
 191:            // generate an ID if the element doesn't have one already
 192:	              if (id == null || id.length() == 0) {
 193:                id = idGen.generateID();
 194:                
 195:                // element gets the generated ID
 196:                elem.setInternalID(id);
 197:            }
 198:            // place the element in the map for the ID
 199:            modelElements.put(id, elem);
 200:        } else {
 201:            // id cannot be null; else we would have a null idOwner
 202:            
 203:            /* We now have an element with the ID attribute set, trying to get the
 204:             * spot in the Map for its ID.  But there's already a different element
 205:             * there.  We assume that elem is the rightful owner of that spot and
 206:             * therefore ought to displace the one that we found in the Map,
 207:             * giving it a new ID and place in the Map.
 208:             * 
 209:             * It seems backwards to make the existing element
 210:             * change its ID instead of the new one, but we do it this way
 211:             * because changing the new element's ID might break any references
 212:             * that another to-be-added element might have to it.
 213:             */
 214:            
 215:            // generate new ID for idOwner to receive; reuse id variable since we
 216:            // have access to the original id value thru elem.
 217:            id = idGen.generateID();
 218:            
 219:            // remove idOwner from the Map
 220:            modelElements.remove(id);
 221:            
 222:            // insert elem into the Map in its rightful place
 223:            modelElements.put(elem.getInternalID(), elem);
 224:            
 225:            // change the ID of idOwner to the new ID and put it in the Map
 226:            ((OSMXElement) idOwner).setInternalID(id);
 227:            modelElements.put(id, idOwner);
 228:        }
 229:        
 230:        /* At this point we are guaranteed that elem is found in the Map under
 231:         * its internalID.  If elem's internalID should change for any reason,
 232:         * this OSMXDocument needs to know about it.  The next part sets up the
 233:         * necessary listeners to handle this.
 234:         */
 235:        
 236:        // register as internal ID change listener on the newly created element
 237:        // we declare this listener as a final local variable so we can refer to it
 238:        // later on in the OSMXElementDeletionListener
 239:	          final OSMXElement.InternalIDChangeListener iidcl = new OSMXElement.InternalIDChangeListener() {
 240:	              public void propertyChange(PropertyChangeEvent evt) {
 241:                String oldID = (String) evt.getOldValue();
 242:                String newID = (String) evt.getNewValue();
 243:                modelElements.remove(oldID);
 244:                modelElements.put(newID, evt.getSource());
 245:            }
 246:        };
 247:        elem.addInternalIDChangeListener(iidcl);
 248:        
 249:        // register as property change listener so any change to the element is
 250:        // detected as a change to the document, so we can set the modified
 251:        // flag accordingly.
 252:	          final PropertyChangeListener pcl = new PropertyChangeListener() {
 253:	              public void propertyChange(PropertyChangeEvent evt) {
 254:                // set modified to true on any property change from elem
 255:                setModified(true);
 256:            }
 257:        };
 258:        elem.addPropertyChangeListener(pcl);
 259:        
 260:        // Having added the element to the document, we need to provide a way to
 261:        // unhook all listeners and remove the element when it is deleted.  The
 262:        // code below performs this step.
 263:        
 264:        // register as deletion listener on the newly created element
 265:	          elem.addElementDeletionListener(new OSMXElement.OSMXElementDeletionListener() {
 266:	              public void elementDeleted(OSMXElement elem) {
 267:                String id = elem.getInternalID();
 268:                modelElements.remove(id);
 269:                
 270:                // iidcl is the OSMXElement.InternalIDChangeListener declared locally above
 271:                elem.removeInternalIDChangeListener(iidcl);
 272:                
 273:                // pcl is the PropertyChangeListener declared locally
 274:                elem.removePropertyChangeListener(pcl);
 275:                
 276:                // element was deleted so set dirty flag
 277:                setModified(true);
 278:            }
 279:        }); // addElementDeletionListener call
 280:        
 281:        // At this point the element has been added to the document with all
 282:        // necessary listeners to detect ID changes or deletions.  Now we just need
 283:        // to let any DocumentChangeListeners know that the document state has
 284:        // changed.
 285:        
 286:        // fire DocumentChangeEvent unless such notification is disabled
 287:	          if (generateDocumentChangeEvents) {
 288:            DocumentChangeEvent dce = new DocumentChangeEvent(OSMXDocument.this, DocumentChangeEvent.CREATE_ELEMENT_EVENT);
 289:            dce.setElement(elem);
 290:            // copy list in case original list changes during event processing
 291:            List dclList = new LinkedList(docChangeListeners);
 292:	              for (Iterator iter = dclList.iterator(); iter.hasNext();) {
 293:                DocumentChangeListener dcl = (DocumentChangeListener) iter.next();
 294:                dcl.stateChanged(dce);
 295:            }
 296:        }
 297:        
 298:    } // end method addElement
 299:    
 300:    /** Sets whether {@link DocumentChangeEvent }s are generated when changes occur in
 301:     * the document.  A new or fully opened document is set to generate these events by
 302:     * default; set to <CODE>false</CODE> to turn off events if they need to be
 303:     * ignored.
 304:     * @param generates <CODE>true</CODE> to allow <CODE>DocumentChangeEvent</CODE>s to be generated;
 305:     * <CODE>false</CODE> otherwise.
 306:     */
 307:	      public void setGeneratesDocumentChangeEvents(boolean generates) {
 308:        generateDocumentChangeEvents = generates;
 309:    } // method setGeneratesDocumentChangeEvents
 310:    
 311:    /** Loads the data from the OSMX document at the URL specified.  This version of the
 312:     * method invokes {@link #openDocument(java.io.Reader) } internally.
 313:     * @param uri The location of the OSMX document, either local or on the Internet.
 314:     * @throws IOException Thrown in the event of an I/O error when opening or reading the file.
 315:     * @throws JAXBException Thrown in the event of invalid XML or some other JAXB processing error.
 316:     * @return An <code>OSMXDocument</code> with content unmarshalled from the resource identified by the input URL.
 317:     */
 318:	      public static OSMXDocument openDocument(URL uri) throws IOException, JAXBException {
 319:        OSMXDocument doc = openDocument(new InputStreamReader(uri.openStream()));
 320:	          try {
 321:            doc.docURI = new URI(uri.toString());
 322:        } catch (URISyntaxException urise) {
 323:            throw new IOException("Invalid URI: " + uri.toString());
 324:        }
 325:        return doc;
 326:    } // method openDocument(URL)
 327:    
 328:    /** Opens an OSMX document given a <CODE>String</CODE> representing a local file
 329:     * path and name.  This version of the method invokes
 330:     * {@link #openDocument(java.io.Reader) } internally.
 331:     * @param filename The local file path and name.  May be relative or absolute.
 332:     * @throws IOException Thrown in the event of an I/O error when opening or reading the file.
 333:     * @throws JAXBException Thrown in the event of invalid XML or some other JAXB processing error.
 334:     * @return An <code>OSMXDocument</code> with content unmarshalled from the file or resource identified by the input string.
 335:     */
 336:	      public static OSMXDocument openDocument(String filename) throws IOException, JAXBException {
 337:        OSMXDocument doc = null;
 338:	          try {
 339:            doc = openDocument(new URL(filename));
 340:        } catch (IOException e) {
 341:            doc = openDocument(new File(filename));
 342:        }
 343:        return doc;
 344:    } // method openDocument(String)
 345:    
 346:    /** Factory method that opens an OSMX document from a {@link java.io.Reader }.
 347:     * @param r The Reader from which to read the OSMX data.
 348:     * @throws JAXBException Thrown in the event of invalid XML or some other JAXB processing error.
 349:     * @return A new <code>OSMXDocument</code> object with content unmarshalled from the <code>Reader</code>.
 350:     */
 351:	      public static OSMXDocument openDocument(Reader r) throws JAXBException {
 352:        OSMXDocument doc = new OSMXDocument();
 353:        
 354:        // don't bother firing events since nobody's listening
 355:        doc.setGeneratesDocumentChangeEvents(false);
 356:        
 357:        // the u.unmarshal call does the work of turning XML into Java objects
 358:        // REVISIT: we should explicitly check that the object returned
 359:        // by the unmarshaller is an 'OSM' element, since the schema
 360:        // itself does not specify a particular element as root.
 361:        // If the unmarshaller does not return an OSM element, we should
 362:        // throw a custom exception.
 363:        doc.setModelRoot((OSM) unmarshaller.unmarshal(new org.xml.sax.InputSource(r)));
 364:        
 365:        // get the document title from the name stored in the document
 366:        doc.setTitle(doc.modelRoot.getOntologyName());
 367:        
 368:        doc.docURI = null;
 369:        
 370:        // re-enable document change events
 371:        doc.setGeneratesDocumentChangeEvents(true);
 372:        
 373:        return doc;
 374:    } // method openDocument(Reader)
 375:    
 376:    /** Factory method that opens an OSMX document from the specified {@link java.io.File}.  This version of the method invokes
 377:     * {@link #openDocument(java.io.Reader) } internally.
 378:     * @param f The <CODE>File</CODE> containing the OSMX document.
 379:     * @throws IOException Thrown in the event of an I/O error when opening or reading the file.
 380:     * @throws JAXBException Thrown in the event of invalid XML or some other JAXB processing error.
 381:     * @return An <code>OSMXDocument</code> with content unmarshalled from the input <code>File</code>.
 382:     */
 383:	      public static OSMXDocument openDocument(File f) throws IOException, JAXBException {
 384:        OSMXDocument doc = openDocument(new FileReader(f));
 385:        // remember where we opened the document from so we can save to it later
 386:        doc.setOutputFilename(f.getAbsolutePath());
 387:        doc.docURI = f.toURI();
 388:        return doc;
 389:    } // method openDocument(File)
 390:    
 391:    /** Creates a valid but (mostly) empty OSMX document, ready for adding new elements.
 392:     * The new document contains only a root {@link edu.byu.deg.osmx.binding.OSM }
 393:     * element.  Calling {@link #isModified} after this call will return
 394:     * <CODE>false</CODE>.
 395:     * @throws JAXBException Thrown if there is a problem creating the root <CODE>OSM</CODE> element.
 396:     * @return A new <code>OSMXDocument</code> with no content except for the root element.
 397:     */
 398:	      public static OSMXDocument newDocument() throws JAXBException {
 399:        OSMXDocument doc = new OSMXDocument();
 400:        
 401:        // don't bother firing events because nobody's listening yet
 402:        doc.setGeneratesDocumentChangeEvents(false);
 403:        
 404:        // satisfy postcondition that the document is valid OSMX if saved
 405:        doc.setModelRoot(doc.objectFactory.createOSM());
 406:        
 407:        // we don't want users to be prompted to save if an untouched document is closed
 408:        doc.setModified(false);
 409:        
 410:        // no URI for a newly created document
 411:        doc.docURI = null;
 412:        
 413:        // enable document change event firing
 414:        doc.setGeneratesDocumentChangeEvents(true);
 415:        
 416:        return doc;
 417:    } // method newDocument
 418:    
 419:    /**
 420:     * Stores the name of the file to which the OSMX document should be saved.
 421:     * @param filename The destination filename for the document.
 422:     */    
 423:	      public void setOutputFilename(String filename) {
 424:        defaultOutputFilename = filename;
 425:    }
 426:    
 427:    /** Marshals the in-memory OSMX objects and writes them to a file.
 428:     * @throws UnspecifiedOutputException Thrown when the document has not been opened from or saved to a local file yet.
 429:     * In such a case, there is no way to know where to save to and no way to prompt
 430:     * (since there is no UI at this level).
 431:     * @throws JAXBException Thrown when a JAXB marshalling error occurs.  The XML object tree may be
 432:     * invalid, or an unusual processing error could have occurred.
 433:     * @throws IOException Thrown when the output file cannot be written for some reason.
 434:     */
 435:	      public void saveDocument() throws UnspecifiedOutputException, JAXBException, IOException {
 436:        // we can't save without knowing the destination
 437:	          if (defaultOutputFilename == null || defaultOutputFilename.equals("")) {
 438:            throw new UnspecifiedOutputException("No output destination specified.");
 439:        }
 440:        // set marshaller to indent the XML properly
 441:        marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
 442:        // set marshaller to write XML to reference online schema to enable validation
 443:        marshaller.setProperty( Marshaller.JAXB_SCHEMA_LOCATION, OSMX_SCHEMA_LOCATION );
 444:        File outFile = new File(defaultOutputFilename);
 445:        marshaller.marshal(modelRoot, new FileWriter(outFile));
 446:        
 447:        // not modified anymore
 448:        setModified(false);
 449:    } // method saveDocument
 450:    
 451:    /** Writes the OSMX document to the specified {@link java.io.Writer}.
 452:     * @param w The output <CODE>Writer</CODE>.
 453:     * @throws JAXBException Thrown if the JAXB marshalling process fails for some reason.
 454:     */
 455:	      public void saveAsDocument(Writer w) throws JAXBException {
 456:        // set marshaller to indent the XML properly
 457:        marshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE );
 458:        // set marshaller to write XML to reference online schema to enable validation
 459:        marshaller.setProperty( Marshaller.JAXB_SCHEMA_LOCATION, OSMX_SCHEMA_LOCATION );
 460:        marshaller.marshal(modelRoot, w);
 461:        
 462:        // not modified anymore
 463:        setModified(false);
 464:    } // saveAsDocument(Writer)
 465:    
 466:    /** Writes the OSMX document to the specified {@link java.io.File}.
 467:     * @param f The <CODE>File</CODE> to save the document to.
 468:     * @throws IOException Thrown if the <CODE>Writer</CODE> encounters an error performing the output.
 469:     * @throws JAXBException Thrown if the JAXB marshalling process fails for some reason.
 470:     */
 471:	      public void saveAsDocument(File f) throws IOException, JAXBException {
 472:        setTitle(f.getName());
 473:        saveAsDocument(new FileWriter(f));
 474:        setOutputFilename(f.getAbsolutePath());
 475:        docURI = f.toURI();
 476:    } // saveAsDocument(File)
 477:    
 478:    /** Writes the OSMX document to the specified file path and name.
 479:     * @param filename Represents the path and name of the file to save the document to.
 480:     * @throws IOException Thrown if the <CODE>Writer</CODE> encounters an error performing the output.
 481:     * @throws JAXBException Thrown if the JAXB marshalling process fails for some reason.
 482:     */
 483:	      public void saveAsDocument(String filename) throws IOException, JAXBException {
 484:        saveAsDocument(new File(filename));
 485:    } // saveAsDocument(String)
 486:    
 487:    /** Returns an {@link edu.byu.deg.osmx.binding.ObjectFactory } instance that can be
 488:     * used to create OSMX elements; the factory also automatically adds the elements
 489:     * to the <CODE>OSMXDocument</CODE>.
 490:     * @return The document's <CODE>ObjectFactory</CODE> instance.
 491:     */
 492:	      public ObjectFactory getObjectFactory() {
 493:        return objectFactory;
 494:    } // method getObjectFactory
 495:    
 496:    /** Returns the title of the document.
 497:     * @return The title of the document; will be <CODE>"Untitled-<I>n</I>"</CODE> if no title has been specified
 498:     * (where <I>n</I> is the number of untitled documents that have been created).
 499:     */
 500:	      public String getTitle() {
 501:	          if (title == null) {
 502:            // create an "Untitled-n" type of title
 503:            newDocNumber++;
 504:            setTitle("Untitled-" + newDocNumber);
 505:        }
 506:        return title;
 507:    }
 508:    
 509:    /**
 510:     * Returns the location of this OSMX document as a URI.  This will be
 511:     * <CODE>null</CODE> if the document has been newly created but not yet saved, or
 512:     * was opened from a stream rather than a local or remote file source.
 513:     * @return The location of this document, or <CODE>null</CODE> if the location is not known
 514:     * or has not been specified.
 515:     */    
 516:	      public URI getURI() {
 517:        return docURI;
 518:    }
 519:    
 520:    /** Sets the title of the document.
 521:     * @param docTitle The new title of the document.
 522:     */
 523:	      public void setTitle(String docTitle) {
 524:        // default to current title if param is null
 525:        if (docTitle == null) docTitle = getTitle();
 526:        
 527:        // set the instance variable
 528:        title = docTitle;
 529:        
 530:        // set the ontology title
 531:        OSM root = getModelRoot();
 532:	          if (root != null) {
 533:            String ontologyName = root.getOntologyName();
 534:            if (!title.equals(ontologyName)) root.setOntologyName(title);
 535:        }
 536:    }
 537:    
 538:    /** Accessor for the root element of the document.
 539:     * @return The root-level element of the OSMX document.
 540:     */
 541:	      public OSM getModelRoot() {
 542:        return modelRoot;
 543:    } // method getModelRoot
 544:    
 545:    /** Establishes an {@link edu.byu.deg.osmx.binding.OSMType} as the root element of
 546:     * the document.  If the document already has a root element, this method does nothing.
 547:     * @param root The intended root element of the document.
 548:     */
 549:	      private void setModelRoot(OSM root) {
 550:        // do nothing if modelRoot has already been set
 551:        if (modelRoot != null) return;
 552:        
 553:        // set the instance variable
 554:        modelRoot = root;
 555:        
 556:        // hook up new model root to document
 557:	          if (root != null && root instanceof OSMXOSMType) {
 558:            ((OSMXOSMType) root).setParentDocument(this);
 559:        }
 560:    }
 561:    
 562:    /** Registers an object to receive notification of changes to the document.
 563:     * @param dcl The object to receive document change notifications.
 564:     */
 565:	      public void addDocumentChangeListener(DocumentChangeListener dcl) {
 566:        if (!docChangeListeners.contains(dcl)) docChangeListeners.add(dcl);
 567:    } // method addDocumentChangeListener
 568:    
 569:    /** Removes an object from the document change notification system.
 570:     * @param dcl The object that should no longer receive document change notifications.
 571:     */
 572:	      public void removeDocumentChangeListener(DocumentChangeListener dcl) {
 573:        docChangeListeners.remove(dcl);
 574:    } // method removeDocumentChangeListener
 575:    
 576:	      private void notifyDocumentChangeListeners(int eventType) {
 577:        // copy listener list to avoid concurrent modification problems
 578:        Iterator i = new LinkedList(docChangeListeners).iterator();
 579:        
 580:        // REVISIT: sharing an event object with all listeners... is this good practice?
 581:        DocumentChangeEvent evt = new DocumentChangeEvent(this, eventType);
 582:	          while (i.hasNext()) {
 583:            DocumentChangeListener dcl = (DocumentChangeListener) i.next();
 584:            dcl.stateChanged(evt);
 585:        }
 586:    } // method notifyDocumentChangeListeners
 587:    
 588:    /** Forces a {@link DocumentChangeEvent} to be sent to all registered
 589:     * {@link DocumentChangeListener}s in order to guarantee that all listeners have an
 590:     * updated view of the document.
 591:     */
 592:	      public void updateModel() {
 593:        notifyDocumentChangeListeners(DocumentChangeEvent.GENERAL_EVENT);
 594:    } // method updateModel
 595:    
 596:    /**
 597:     * Returns the first primary object set in the document if one is found
 598:     * @return The primary object set of the ontology, or <CODE>null</CODE> if no object sets
 599:     * are marked as primary.
 600:     */
 601:	      public OSMXObjectSetType getPrimaryObjectSet(){
 602:        // REVISIT: we could cache this result for better performance
 603:        List l = getObjectSets();
 604:	          for(Iterator i=l.iterator();i.hasNext();){
 605:            OSMXObjectSetType objset = (OSMXObjectSetType) i.next();
 606:            if (objset.isPrimary()) return objset;
 607:        }
 608:        return null;
 609:    }
 610:    
 611:    /**
 612:     * Convenience method for accessing all of the ontology's object sets in one list.
 613:     * @return A <CODE>List</CODE> of <CODE>ObjectSet</CODE> elements.
 614:     */    
 615:	      public List getObjectSets() {
 616:        return getElementsOfType(OSMXObjectSetType.class);
 617:    }
 618:    
 619:    /**
 620:     * Convenience method for accessing all of the ontology's
 621:     * generalization/specialization relationships in one list.
 622:     * @return A <CODE>List</CODE> of all <CODE>GenSpec</CODE> elements.
 623:     */    
 624:	      public List getGenSpecs() {
 625:        return getElementsOfType(OSMXGenSpecType.class);
 626:    }
 627:    
 628:    /**
 629:     * Convenience method for accessing all of the ontology's relationship sets in one list.
 630:     * @return A <CODE>List</CODE> of all <CODE>RelationshipSet</CODE> elements.
 631:     */    
 632:	      public List getRelSets() {
 633:        return getElementsOfType(OSMXRelationshipSetType.class);
 634:    }
 635:    
 636:    /**
 637:     * Convenience method for accessing all of the ontology's data instances in one list.
 638:     * @return A <CODE>List</CODE> of all <CODE>DataInstance</CODE> elements.
 639:     */    
 640:	      public List getDataInstances() {
 641:        return getElementsOfType(OSMXDataInstanceType.class);
 642:    }
 643:    
 644:	      private List getElementsOfType(Class type, List modelElements) {
 645:        // REVISIT: cache results in a Map indexed by class for better performance?
 646:        LinkedList result = new LinkedList();
 647:        if (modelElements == null) return Collections.unmodifiableList(result);
 648:	          for (Iterator i = modelElements.iterator(); i.hasNext();) {
 649:            Object o = i.next();
 650:	              if (type.isInstance(o)) {
 651:                result.add(o);
 652:            }
 653:            
 654:	              if (o instanceof ModelContainer) {
 655:                OSMType osm = ((ModelContainer) o).getOSM();
 656:	                  if (osm != null) {
 657:                    result.addAll(getElementsOfType(type, osm.getAllModelElements()));
 658:                }
 659:            }
 660:        }
 661:        return Collections.unmodifiableList(result);
 662:        
 663:    }
 664:    
 665:    /**
 666:     * Convenience method for accessing all elements of a particular type that belong
 667:     * to the ontology, in one list.
 668:     * @param type The <CODE>Class</CODE> that determines which elements of the ontology to return.
 669:     * @return All elements of the ontology that are an <CODE>instanceof</CODE> the given
 670:     * class.
 671:     */    
 672:	      public List getElementsOfType(Class type) {
 673:        if (!OSMXElement.class.isAssignableFrom(type)) return null;
 674:        List allElems = getModelRoot().getAllModelElements();
 675:        List result = getElementsOfType(type, allElems);
 676:        
 677:        return result;
 678:    }
 679:    
 680:    /**
 681:     * Removes all <CODE>DataInstance</CODE> elements from the ontology.
 682:     */    
 683:	      public void clearDataInstances(){
 684:      List l = getModelRoot().getAllModelElements();
 685:	        for(int i=0;i<l.size();i++){
 686:	          if(l.get(i) instanceof DataInstance){
 687:          l.remove(i);
 688:          i--;
 689:        }
 690:      }
 691:    }
 692:    
 693:	      public List getSourceDocumentURIs() {
 694:        SortedSet rootURIs = new TreeSet();
 695:        
 696:        // determine set of root documents
 697:        List objSets = getObjectSets();
 698:	          for (Iterator i = objSets.iterator(); i.hasNext();) {
 699:            DataFrameType datFr = ((ObjectSetType) i.next()).getDataFrame();
 700:            if (datFr == null || datFr.getValuePhrase() == null) continue;
 701:            List valPhrases = datFr.getValuePhrase();
 702:            if (valPhrases == null) continue;
 703:	              for (Iterator j = valPhrases.iterator(); j.hasNext();) {
 704:                DataFrameExpression dfe = ((ValuePhrase) j.next()).getValueExpression();
 705:                if (dfe == null) continue;
 706:	                  for (Iterator k = dfe.getMatchedText().iterator(); k.hasNext();) {
 707:                    rootURIs.add(((MatchedTextType) k.next()).getDocument());
 708:                }
 709:            }
 710:        }
 711:        
 712:        // remove URIs that have another doc URI as the prefix
 713:        // we can do this in one pass because the set is sorted
 714:        String rootURI = (String) rootURIs.first();
 715:	          for (Iterator i = rootURIs.iterator(); i.hasNext();) {
 716:            String docURI = (String) i.next();
 717:	              if (!rootURI.equals(docURI)) {
 718:	                  if (docURI.startsWith(rootURI)) {
 719:                    i.remove();
 720:                } else {
 721:                    rootURI = docURI;
 722:                }
 723:            }
 724:        }   
 725:        return new ArrayList(rootURIs);
 726:    }
 727:    
 728:    /* Inner classes */
 729:    
 730:    /** Exception indicating that an output destination has not been specified for the
 731:     * document when output is attempted.
 732:     */
 733:	      public class UnspecifiedOutputException extends Exception {
 734:        /** Standard exception constructor.
 735:         * @param msg Message to associate with the exception.
 736:         */
 737:	          public UnspecifiedOutputException(String msg) {
 738:            super(msg);
 739:        }
 740:    } // class UnspecifiedOutputException
 741:    
 742:	      private class OSMXObjectFactory extends ObjectFactory {
 743:        
 744:	          private void addToDocument(Object obj) {
 745:            OSMXDocument.this.addElement((OSMXElement) obj);
 746:        }
 747:        
 748:	          public Relationship createRelationship() throws JAXBException {
 749:            Relationship result = super.createRelationship();
 750:            addToDocument(result);
 751:            return result;
 752:        }
 753:        
 754:	          public DataFrameType createDataFrameType() throws JAXBException {
 755:            DataFrameType result = super.createDataFrameType();
 756:            addToDocument(result);
 757:            return result;
 758:        }
 759:        
 760:	          public SpecializationConnection createSpecializationConnection() throws JAXBException {
 761:            SpecializationConnection result = super.createSpecializationConnection();
 762:            addToDocument(result);
 763:            return result;
 764:        }
 765:        
 766:	          public Macro createMacro() throws JAXBException {
 767:            Macro result = super.createMacro();
 768:            addToDocument(result);
 769:            return result;
 770:        }
 771:        
 772:	          public GeneralConstraint createGeneralConstraint() throws JAXBException {
 773:            GeneralConstraint result = super.createGeneralConstraint();
 774:            addToDocument(result);
 775:            return result;
 776:        }
 777:        
 778:	          public RealTimeConstraint createRealTimeConstraint() throws JAXBException {
 779:            RealTimeConstraint result = super.createRealTimeConstraint();
 780:            addToDocument(result);
 781:            return result;
 782:        }
 783:        
 784:	          public PositionedText createPositionedText() throws JAXBException {
 785:            PositionedText result = super.createPositionedText();
 786:            addToDocument(result);
 787:            return result;
 788:        }
 789:        
 790:	          public Parameter createParameter() throws JAXBException {
 791:            Parameter result = super.createParameter();
 792:            addToDocument(result);
 793:            return result;
 794:        }
 795:        
 796:	          public KeywordPhraseType createKeywordPhraseType() throws JAXBException {
 797:            KeywordPhraseType result = super.createKeywordPhraseType();
 798:            addToDocument(result);
 799:            return result;
 800:        }
 801:        
 802:	          public Transition createTransition() throws JAXBException {
 803:            Transition result = super.createTransition();
 804:            addToDocument(result);
 805:            return result;
 806:        }
 807:        
 808:	          public StateType createStateType() throws JAXBException {
 809:            StateType result = super.createStateType();
 810:            addToDocument(result);
 811:            return result;
 812:        }
 813:        
 814:	          public ObjectType createObjectType() throws JAXBException {
 815:            ObjectType result = super.createObjectType();
 816:            addToDocument(result);
 817:            return result;
 818:        }
 819:        
 820:	          public GenSpecType createGenSpecType() throws JAXBException {
 821:            GenSpecType result = super.createGenSpecType();
 822:            addToDocument(result);
 823:            return result;
 824:        }
 825:        
 826:	          public ValuePhraseType createValuePhraseType() throws JAXBException {
 827:            ValuePhraseType result = super.createValuePhraseType();
 828:            addToDocument(result);
 829:            return result;
 830:        }
 831:        
 832:	          public MethodType createMethodType() throws JAXBException {
 833:            MethodType result = super.createMethodType();
 834:            addToDocument(result);
 835:            return result;
 836:        }
 837:        
 838:	          public StyleType createStyleType() throws JAXBException {
 839:            StyleType result = super.createStyleType();
 840:            addToDocument(result);
 841:            return result;
 842:        }
 843:        
 844:	          public RealTimeConstraintType createRealTimeConstraintType() throws JAXBException {
 845:            RealTimeConstraintType result = super.createRealTimeConstraintType();
 846:            addToDocument(result);
 847:            return result;
 848:        }
 849:        
 850:	          public RelationshipSetType createRelationshipSetType() throws JAXBException {
 851:            RelationshipSetType result = super.createRelationshipSetType();
 852:            addToDocument(result);
 853:            return result;
 854:        }
 855:        
 856:	          public GeneralizationConnection createGeneralizationConnection() throws JAXBException {
 857:            GeneralizationConnection result = super.createGeneralizationConnection();
 858:            addToDocument(result);
 859:            return result;
 860:        }
 861:        
 862:	          public TransitionType createTransitionType() throws JAXBException {
 863:            TransitionType result = super.createTransitionType();
 864:            addToDocument(result);
 865:            return result;
 866:        }
 867:        
 868:	          public ObjectSet createObjectSet() throws JAXBException {
 869:            ObjectSet result = super.createObjectSet();
 870:            addToDocument(result);
 871:            return result;
 872:        }
 873:        
 874:	          public Style createStyle() throws JAXBException {
 875:            Style result = super.createStyle();
 876:            addToDocument(result);
 877:            return result;
 878:        }
 879:        
 880:	          public ParameterType createParameterType() throws JAXBException {
 881:            ParameterType result = super.createParameterType();
 882:            addToDocument(result);
 883:            return result;
 884:        }
 885:        
 886:	          public KeywordPhrase createKeywordPhrase() throws JAXBException {
 887:            KeywordPhrase result = super.createKeywordPhrase();
 888:            addToDocument(result);
 889:            return result;
 890:        }
 891:        
 892:	          public CoOccurrenceConstraintType createCoOccurrenceConstraintType() throws JAXBException {
 893:            CoOccurrenceConstraintType result = super.createCoOccurrenceConstraintType();
 894:            addToDocument(result);
 895:            return result;
 896:        }
 897:        
 898:	          public AnchorType createAnchorType() throws JAXBException {
 899:            AnchorType result = super.createAnchorType();
 900:            addToDocument(result);
 901:            return result;
 902:        }
 903:        
 904:	          public DataType createDataType() throws JAXBException {
 905:            DataType result = super.createDataType();
 906:            addToDocument(result);
 907:            return result;
 908:        }
 909:        
 910:	          public ObjectBinding createObjectBinding() throws JAXBException {
 911:            ObjectBinding result = super.createObjectBinding();
 912:            addToDocument(result);
 913:            return result;
 914:        }
 915:        
 916:	          public ObjectBindingType createObjectBindingType() throws JAXBException {
 917:            ObjectBindingType result = super.createObjectBindingType();
 918:            addToDocument(result);
 919:            return result;
 920:        }
 921:        
 922:	          public ObjectSetReference createObjectSetReference() throws JAXBException {
 923:            ObjectSetReference result = super.createObjectSetReference();
 924:            addToDocument(result);
 925:            return result;
 926:        }
 927:        
 928:	          public ModelElement createModelElement() throws JAXBException {
 929:            ModelElement result = super.createModelElement();
 930:            addToDocument(result);
 931:            return result;
 932:        }
 933:        
 934:	          public LexiconType createLexiconType() throws JAXBException {
 935:            LexiconType result = super.createLexiconType();
 936:            addToDocument(result);
 937:            return result;
 938:        }
 939:        
 940:	          public RelSetConnectionType createRelSetConnectionType() throws JAXBException {
 941:            RelSetConnectionType result = super.createRelSetConnectionType();
 942:            addToDocument(result);
 943:            return result;
 944:        }
 945:        
 946:	          public Note createNote() throws JAXBException {
 947:            Note result = super.createNote();
 948:            addToDocument(result);
 949:            return result;
 950:        }
 951:        
 952:	          public RelationshipSet createRelationshipSet() throws JAXBException {
 953:            RelationshipSet result = super.createRelationshipSet();
 954:            addToDocument(result);
 955:            return result;
 956:        }
 957:        
 958:	          public TypeSpecification createTypeSpecification() throws JAXBException {
 959:            TypeSpecification result = super.createTypeSpecification();
 960:            addToDocument(result);
 961:            return result;
 962:        }
 963:        
 964:	          public ObjectSetReferenceType createObjectSetReferenceType() throws JAXBException {
 965:            ObjectSetReferenceType result = super.createObjectSetReferenceType();
 966:            addToDocument(result);
 967:            return result;
 968:        }
 969:        
 970:	          public ConjunctionConnectionType createConjunctionConnectionType() throws JAXBException {
 971:            ConjunctionConnectionType result = super.createConjunctionConnectionType();
 972:            addToDocument(result);
 973:            return result;
 974:        }
 975:        
 976:	          public Association createAssociation() throws JAXBException {
 977:            Association result = super.createAssociation();
 978:            addToDocument(result);
 979:            return result;
 980:        }
 981:        
 982:	          public OSM createOSM() throws JAXBException {
 983:            OSM result = super.createOSM();
 984:            addToDocument(result);
 985:            return result;
 986:        }
 987:        
 988:	          public Method createMethod() throws JAXBException {
 989:            Method result = super.createMethod();
 990:            addToDocument(result);
 991:            return result;
 992:        }
 993:        
 994:	          public ConjunctionType createConjunctionType() throws JAXBException {
 995:            ConjunctionType result = super.createConjunctionType();
 996:            addToDocument(result);
 997:            return result;
 998:        }
 999:        
1000:	          public ConjunctionConnection createConjunctionConnection() throws JAXBException {
1001:            ConjunctionConnection result = super.createConjunctionConnection();
1002:            addToDocument(result);
1003:            return result;
1004:        }
1005:        
1006:	          public SourceDocument createSourceDocument() throws JAXBException {
1007:            SourceDocument result = super.createSourceDocument();
1008:            addToDocument(result);
1009:            return result;
1010:        }
1011:        
1012:	          public Anchor createAnchor() throws JAXBException {
1013:            Anchor result = super.createAnchor();
1014:            addToDocument(result);
1015:            return result;
1016:        }
1017:        
1018:	          public DataFrameExpression createDataFrameExpression() throws JAXBException {
1019:            DataFrameExpression result = super.createDataFrameExpression();
1020:            addToDocument(result);
1021:            return result;
1022:        }
1023:        
1024:	          public Conjunction createConjunction() throws JAXBException {
1025:            Conjunction result = super.createConjunction();
1026:            addToDocument(result);
1027:            return result;
1028:        }
1029:        
1030:	          public DataInstance createDataInstance() throws JAXBException {
1031:            DataInstance result = super.createDataInstance();
1032:            addToDocument(result);
1033:            return result;
1034:        }
1035:        
1036:	          public ConditionType createConditionType() throws JAXBException {
1037:            ConditionType result = super.createConditionType();
1038:            addToDocument(result);
1039:            return result;
1040:        }
1041:        
1042:	          public MacroType createMacroType() throws JAXBException {
1043:            MacroType result = super.createMacroType();
1044:            addToDocument(result);
1045:            return result;
1046:        }
1047:        
1048:	          public AggregationType createAggregationType() throws JAXBException {
1049:            AggregationType result = super.createAggregationType();
1050:            addToDocument(result);
1051:            return result;
1052:        }
1053:        
1054:	          public CoOccurrenceConstraint createCoOccurrenceConstraint() throws JAXBException {
1055:            CoOccurrenceConstraint result = super.createCoOccurrenceConstraint();
1056:            addToDocument(result);
1057:            return result;
1058:        }
1059:        
1060:	          public DataTypeType createDataTypeType() throws JAXBException {
1061:            DataTypeType result = super.createDataTypeType();
1062:            addToDocument(result);
1063:            return result;
1064:        }
1065:        
1066:	          public SourceDocumentType createSourceDocumentType() throws JAXBException {
1067:            SourceDocumentType result = super.createSourceDocumentType();
1068:            addToDocument(result);
1069:            return result;
1070:        }
1071:        
1072:	          public AssociationType createAssociationType() throws JAXBException {
1073:            AssociationType result = super.createAssociationType();
1074:            addToDocument(result);
1075:            return result;
1076:        }
1077:        
1078:	          public Lexicon createLexicon() throws JAXBException {
1079:            Lexicon result = super.createLexicon();
1080:            addToDocument(result);
1081:            return result;
1082:        }
1083:        
1084:	          public State createState() throws JAXBException {
1085:            State result = super.createState();
1086:            addToDocument(result);
1087:            return result;
1088:        }
1089:        
1090:	          public RelSetConnection createRelSetConnection() throws JAXBException {
1091:            RelSetConnection result = super.createRelSetConnection();
1092:            addToDocument(result);
1093:            return result;
1094:        }
1095:        
1096:	          public ParticipationConstraintType createParticipationConstraint() throws JAXBException {
1097:            ParticipationConstraintType result = super.createParticipationConstraintType();
1098:            addToDocument(result);
1099:            return result;
1100:        }
1101:        
1102:	          public DataFrame createDataFrame() throws JAXBException {
1103:            DataFrame result = super.createDataFrame();
1104:            addToDocument(result);
1105:            return result;
1106:        }
1107:        
1108:	          public GenSpec createGenSpec() throws JAXBException {
1109:            GenSpec result = super.createGenSpec();
1110:            addToDocument(result);
1111:            return result;
1112:        }
1113:        
1114:	          public Aggregation createAggregation() throws JAXBException {
1115:            Aggregation result = super.createAggregation();
1116:            addToDocument(result);
1117:            return result;
1118:        }
1119:        
1120:	          public OSMType createOSMType() throws JAXBException {
1121:            OSMType result = super.createOSMType();
1122:            addToDocument(result);
1123:            return result;
1124:        }
1125:        
1126:	          public DataInstanceType createDataInstanceType() throws JAXBException {
1127:            DataInstanceType result = super.createDataInstanceType();
1128:            addToDocument(result);
1129:            return result;
1130:        }
1131:        
1132:	          public OSMObject createOSMObject() throws JAXBException {
1133:            OSMObject result = super.createOSMObject();
1134:            addToDocument(result);
1135:            return result;
1136:        }
1137:        
1138:	          public ObjectSetType createObjectSetType() throws JAXBException {
1139:            ObjectSetType result = super.createObjectSetType();
1140:            addToDocument(result);
1141:            return result;
1142:        }
1143:        
1144:	          public RelationshipType createRelationshipType() throws JAXBException {
1145:            RelationshipType result = super.createRelationshipType();
1146:            addToDocument(result);
1147:            return result;
1148:        }
1149:        
1150:	          public ValuePhrase createValuePhrase() throws JAXBException {
1151:            ValuePhrase result = super.createValuePhrase();
1152:            addToDocument(result);
1153:            return result;
1154:        }
1155:        
1156:    } // class OSMXObjectFactory
1157:    
1158:	      private class IDGenerator {
1159:        private int lastID = 0;
1160:	          public IDGenerator() {
1161:            /* default constructor */
1162:        }
1163:        
1164:	          public String generateID() {
1165:            while (modelElements.containsKey(constructID(++lastID)));
1166:            return constructID(lastID);
1167:        }
1168:        
1169:	          private String constructID(int idNum) {
1170:            return "osmx" + idNum;
1171:        }
1172:    } // class IDGenerator
1173:    
1174:}