Here are the UML elements you can use to define the metamodel :
The UML element name must be valid Java identifier. For example it can't contain whitespaces.
Packages will help to organize your classes. Packages are not important for the needs of the Metamodel Generator, since a class name must be unique in the metamodel (see the Class section). They will only be useful when exporting the model using the XMI 1.0 standard, since package names are included in XMI 1.0 tags. Note they are no longer used in more recent XMI versions (1.1 to 2.1).
Note: the Metamodel Generator flattens UML packages into a single Ecore package, and gives this package the name of the metamodel.
A UML Class is used to denote a concept (metatype) in your metamodel. We'll start by assuming we have a trivial, one-class model like this:
The model shows a single class called Book with two attributes: title of type String and pages of type int.
A class name must be unique within the entire model. Two classes cannot share the same name, even in they are part of two different packages (because the Metamodel Generator flattens UML packages into a single package).
According to your UML case tool, abstract classes can be represented using the <<abstract>> stereotype or setting to true the isAbstract attribute of the UML class. In a well-formed model, an abstract class should have subclasses.
An attribute name must be unique within its class hierarchy (a super class must not define the same attribute name). An attribute name must be unique regarding its class roles (an attribute can't have the same name as a role of its class, or one of its super class). An attribute should specify its type (otherwise it will be considered as a String attribute). The type may be a primitive type or a class in the metamodel. If the type is a UML Class, this attribute becomes a composite relation.
The type of attribute can be a primitive type. Here is the list of the supported primitive types, with the corresponding Java type :
Metamodel Data Type | Java Type |
string | java.lang.String |
boolean, bool | boolean |
char | char |
integer, int | int | short | short |
long | long |
float, real | float |
double | double |
All the metatypes defined in a metamodel (for example, Book, Writer) implicitly derive from the EMF base class EObject. However, all the classes that a model uses are not necessarily EObjects. For example, assume we want to add an attribute of type java.util.Date to our metamodel. Before we can do so, we need to define an EMF DataType to represent the external type.
As shown, a data type is simply a named element in the model that acts as a proxy for some Java class. The actual Java class is provided as an attribute with the <<javaclass>> stereotype, whose name is the fully qualified class being represented. With this data type defined, we can now declare attributes of type java.util.Date like this:
Note: Depending on your UML case tool, you can use a class with the <<datatype>> stereotype or the native UML DataType for this purpose.
EMF provides predefined standard datatypes that can be used in your UML models:
Ecore Data Type | Java Primitive Type or Class |
---|---|
EBoolean | boolean |
EByte | byte |
EChar | char |
EDouble | double |
EFloat | float |
EInt | int |
ELong | long |
EShort | short |
EBooleanObject | java.lang.Boolean |
EByteObject | java.lang.Byte |
ECharacterObject | java.lang.Character |
EDoubleObject | java.lang.Double |
EFloatObject | java.lang.Float |
EIntegerObject | java.lang.Integer |
ELongObject | java.lang.Long |
EShortObject | java.lang.Short |
EString | java.lang.String |
EJavaObject | java.lang.Object |
EJavaClass | java.lang.Class |
So far, we've looked at how to handle simple attributes. Another commonly used type of attribute is an enumeration. Enumeration-type attributes are implemented using the Java typesafe enum pattern.
Note: According to your UML case tool, you can use a class with the <<enumeration>> stereotype or native UML Enumeration for this purpose.
All navigable roles in the model must be named in the metamodel. A role name must be unique within its class hierarchy (a super class must not define the same role name). A role name must be unique regarding its connected class attributes (a role can't have the same name as an attribute of its connected class, or one of its super class) A role should define a multiplicity (otherwise it will be considered as multiplicity "*").
Let's expand our example model with another class Writer that has an association with class Book:
The association between a book and its writer is, in this example, a single one-way reference. The reference (role) name used to access the Writer from a Book is author.
A reference between objects can be bi-directional as well.
The association is now two-way, as indicated by the lack of an arrowhead on the Writer end of the association line. The role name used to access Books from a Writer is books.
You may have noticed in our example that the books association (from Writer to Book) is of multiplicity many (that is, 0..*). In other words, one writer may have written many books.
Let's add a new class, Library, which will act as the container for Books.
In full, the association indicates that a Library is composed of 0 or more Books.
A role has an aggregation property, which can be "none", "aggregate" or composite". A role "aggregate" is considered in the same way as a role "none". The "composite" (also named containment or by-value aggregation) reference is indicated by the black diamond on the Library end of the association. Containment associations are particularly important because they identify the parent or owner of a target instance, which implies the physical location of the object when persisted. Indeed, a "composite" role has a different behavior when writing an XMI file. Any element contained by a "composite" role will be exported in an XMI file as a child element of the container, while an element attached by an "aggregate" or "none" role will be exported as a reference to another XML element (using an XMI identifier).
Let's say we want to create a subclass, SchoolBook, of our Book class.
In this case we used a Generalization from SchoolBook to Book to represent the inheritance relationship.
Consider the following example:
Here we've made SchoolBook derive from two classes: Book and Asset. A class can have as many superclasses as necessary.