XMI APPLICATION FRAMEWORK
by Tim Grose
March 8, 2001
(C) Copyright International Business Machines Corporation 1999- 2001. All rights reserved. Note to US Government Users--Documentation related to restricted rights--Use, duplication or disclosure is subject to restrictions set forth in GSA ADP Schedule Contract with IBM Corp.
Please send feedback about this document, or about the framework itself, to Tim Grose at tgrose@us.ibm.com, or append your comments or questions to the alphaWorks XMI Toolkit discussion forum.
CHANGES (March 8, 2001)
No changes to the framework APIs have been made since it was last posted to alphaWorks as part of the XMI Toolkit. However, bug fixes and performance enhancements have been done. It is anticipated that changes in the handling of cross-file references will be made next.
CHANGES ( April 14, 2000)
Many significant changes have been made to the framework since its initial release, including the following:
INTRODUCTION
The XMI Application Framework empowers you to work with XMI files and DTDs without becoming an XMI expert. As you learn more about XMI, you can use the framework to tailor the XMI files and DTDs it produces.
You can work with any data that can be expressed in terms of objects, properties, and links, including most of the data that can be expressed with MOF and UML. In the framework, objects are things that have properties and are related to other objects through links.
You can work with classes for your objects. Properties and links in a class define the legal properties and links of instances of the class. Classes can be added to packages, and packages can contain packages and classes. The framework converts UML metamodels in XMI format into framework classes and packages. The framework will connect objects in an XMI file to their classes, properties of objects to corresponding properties in classes, and links of objects to corresponding links in classes when an XMI file is loaded, if you wish.
The framework allows you to work with objects, properties, links, and classes directly, rather than working with XML tags and attributes. You do not need to know the DOM or SAX interfaces provided by XML parsers to use the framework, which means that you need to know very little about the format of XMI files to work with them.
The framework allows you to split data into several XMI files. The framework creates proxies for objects in XMI files that have not been loaded yet. If you query a proxy for data that must come from the object, the XMI file containing the object is automatically loaded, if you wish. You can specify directories and zip files that the framework will search for the files to be loaded.
Several classes in the framework let you work with collections of XMI objects and models, which are collections of classes and packages.
The framework contains interfaces and implementation classes that you can use to attach your classes to the framework. You may implement framework interfaces or subclass the framework implementation classes. You may also implement adapters so the framework works with your objects that don't implement framework interfaces. Also, the framework generates Java classes that inherit from the framework from UML models.
WHERE TO START
This document provides you with the information you need to know to use the XMI Application Framework, but you can also learn how to use the framework by using the javadoc documentation and the code examples.
This document organizes details about the framework into the following sections: XMI Data Hierarchy, XMI Classes and Packages, XMI Files, Advanced Capabilities, and Suggestions for Using the Framework. Each subject except for Advanced Capabilities and Suggestions is split into basic, intermediate, and advanced concepts. You can begin to use the framework as soon as you master the basic concepts. After you know them, you can learn the intermediate and advanced concepts if you need to.
You should read the "Overview" and "XMI Names" sections before using the framework. After reading those sections, read the "Basic Concepts" parts of the following sections to learn how to do the following tasks:
| Task | Sections To Read |
| Handle XMI files | XMI Data Hierarchy, XMI Files |
| Make XMI DTDs | XMI Classes and Packages, XMI DTDs |
This document does not explain every detail of every public method in the framework; to learn more about individual methods, consult the javadoc documentation.
If you learn best by working with sample code, you should investigate the examples provided with the framework. The examples are organized the same as this document. The examples included in this document are, in many cases, simpler versions of the examples provided with the framework.
OVERVIEW
The framework consists of an interface hierarchy--with corresponding implementation classes--that implement a simple object-oriented model. The hierarchy represents an object model that is a subset of MOF and UML. The other classes in the framework represent XMI files, XMI DTDs, XMI schemas, collections of objects, models, and adapters that let you connect your objects that don't implement framework interfaces to the framework to save it in XMI files without needing to implement framework interfaces.
The names of the interface classes are related to the names of the corresponding implementation classes. Each implementation class has the name of the corresponding interface with a suffix of "Impl". For example, the name of the implementation class corresponding to the XMIObject interface is XMIObjectImpl.
The root interface of the XMI data hierarchy is called Data. You make XMI data (objects, properties, links, etc.) by instantiating subclasses of the DataImpl class or by loading XMI files. By default, the framework instantiates implementation classes for the data interfaces when it loads an XMI file. You can, however, register a factory object with the framework that will make instances of your own classes that inherit from the framework or implement framework interfaces.
The XMIObject interface represents objects. Objects have properties and links, which represent relationships between objects. Please note that XMIObject does not let you represent the behavior of objects, so there is no Method or Operation interface in the data hierarchy. The purpose of the data hierarchy is to represent the state of an object so the framework can save the state of the object in an XMI file and restore it when loading an XMI file (it also represents the structural features of classes so XMI DTDs can be generated).
The data hierarchy also contains interfaces to represent classes and packages. The XMIClass interface extends the XMIObject interface in the hierarchy, so the methods you use to work with properties and links for objects are identical to the ones you use when working with classes. Classes have subclasses and superclasses, in addition to properties and links. Classes inherit properties and links from their superclasses. A class may have multiple superclasses. A package is a container that holds classes and other packages.
You make classes and packages by instantiating subclasses of the DataImpl class or by loading an XMI file containing a UML model and converting the XMI objects into classes. You use the DeclarationFactory class to convert the XMI objects from the XMI file containing UML to framework classes and packages.
To enable semantic checking of objects, the framework's object model specifies a definer for objects, properties, and links. The definer of an object is its class; the definer of a property belonging to an object is the corresponding property in the class; and the definer of a link belonging to an object is the corresponding link in the class. The framework will connect objects and their properties and links to their definers when loading from an XMI file, if you wish.
To enable the framework to compute names, its object model specifies owners for packages, classes, objects, properties, and links. The owner of a property or link is the object or class it belongs to; the owner of a class is the package it is contained in; and the owner of a package is the package that contains it. The default implementation classes provided with the framework automatically set the owners of constructs when they are added to other constructs. For example, when a class is added to a package, the owner of the class is set to the package.
The XMIFile class represents an XMI file. You make an instance of this class and pass it XMI objects to write an XMI file. When writing an XMI file, you can choose from different options to tailor the XMI file to your liking. You can also specify optional data that is put in the XMI header section of XMI files. If you do not know much about XMI, use the defaults and learn about the other options if necessary. You can work with both XMI 1.0 and XMI 1.1 files.
When loading an XMI file, the framework creates an instance of the XMIFile class. You can then get the XMI objects that were in the XMI file from the XMIFile instance. You can load any XMI file, but if the framework does not have the classes for the objects in the file, it might misinterpret them when loading the file. You can prevent misinterpretation by using the Workspace class described later.
If your objects are stored in several files, use the XMIFiles class to load the files. This class correctly resolves cross-file links and makes proxies for objects in files that have not yet been loaded. If you load a file that contains an object, the framework will convert a proxy for that object to the actual object, if a proxy exists. Also, if you query a proxy for data that it does not have, the framework will load the file containing the object if you turn on the demand load setting of the XMIFiles class. You specify which directories and zip files the framework will search when loading files by setting the file path of the XMIFiles class.
The XMIDTD class represents an XMI DTD. You make an instance of this class and pass it packages and classes to create an XMI DTD. You can make XMI 1.0 DTDs and XMI 1.1 DTDs.
The XMIContainer class allows you to perform queries on a collection of XMI objects; the Model class allows you to perform queries on a collection of classes and packages. Since supporting the queries efficiently requires the data to be put into data structures, you should use the XMIContainer and Model classes sparingly if you are concerned about minimizing the amount of memory you use.
The Workspace class allows you to load XMI files and associate the XMI objects in them with classes that you put in models. Using this class enables you to perform semantic checking of the objects in XMI files; eventually, the framework may perform this checking for you.
XMI NAMES
Each package, class, object, property, and link has an XMI name; if a construct has a definer, its XMI name is the XMI name of its definer.
In XMI 1.0, the XMI name was the fully qualified name, consisting in most cases of the names of the owners of the construct (and their owners) and the name of the construct, with each name separated by ".". The fully qualified name of a class consists of the fully qualified name of the package containing the class followed by "." and the name of the class. For a package, the fully qualified name is the fully qualified name of the package containing it followed by "." and the name of the package. The XMI name of an object is the XMI name of its class.
For properties and links belonging to classes, the XMI name for XMI 1.0 is the fully qualified name of the class they belong to, followed by "." and the name of the property or link. The fully qualified name of a local property or link of an object is the fully qualified name of the object followed by "." and the name of the property or link. However, the fully qualified name of an inherited property or link is the fully qualified name of the class the property or link's definer belongs to followed by "." and the name of the property or link. For example, consider a class B that inherits from class A. Class A has a property whose name is "a". What is the fully qualified name of property "a" belonging to an instance of class B? The fully qualified name of the property is not "B.a" but rather "A.a" since the definer of "a" belongs to class A, not class B.
In XMI 1.1, it is no longer necessary to use fully qualified names of constructs as their XMI names. The XMI name of a package or class is the minimum name that ensures uniqueness. If there are no name conflicts between packages and classes, their XMI names for XMI 1.1 are simply their names. If there are name conflicts, it is recommended that the names be parts of fully qualified names that are unique. The XMI name in XMI 1.1 of a property or link is its name, or the XMI name of the class of its definer followed by "." and its name, depending on the type of property or link.
XMI 1.1 allows you to use XML namespaces as parts of names. In the framework, you can attach a namespace to any construct in the data hierarchy, and the framework will include the namespace name if the data is saved in XMI 1.1 files or used to make XMI 1.1 DTDs. The namespace name may be "" but cannot be null.
For example, to represent a dog you might create an XMI object and give it an XMI name of "Dog". If you create another dog, its XMI name is "Dog" also. Each XMI object is an instance of the "Dog" class. If the Dog class was in a package called Mammals, which was in another package called Animals, the XMI name of each object for XMI 1.0 would be "Animals.Mammals.Dog". For XMI 1.1, the XMI name would just be "Dog" if there were no other packages or classes named "Dog". To assign a unique name to each dog, you add a basic property to each dog and assign the value of the property to the name of the dog, as explained in the "XMI Data Hierarchy" section.
Now consider a model containing a package P that contains two other packages, P1 and P2. Both P1 and P2 contain classes named C. In XMI 1.0, the XMI names of the two classes would be "P.P1.C" and "P.P2.C". In XMI 1.1, the name "C" cannot be used as the XMI name, since that is not unique. You should use "P1.C" and "P2.C" as the XMI names of the classes.
If objects, properties, and links are connected to their definers, they can be saved in either XMI 1.0 or XMI 1.1 files. If the definers are not set, and the XMI 1.1 names for objects differ from their XMI 1.0 names, it is not possible to store an object in both XMI 1.0 and XMI 1.1 formats without changing its XMI name.
To support both XMI 1.0 and XMI 1.1 whenever possible, set the XMI names of constructs as follows:
The framework automatically assigns the correct XMI names to classes and packages if it makes them from a UML model. Then you can make objects and set the definers of the objects and their properties and links to the ones the framework created to ensure that the XMI names are correct. The framework also assigns correct XMI names to objects, properties, and links when you use the classes generated by the UML2Java program.
The framework sets the definers for objects, their properties, and their links when loading XMI files if you use the Workspace class.
Since the XMI name is the XML tag name, the XMI name must be a valid XML tag name. See the XML 1.0 specification for more details. In general, letters and numbers are allowed in names, but spaces and punctuation marks--except for '-' and '.'--are not allowed.
XMI DATA HIERARCHY
The "Basic Concepts" part explains the creation and use of objects, their properties, and their links, while "Intermediate Concepts" covers the use of sets of tag values and "Advanced Concepts" illustrates one way to attach your classes to the framework.
Basic Concepts
You use XMI objects to represent things in your problem domain. You use properties to hold values for the properties of objects. You use links to relate objects to other objects. In UML terminology, XMI objects are instances of classes, properties are instances of attributes (UML calls them attribute links), and links are instances of association ends (UML calls them link ends). Links correspond to OMG MOF references.
The interfaces for objects, properties, and links extend the Data interface, as shown in Figure 1.
Figure 1. Object, Property, and Link Interfaces
You make objects by loading them from XMI files, by making instances of the XMIObjectImpl class, or by using the FactoryAdapter class. Loading XMI files is described in the "XMI Files" section. The XMIObjectImpl class implements the XMIObject interface and provides additional methods for accessing properties and links. The FactoryAdapter class makes instances of the XMIObjectImpl class--as well as the other implementation classes provided by the framework--but you can subclass the FactoryAdapter class to make instances of your classes, as explained in the "Advanced Concepts" section.
You use the XMIObject interface methods to: add and delete properties and links, set the XMI identity, get properties and links, work with sets of tag values, and work with XMI extensions. Properties and links are described in more detail below. The XMI identity of an object consists of the XMI id, uuid, and label. You can get all properties for an object and all links for an object using the XMIObject interface itself. If you use the XMIObjectImpl class, there are more methods that you can use to query properties and links in different ways. See the "Intermediate Concepts" section for a description of sets of tag values and XMI extensions.
There are three kinds of properties; their interfaces are shown in Figure 2. A basic property has a primitive data value, such as a string, integer, real number, date, etc. that is treated by the framework as a string. An enumeration property is a basic property whose
legal values are specified in an enumeration whose literals represent the legal values. An object property has a value that is an object.
Figure 2. Property Interfaces
The property interfaces have getXMIValue and setXMIValue methods; the methods in BasicProperty and EnumProperty let you get and set strings, while the ones in ObjectProperty let you get and set an XMIObject.
There are three kinds of links; their interfaces are shown in Figure 3. A reference link, represented by the RefLink interface, is a simple relationship between two objects. A containment link, represented by the ContainLink interface, indicates that one of the objects in the link contains the other object; if the container object is deleted, the contained object is too. A container link, represented by the ContainerLink interface, is a reference link from a contained object to its container.
Figure 3. Link Interfaces
Each link has a source and a target. The source is the object the link belongs to, and the target is the object linked to the source object. You specify the target of a link by using the getXMIObject and setXMIObject methods; you add a link to the object that is the source of the link.
Here are some examples of using objects, properties, and links. How do you create two dogs, one named Sparky, the other, Lassie? Here's the code:
XMIObject dog1 = new XMIObjectImpl("Dog");
XMIObject dog2 = new XMIObjectImpl("Dog");
BasicProperty p1 = new BasicPropertyImpl("name", "Sparky");
BasicProperty p2 = new BasicPropertyImpl("name", "Lassie");
dog1.add(p1);
dog2.add(p2);
In the previous example, dog1 is named "Sparky" and dog2 is named "Lassie". Note that you do not need to have an XMIClass to make objects. You might use the following code to obtain the name of both dogs, assuming dog1 and dog2 are set as above:
XMIObjectImpl dog1Impl = (XMIObjectImpl) dog1;
XMIObjectImpl dog2Impl = (XMIObjectImpl) dog2;
System.out.println("dog1 name: " + dog1Impl.getXMIValue("name"));
BasicProperty p = (BasicProperty) dog2Impl.getXMIProperty("name");
if (p != null)
System.out.println("dog2 name: " + p.getXMIValue());
The previous code uses methods in XMIObjectImpl that are not in the XMIObject interface. Using the XMIObject interface, the properties for each dog can be obtained, then processed as in this example for getting the name property of dog1:
Iterator props = dog1.getXMIProperties().iterator();
while (props.hasNext()) {
Property p = (Property) props.next();
if (p.getXMIName().equals("name"))
System.out.println("dog1 name: " +
((BasicProperty) p).getXMIValue());
}
Consider how to represent cars, their owners, and the car's engine in the framework. This code creates a car that contains an engine and is owned by a person:
XMIObject car = new XMIObjectImpl("Car");
XMIObject engine = new XMIObjectImpl("Engine");
XMIObject person = new XMIObjectImpl("Person");
ContainLink engineLink = new ContainLinkImpl("engine", engine);
ContainerLink carLink = new ContainerLinkImpl("car", car);
RefLink owner = new RefLinkImpl("owner", person);
car.add(engineLink);
car.add(owner);
engine.add(carLink);
This example demonstrates the use of all three types of links. Since the engine is physically contained inside the car, it is related to the car by containment link engineLink. The engine is related to the car by the container link carLink. Finally, the car is related to its owner by the owner reference link.
To obtain the objects linked to the car object created above, you can use the getLinkedObject method of XMIObjectImpl as follows:
try {
XMIObject carOwner = ((XMIObjectImpl) car).getLinkedObject("owner");
XMIObject carEngine = ((XMIObjectImpl) car).getLinkedObject("engine");
}
catch (XMIException e) {}
You can also use the getXMILinks method in the XMIObject interface to obtain the links for the car, then process each one in a manner similar to processing properties using the XMIObject interface.
The following example shows how to represent a person owning two cars, and how to obtain both cars from the person:
XMIObject person = new XMIObjectImpl("Person");
XMIObject car1 = new XMIObjectImpl("Car");
XMIObject car2 = new XMIObjectImpl("Car");
RefLink r1 = new RefLinkImpl("car", car1);
RefLink r2 = new RefLinkImpl("car", car2);
person.add(r1);
person.add(r2);
Collection cars = ((XMIObjectImpl) person).getLinkedObjects("car");
In the above example, the cars collection contains car1 and car2.
Intermediate Concepts
When implementing programs that exchange data, it is common for each program to require information that is not needed by the other programs. A classic example is user interface information. It is unlikely that programs will be able to use other programs' user interface data unless an effort is made to standardize that data. If no such effort is made, each program may need to store user interface data along with the data to exchange with other programs without interfering with the exchange of the data used by all programs.
The framework provides sets of tag values for objects that can be used to attach program-specific data to data exchanged with other programs. If you specify sets of tag values in the framework, the framework will save them as XMI extensions in the XMI files you create. The framework handles XMI extensions in XMI files as described in the "XMI Files" section.
A tag is a named property for an object that has a string value; the name of the tag is independent of the names of properties of the object. You can collect related tag values in named sets; the name of each set must be unique within an object, and the names of tags in a set must be unique.
The following example illustrates the setting of sets of tag values for cars; the data in the sets of tag values is not meant to be shared with other programs, but the car objects and properties are meant to be shared:
XMIObject car1 = new XMIObjectImpl("Car");
XMIObject car2 = new XMIObjectImpl("Car");
BasicProperty p1 = new BasicPropertyImpl("model", "Corolla");
BasicProperty p2 = new BasicPropertyImpl("model", "Civic");
car1.add(p1);
car2.add(p2);
car1.setXMITagValue("dealer1", "position", "space 11");
car2.setXMITagValue("dealer1", "position", "space 22");
car1.setXMITagValue("dealer1", "acquired", "October 1, 1998");
In the above example, each car and its model are meant to be shared with other dealers, but the other dealers do not care where the car is parked on dealer1's lot (indicated by the position tag) or the date dealer1 acquired the car. The tag values will be saved in XMI files, but other dealers may ignore the tag values that were set by dealer1. Other dealers may also attach their own unique data to the cars; if they use their own sets of tag values, their tag values will not interfere with dealer1's tag values.
Advanced Concepts
You may attach your classes to the framework by inheriting from the implementation classes or by implementing the interfaces. See the "Intermediate Concepts" part of the "XMI Files" section for details about what you need to do to enable the framework to make instances of your classes when loading XMI files. See the "Code Generation" section for details about how the framework will make subclasses of the XMIObjectImpl class for you.
All of the interfaces and implementation classes for the data hierarchy are public, so you may extend and implement any of them you wish. Here is an example that demonstrates subclasses of XMIObjectImpl to represent cars, engines, and persons, where each car has a property whose value is the model of the car, and each car also has a link to the engine. Each engine has a link to the car it is in, and each person has one or more links to cars the person owns:
public class Car extends XMIObjectImpl {
public Car() {
super("Car");
}
public void setEngine(Engine e) {
if (getLinkedObjects("engine").iterator().hasNext()) {
Iterator links = getLinks("engine").iterator();
((Link) links.next()).setXMIObject(e);
}
else
add(new ContainLinkImpl("engine", e));
}
public Engine getEngine() {
try {
return (Engine) getLinkedObject("engine");
}
catch (XMIException e) {}
return null;
}
public String getModel() {
return getXMIValue("model");
}
public void setModel(String model) {
if (getXMIProperty("model") != null)
((BasicProperty) getXMIProperty("model")).setXMIValue(model);
else
add(new BasicPropertyImpl("model", model));
}
}
public class Engine extends XMIObjectImpl {
public Engine() {
super("Engine");
}
public Car getCar() {
try {
return (Car) getLinkedObject("car");
}
catch (XMIException e) {}
return null;
}
public void setCar(Car c) {
Iterator links = getLinks("car").iterator();
if (links.hasNext())
((Link) links.next()).setXMIObject(c);
else
add(new ContainerLinkImpl("car", c));
}
}
public class Person extends XMIObjectImpl {
public Person() {
super("Person");
}
public void addCar(Car c) {
add(new RefLinkImpl("owns", c));
}
public Collection getCars() {
return getLinkedObjects("owns");
}
}
This example demonstrates that you can write your own subclasses of the XMIObjectImpl class with your own methods that hide framework methods from users of your classes.
XMI CLASSES AND PACKAGES
The "Basic Concepts" part explains the creation and use of classes and their properties and links, while "Intermediate Concepts" explains how to represent class inheritance with XMI classes and "Advanced Concepts" explains how the framework makes XMI classes and packages from UML models.
Basic Concepts
The data hierarchy contains interfaces for XMI classes and packages, as shown in Figure 4.
Figure 4. Classes and Properties
XMI classes have an XMI name, properties, and links. The properties and links of a class define the properties and links of objects that are instances of the class. Since the XMIClass interface extends the XMIObject interface, you can set the target of a link to a class, and you can set the value of an object property to a class. This should only be done for links and properties that belong to classes.
If you set the target of a link belonging to a class to another class, links in instances of the class that is the source of the link should have targets that are instances of the other class or subclasses of the other class. The framework currently does not prevent you from setting the target of a link illegally. For example, if class A has a link l whose target is class B, objects that are instances of A should have l links whose targets are instances of B or subclasses of B.
If you set the value of an object property in a class to another class, you should only set values for the object property in instances of the class to instances of the other class or subclasses of the other class. The framework currently does not prevent you from setting an illegal value for an object property. For example, if class A has an object property p whose value is class B, objects that are instances of A should have p properties whose values are instances of B or subclasses of B.
An enumeration property belonging to a class can be attached to an enumeration represented by the Enum interface (not shown in Figure 4) in the data hierarchy. Enumerations have literals that define the legal values for the property.
Links belonging to a class have an XMI multiplicity that indicates how many of the links can appear in instances of the class. The framework currently does not prevent an illegal number of links from being added to objects.
The following example demonstrates how to create a class for dogs if each dog has a property for the name of the dog:
XMIClass dog = new XMIClassImpl("Dog");
BasicProperty name = new BasicPropertyImpl("name");
dog.add(name);
The following example illustrates how to create links and add them to classes. The classes are car, person, and engine; a car contains an engine, an engine has a link to the car it is in, and a car has a link to the person(s) that own it:
XMIClass car = new XMIClassImpl("Car");
XMIClass engine = new XMIClassImpl("Engine");
XMIClass person = new XMIClassImpl("Person");
ContainLink engineLink = new ContainLinkImpl("engine");
engineLink.setXMIMultiplicity("1");
engineLink.setXMIObject(engine);
car.add(engineLink);
ContainerLink carLink = new ContainerLinkImpl("car", car);
carLink.setXMIMultiplicity("1");
engine.add(carLink);
RefLink owner = new RefLinkImpl("owner", person);
owner.setXMIMultiplicity("+");
car.add(owner);
Intermediate Concepts
Framework classes may have subclasses and superclasses. You add superclasses and subclasses to framework classes using the addSuperclass and addSubclass methods in the XMIClass interface.
If you are working with animals, mammals, dogs, and cats, and mammals inherit from animals, and dogs and cats inherit from mammals, you can use the following code to create XMI classes representing the situation:
XMIClass animal = new XMIClassImpl("Animal");
XMIClass mammal = new XMIClassImpl("Mammal");
XMIClass dog = new XMIClassImpl("Dog");
XMIClass cat = new XMIClassImpl("Cat");
animal.addSubclass(mammal);
mammal.addSuperclass(animal);
mammal.addSubclass(dog);
mammal.addSubclass(cat);
dog.addSuperclass(mammal);
cat.addSuperclass(mammal);
Advanced Concepts
The framework converts UML models saved in XMI files into classes and packages if you use the DeclarationFactory class. The framework converts UML packages into framework packages, UML classes into framework classes, UML attributes into properties of framework classes, and UML association ends into links of framework classes.
If the type of a UML attribute is an enumeration, the framework makes an enumeration property for the attribute and an enumeration for the type. If the attribute type is a UML class, the framework makes an object property. If the attribute type is not an enumeration or a UML class, the framework makes a basic property.
The framework makes containment links, container links, and reference links from UML association ends. For UML associations that represent containment, the framework makes containment links and container links from the associations' association ends. For other associations, the framework makes reference links from the association ends.
The framework assigns subclasses and superclasses of framework classes to reflect inheritance between classes in the UML model.
The framework assigns XMI names to the packages, classes, properties, and links based on the names in the UML XMI file.
Currently, the DeclarationFactory class supports UML 1.1 models saved in XMI 1.0 format.
To create framework classes and packages from a UML XMI file, load the XMI file and get the top objects, as explained in the "XMI Files" section. Then make an instance of the DeclarationFactory class and invoke one of the makeDeclarations methods, passing the objects from the XMI file. See the example called "UML2Classes" for more details.
XMI FILES
The "Basic Concepts" part covers loading and creating single XMI files. The "Intermediate Concepts" part covers setting and getting XMI header information for XMI files, as well as other ways to tailor the XMI files the framework creates. The "Advanced Concepts" part covers loading XMI files that contain cross-file links, and loading files so that the framework sets definers for the data from the files.
Basic Concepts
Creating Single XMI Files
To create an XMI file, make an instance of the XMIFile class, then use the write method to write your XMI objects to the file. Before calling the write method, call the setXMIVersion method with "1.1" as the parameter if you want to create an XMI 1.1 file. Here is how to make an XMI file called "empty.xmi" in the current directory without any of your data in it (the file will not be empty because XMI has some header information that surrounds your data):
XMIFile f = new XMIFile("empty.xmi");
try {
f.write(null, XMIFile.DEFAULT);
}
catch (Exception e) {
e.printStackTrace();
}
The write method takes two parameters: an iterator for a collection of XMI objects, and an option which can be used to change the XMI file. The only currently available option is the DEFAULT option. The write method throws an Exception, which can either be an XMIException or a Java I/O exception.
Here is the content of the file "empty.xmi" produced by the previous code example:
<?xml version="1.0" encoding="UTF-8"?>
<XMI xmi.version="1.0" timestamp="Thu Sep 23 10:25:04 PDT 1999">
<XMI.header>
<XMI.documentation>
<XMI.exporter>XMI Application Framework</XMI.exporter>
<XMI.exporterVersion>1.05</XMI.exporterVersion>
</XMI.documentation>
</XMI.header>
<XMI.content/>
</XMI>
Note that the time the file was produced is automatically saved, along with the exporter and exporter version, which indicate that the XMI Application Framework version 1.05 produced the file. The file is a valid XML file.
Here is an example of how to write your own objects to an XMI file:
try {
XMIObject airplane = new XMIObjectImpl("Airplane");
XMIObject engine = new XMIObjectImpl("Engine")
XMIObject airline = new XMIObjectImpl("Airline");
ContainLink engineLink = new ContainLinkImpl("engine",
engine);
airplane.add(engineLink);
Vector v = new Vector();
v.addElement(airplane);
v.addElement(airline);
XMIFile f = new XMIFile("airplane.xmi");
f.setXMIVersion("1.1");
f.write(v.iterator(), XMIFile.DEFAULT);
}
catch (Exception e) {
e.printStackTrace();
}
The contents of file airplane.xmi are:
<?xml version="1.0" encoding="UTF-8"?>
<XMI xmi.version="1.1" timestamp="Sun Oct 10 14:06:54 PDT 1999">
<XMI.header>
<XMI.documentation>
<XMI.exporter>XMI Application Framework</XMI.exporter>
<XMI.exporterVersion>1.05</XMI.exporterVersion>
</XMI.documentation>
</XMI.header>
<XMI.content>
<Airplane xmi.id="_1">
<Airplane.engine>
<Engine xmi.id="_1.1"/>
</Airplane.engine>
</Airplane>
<Airline xmi.id="_2"/>
</XMI.content>
</XMI>
The above example illustrates that you need to include only the top level objects in the collection for the iterator you give to the write method; the framework saves all contained objects. Also, the framework assigns XMI ids to each object that is saved that does not have one. The framework uses ids you assign to objects when writing the objects in XMI files.
Loading Single XMI Files
You use the static load method of the XMI file class to load a single XMI file; the framework creates an instance of the XMIFile class for you that you can use to get the top level objects from the file. Here is an example that demonstrates how to load the XMI file "airplane.xmi" from the "Creating Single XMI Files" section above and write the XMI name of each object from the file:
try {
XMIFile f = XMIFile.load("airplane.xmi", XMIFile.DEFAULT, false);
Iterator objs = f.getObjects().iterator();
while (objs.hasNext()) {
XMIObject o = (XMIObject) objs.next();
System.out.println(o.getXMIName());
Iterator props = o.getXMIProperties().iterator();
while (props.hasNext()) {
Property p = (Property) props.next();
if (p instanceof ObjectProperty) {
ObjectProperty op = (ObjectProperty) op;
System.out.println(op.getXMIValue().getXMIName());
}
}
}
}
catch (Exception e) {
e.printStackTrace();
}
This example produces the following output:
Airplane Engine Airline
The output indicates that all of the objects were loaded into the framework.
You can get the header information, the XMI version, and the timestamp from the XMIFile object as well, as the intermediate section illustrates.
Loading Assumptions
If you carefully look at the previous example from the last section, you may notice that the engine for the airplane is obtained by getting the value of an object property, but the airplane had a containment link whose target was the engine. Why did this happen?
You need to know the model for the objects in an XMI file to correctly interpret them. The model includes the classes the objects are instances of. By looking at the classes you can determine whether something in the file is a property or link, and what kind of property or link it is.
You can use the Workspace class to enable the framework to match objects, properties, and links in an XMI file with the classes that define them (please see the "Attaching Definers To Data When Loading XMI Files" section for the details). If you load without using a Workspace, the framework assumes that the objects in the file consist of basic properties and object properties; the framework doesn't create links or enumeration properties.
This means that if you create objects that have links, save them in an XMI file, and then load the file without using a Workspace, the links will be restored as either object properties or basic properties. The framework will create object properties from containment links regardless of the XMI version used. For XMI 1.1 files, the framework makes basic properties from other kinds of links; for XMI 1.0, the framework makes object properties from other kinds of links. If the framework creates an object property from a link, the name of the object property is the name of the link, and the value of the object property is the target of the link. If the framework creates a basic property from one or more links, the name of the basic property is the name of the link or links, and the value of the basic property is a string consisting of XMI Ids separated by spaces; the ids are the ids for the objects that are targets of the link or links.
From the airplane example in the previous section, the engine for the airplane was created by making a containment link, setting the target of the link to the engine, and adding the containment link to the airplane. However, when loading, the link was restored as an object property whose value is the engine.
Intermediate Concepts
Header Data
XMI defines header data that can be put in each XMI file. To put header data into an XMI file, use the accessor methods in the table below to set the header data before calling the write method. To get header data from an XMI file, use the accessor methods to get the header data after loading the XMI file.
| Data | Accessor Methods |
| XMI version | getXMIVersion, setXMIVersion |
| Timestamp | getTimestamp, setTimestamp |
| Exporter | getExporter, setExporter |
| Exporter version | getExporterVersion, setExporterVersion |
| Verified | isVerified, setVerified |
| Notice | getNotice, setNotice |
| Owner | getOwner, setOwner |
| Long description | getLongDescription, setLongDescription |
| Short description | getShortDescription, setShortDescription |
Writing Options
You can modify the XMI files the framework produces by setting header data, encoding, indenting, and DTD options for an XMIFile object before calling the write method.
XML allows you to specify encoding options and DTDs for XML files. You can set the encoding by using the setEncoding method; the default value is "UTF-8" unless you specify a different value. A DTD specified in an XML file enables XML parsers to perform XML validation for the file when it is parsed. By default, the framework does not identify a DTD for the XMI files it produces. You can set the DTD by using the setDTD method of the XMIFile object.
The framework indents two spaces when writing XML tags contained in other tags. You may set the indentation by using the setIndent method; you can avoid indenting by passing 0 to the setIndent method.
The following example creates a file that indicates that "my.dtd" should be used to validate the XML file, and that "My Awesome Tool" version "2.0" produced the file:
try {
XMIFile f = new XMIFile("options.xmi");
f.setExporter("My Awesome Tool");
f.setExporterVersion("2.0");
f.setDTD("my.dtd");
f.setIndent(0);
f.write(null, XMIFile.DEFAULT);
}
catch (Exception e) {
e.printStackTrace();
}
The options.xmi file contains a reference to the DTD, the values for the exporter and exporter versions, and does not indent contained XML tags:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE XMI SYSTEM "my.dtd" [
<!ELEMENT ixafs (ixaftv)+ >
<!ATTLIST ixafs
n CDATA #REQUIRED
>
<!ELEMENT ixaftv EMPTY >
<!ATTLIST ixaftv
t CDATA #REQUIRED
v CDATA #REQUIRED
>]>
<XMI xmi.version="1.0" timestamp="Sun Oct 10 15:35:01 PDT 1999">
<XMI.header>
<XMI.documentation>
<XMI.exporter>My Awesome Tool</XMI.exporter>
<XMI.exporterVersion>2.0</XMI.exporterVersion>
</XMI.documentation>
</XMI.header>
<XMI.content/>
</XMI>
Getting Header Data from XMI Files
You can obtain data from the headers of XMI files by invoking the appropriate get methods on the XMIFile objects the framework makes when loading them.
The following example obtains some of the data in the table from the options.xmi file:
try {
XMIFile f = XMIFile.load("options.xmi", XMIFile.DEFAULT, false);
String xmiVersion = f.getXMIVersion();
String timestamp = f.getTimestamp();
String exporter = f.getExporter();
String exporterVersion = f.getExporterVersion();
}
catch (Exception e) {
e.printStackTrace();
}
Advanced Concepts
Creating Related XMI Files
You can split your data into multiple XMI files by creating multiple XMIFile objects and by using a particular tag so the framework creates cross-file references. The framework uses the xmiFile tag in a set named "", as well as the UUID of the object, to create a cross-file reference.
Consider the following code that creates one XMI file with a model that contains a package; the package has one class in it:
try {
XMIObject m = new XMIObjectImpl("Model");
m.setXMILabel("m");
XMIObject p = new XMIObjectImpl("Package");
p.setXMILabel("p");
XMIObject c = new XMIObjectImpl("Class");
c.setXMILabel("c");
m.add(new ContainLinkImpl("owned", p));
p.add(new ContainLinkImpl("owned", c));
p.add(new ContainerLinkImpl("owner", m));
c.add(new ContainerLinkImpl("owner", p));
XMIFile f = new XMIFile("model.xmi");
Vector v = new Vector(1);
v.addElement(m);
f.write(v.iterator(), XMIFile.DEFAULT);
}
catch (Exception e) {
e.printStackTrace();
}
To make the example simple, the XMI label of each object is used for the object's name; normally, you would use a basic property to hold the name. Also, this example does not match the official UML 1.1 XMI 1.0 DTD.
The contents of model.xmi are:
<?xml version="1.0" encoding="UTF-8"?>
<XMI xmi.version="1.0" timestamp="Sun Oct 10 16:23:39 PDT 1999">
<XMI.header>
<XMI.documentation>
<XMI.exporter>XMI Application Framework</XMI.exporter>
<XMI.exporterVersion>1.05</XMI.exporterVersion>
</XMI.documentation>
</XMI.header>
<XMI.content>
<Model xmi.id="_1" xmi.label="m">
<Model.owned>
<Package xmi.id="_1.1" xmi.label="p">
<Package.owned>
<Class xmi.id="_1.1.1" xmi.label="c">
<Class.owner>
<Package xmi.idref="_1.1"/>
</Class.owner>
</Class>
</Package.owned>
<Package.owner>
<Model xmi.idref="_1"/>
</Package.owner>
</Package>
</Model.owned>
</Model>
</XMI.content>
</XMI>
Note that the framework uses the xmi.idref XML attribute for references to objects within a file.
If you decide to split your data into two files, m.xmi and p.xmi, where m.xmi contains the model and p.xmi contains the package and its class, you must set the UUID for each object in a cross-file reference, and you must set the xmiFile tag for the top level objects in each file, if there will be cross-file references to the contents of the file. In the case of the model, package, and class, you need to set the UUID and xmiFile tag for the model and the package, since m.xmi will contain a cross-file reference to the package p in p.xmi, and p.xmi will contain a cross-file reference to model m in m.xmi; since there will be no cross-file references to the class, you do not need to set its UUID; since the class is to be saved in the same file as package p, you do not need to set its xmiFile tag.
The following program produces the two files m.xmi and p.xmi:
try {
XMIObject m = new XMIObjectImpl("Model");
m.setXMILabel("m");
m.setXMIUUID("m001");
m.setXMITagValue("", "xmiFile", "m.xmi");
XMIObject p = new XMIObjectImpl("Package");
p.setXMILabel("p");
p.setXMIUUID("p001");
p.setXMITagValue("", "xmiFile", "p.xmi");
XMIObject c = new XMIObjectImpl("Class");
c.setXMILabel("c");
m.add(new ContainLinkImpl("owned", p));
p.add(new ContainLinkImpl("owned", c));
p.add(new ContainerLinkImpl("owner", m));
c.add(new ContainerLinkImpl("owner", p));
XMIFile modelFile = new XMIFile("m.xmi");
Vector v = new Vector(1);
v.addElement(m);
modelFile.write(v.iterator(), XMIFile.DEFAULT);
XMIFile packageFile = new XMIFile("p.xmi");
v = new Vector(1);
v.addElement(p);
packageFile.write(v.iterator(), XMIFile.DEFAULT);
}
catch (Exception e) {
e.printStackTrace();
}
The contents of m.xmi are:
<?xml version="1.0" encoding="UTF-8"?>
<XMI xmi.version="1.0" timestamp="Sun Oct 10 16:49:41 PDT 1999">
<XMI.header>
<XMI.documentation>
<XMI.exporter>XMI Application Framework</XMI.exporter>
<XMI.exporterVersion>1.05</XMI.exporterVersion>
</XMI.documentation>
</XMI.header>
<XMI.content>
<Model xmi.id="_1" xmi.label="m" xmi.uuid="m001">
<XMI.extension xmi.extender="IXAF TVS" xmi.extenderID="">
<ixafs n="">
<ixaftv t="xmiFile" v="m.xmi"/>
</ixafs>
</XMI.extension>
<Model.owned>
<Package xmi.uuid="p001" href="p.xmi"/>
</Model.owned>
</Model>
</XMI.content>
</XMI>
The contents of p.xmi are:
<?xml version="1.0" encoding="UTF-8"?>
<XMI xmi.version="1.0" timestamp="Sun Oct 10 16:49:43 PDT 1999">
<XMI.header>
<XMI.documentation>
<XMI.exporter>XMI Application Framework</XMI.exporter>
<XMI.exporterVersion>1.05</XMI.exporterVersion>
</XMI.documentation>
</XMI.header>
<XMI.content>
<Package xmi.id="_1" xmi.label="p" xmi.uuid="p001">
<XMI.extension xmi.extender="IXAF TVS" xmi.extenderID="">
<ixafs n="">
<ixaftv t="xmiFile" v="p.xmi"/>
</ixafs>
</XMI.extension>
<Package.owned>
<Class xmi.id="_1.1" xmi.label="c">
<Class.owner>
<Package xmi.idref="_1"/>
</Class.owner>
</Class>
</Package.owned>
<Package.owner>
<Model xmi.uuid="m001" href="m.xmi"/>
</Package.owner>
</Package>
</XMI.content>
</XMI>
You do not need to manually track which objects will participate in cross-file references when making related XMI files, if you do the following:
Loading Related XMI Files
You load related XMI files by using the XMIFiles class. That class also lets you specify whether the framework loads files that contain objects if you query a proxy for an object for data that must come from the object itself.
If you use the load method of the XMIFiles class, the XMIFile object is stored in the XMIFiles class, and the objects in the file are put in an XMIContainer for the XMIFiles class. Here is how to load both files created by the previous example using the XMIFiles class:
try {
XMIFiles files = new XMIFiles();
XMIFile mFile = files.load("m.xmi", XMIFiles.DEFAULT, false);
XMIFile pFile = files.load("p.xmi", XMIFiles.DEFAULT, false);
Collection xmiFiles = files.getFiles(); // Contains mFile and pFile
XMIContainer c = files.getContainer();
System.out.println(c); // Prints m, p, and c
}
catch (Exception e) {
e.printStackTrace();
}
If you load m.xmi, you will get objects for the model and the package; the package is a proxy, however, since its data is in file p.xmi. If you load m.xmi, then query the proxy representing the package, the framework automatically loads p.xmi and returns the data you requested. When it does so, the data for the package is added to the proxy, converting the proxy for the package to the actual package. The following program demonstrates this situation:
try {
XMIFiles files = new XMIFiles();
XMIFile mFile = files.load("m.xmi", XMIFiles.DEFAULT, false);
Collection xmiFiles = files.getFiles(); // Contains mFile
XMIContainer container = files.getContainer();
System.out.println(container); // Prints m and the proxy for p
XMIObject m = (XMIObject) mFile.getObjects().iterator().next();
XMIObject p;
p = ((XMIObjectImpl) m).getObjectPropertyValue("Model.owned");
// The following query triggers demand load, since the proxy for
// p does not have the contained objects for p.
XMIObject c;
c = ((XMIObjectImpl) p).getObjectPropertyValue("Package.owned");
// xmiFiles now contains mFile and the XMIFile for p.xmi
xmiFiles = files.getFiles();
container = files.getContainer();
// Prints m, p, and c; p is no longer a proxy.
System.out.println(container);
}
catch (Exception e) {
e.printStackTrace();
}
If you set the demand load option of an XMIFiles instance to false, the framework will not load files for you; your queries will not return complete data if you attempt to get data from proxies that can only be obtained from the actual objects.
Attaching Definers To Data When Loading XMI Files
The framework attaches definers (classes and their properties and links) to your data if you load XMI files using the Workspace class. You must put the definers in Model instances and add the Model instances to the workspace class. You must also load your files using the XMIFiles class, and add the XMIFiles instances to the Workspace. Then the framework uses the definers to interpret the contents of the XMIFiles you load and attaches the data in the files to the definitions. The following program demonstrates the use of the Workspace class. The content of the file dog.xmi used in this example is:
<?xml version="1.0" encoding="UTF-8"?>
<XMI xmi.version="1.0" timestamp="Sun Oct 10 18:02:29 PDT 1999">
<XMI.header>
<XMI.documentation>
<XMI.exporter>XMI Application Framework</XMI.exporter>
<XMI.exporterVersion>1.0</XMI.exporterVersion>
</XMI.documentation>
</XMI.header>
<XMI.content>
<Dog xmi.id="_1">
<Dog.name>Sparky</Dog.name>
<Dog.friend>
<Cat xmi.idref="_2"/>
</Dog.friend>
</Dog>
<Cat xmi.id="_2"/>
</XMI.content>
</XMI>
The program is:
try {
XMIClass dog = new XMIClassImpl("Dog");
XMIClass cat = new XMIClassImpl("Cat");
BasicProperty name = new BasicPropertyImpl("name");
dog.add(name);
RefLink friend = new RefLinkImpl("friend", cat);
dog.add(friend);
Vector classes = new Vector();
classes.addElement(dog);
classes.addElement(cat);
XMIFiles files = new XMIFiles();
Model m = new Model("Dogs and Cats", classes.iterator());
Workspace w = new Workspace();
w.add(m);
w.add(files);
XMIFile f = files.load("dog.xmi", XMIFiles.DEFAULT, false);
XMIContainer c = files.getContainer();
System.out.println(c); // Prints the dog and cat
}
catch (Exception e) {
e.printStackTrace();
}
You can compare the output of the previous program with the results from loading without a Workspace to gain a better understanding of the assumptions the framework makes when loading an XMI file without a Workspace.
ADVANCED CAPABILITIES
XMI DTDs
To create XMI DTDs, you create classes and packages and pass them to the write method of an XMIDTD instance. If you wish to create an XMI 1.1 DTD, call the setXMIVersion method with a parameter of "1.1" before using the write method. Here is an example of the use of the XMIDTD class:
try {
XMIClass dog = new XMIClassImpl("Dog");
XMIClass cat = new XMIClassImpl("Cat");
BasicProperty name = new BasicPropertyImpl("name");
dog.add(name);
RefLink friend = new RefLinkImpl("friend", cat);
dog.add(friend);
Vector classes = new Vector();
classes.addElement(dog);
classes.addElement(cat);
XMIDTD dtd = new XMIDTD("dog.dtd");
dtd.write(classes.iterator(), XMIDTD.DEFAULT);
}
catch (Exception e) {
e.printStackTrace();
}
XMI Schemas
XMI schemas may eventually replace XMI DTDs. They are currently being defined and standardized by the W3C. The framework contains an XMISchema class, which produces XMI schemas using syntax similar to an old Working Draft from 1999. The use of the XMISchema class is similar to the use of the XMIDTD class, as illustrated by the code sample below:
try {
XMIClass dog = new XMIClassImpl("Dog");
XMIClass cat = new XMIClassImpl("Cat");
BasicProperty name = new BasicPropertyImpl("name");
dog.add(name);
RefLink friend = new RefLinkImpl("friend", cat);
dog.add(friend);
Vector classes = new Vector();
classes.addElement(dog);
classes.addElement(cat);
XMISchema schema = new XMISchema("dog.xsd");
schema.setURI("http://com.MyCompany/dog.xsd");
schema.setVersion("1.0");
schema.write(classes.iterator(), XMISchema.DEFAULT);
}
catch (Exception e) {
e.printStackTrace();
}
Code Generation
The UML2Java class has been significantly improved. It correctly handles packages in UML models and provides more options for generating code. It also handles multiple inheritance in UML models if the "-interfaces" option is used. UML2Java will then generate both a class and an interface for each class in the UML model; the interfaces will extend each other to reflect inheritance in the UML model, but the generated implementation classes do not inherit from each other.
The UML2Java class produces subclasses of XMIObjectImpl from UML models. This capability will help you create subclasses of XMIObjectImpl automatically, so users of your code can work with your classes rather than the framework classes. The UML2Java class also creates a factory that can be registered with the framework, so the generated subclasses are instantiated by the framework when loading XMI files.
To use the UML2Java class, invoke it with the following options from the command line:
Using the Framework By Implementing Adapters
One of the most significant new features of this version of the framework is the addition of the WriterAdapter and ReaderAdapter interfaces. These interfaces allow you to save and load your objects in XMI files and save your classes in XMI DTDs even if your objects and classes do not implement framework interfaces or inherit from framework classes.
For example, you can use Java reflection to implement the WriterAdapter and ReaderAdapter interfaces so you can save any Java object to an XMI file and restore it. Please see the adapters examples to see how this can be done.
There is a default WriterAdapter implementation and a default ReaderAdapter implementation that work with the classes in the Data hierarchy, so you do not need to implement your own adapters to work with the Data hierarchy; you should implement adapters if you want to work with objects and classes that are not related to the Data hierarchy of the framework.
Please look at the example adapter implementations provided with the framework and the javadocs for the framework for more details about implementing adapters.
Suggestions for Using the Framework
There are several ways to use the framework. This section presents some possible ways of using the framework and the pros and cons of each way.
If you want to work with XMI files that contain data conforming to a specific model, create a UML model for your data, then use the UML2Java class in the framework to generate classes that you can use to work with your data. (You can also generate a DTD from your model, if you wish.) This option enables you to work with both XMI 1.0 and XMI 1.1 files, if you load your files using a workspace. A major additional advantage of using this approach is that your users do not need to keep track of XMI names; the generated classes hide details about XMI names and the Data hierarchy from your users. This approach is best for people that do not want to learn a lot about XMI.
If you know about XMI, you can use the implementation classes provided for the Data hierarchy to make XMI files regardless of your problem domain. You can also use the Data hierarchy to represent objects from XMI files even if you do not know the model they conform to. You can use the XMIClass and Package implementations to create XMI DTDs.
You should use a workspace for loading XMI files if you want to preserve links and enumeration properties; otherwise, when you load an XMI file, you will only get object properties and basic properties. You need to create a model for your objects and add the model to the workspace before loading XMI files for this approach to work.
You can implement adapters if you want to connect your objects or classes to the framework without having those objects or classes inherit from the framework or implement framework interfaces.