Mapping_UML_Associations_into_Java_Code
Mapping_UML_Associations_into_Java_Code
net/publication/220298986
CITATIONS READS
63 2,863
3 authors, including:
All content following this page was uploaded by Juan Llorens on 19 May 2014.
Abstract
Object-oriented programming languages do not contain syntax or semantics to express
associations directly. Therefore, UML associations have to be implemented by an
adequate combination of classes, attributes and methods. This paper presents some
principles for the implementation of UML binary associations in Java, paying special
attention to multiplicity, navigability and visibility. Our analysis has encountered some
paradoxes in the specification of visibility for bidirectional associations. These principles
have been used to write a series of code patterns that we use in combination with a tool
that generates code for associations, which are read from a model stored in XMI format.
1 INTRODUCTION
One of the key building blocks in the Unified Modeling Language [UML] is the concept
of association. An "association" in UML is defined as a kind of relationship between
classes1, which represents the semantic relationship between two or more classes that
involves connections (links) among their instances [UML, p. 2-20]2.
As it has been denounced long ago [Rumbaugh 87], object-oriented programming
languages express classification and generalization well, but do not contain syntax or
semantics to express associations directly. Therefore, associations have to be
implemented by an adequate combination of classes, attributes and methods [Rumbaugh
96a, Noble 96, Noble 97, Ambler 01]. The simplest idea is to provide an attribute to store
the links of the association, and accessor and mutator methods to manipulate the links.
Other approaches emphasize the use of Java interfaces to implement associations with
some practical advantages [Harrison 00].
1
Actually classifiers. Classifier is a superclass of Class in the UML metamodel.
2
The current submission of communityUML to the OMG for the development of UML 2.0 [cUML] proposes a change
in terminology: "association" instead of "link" and "association type" instead of "association". We support this change,
but in this paper we are going to follow the current official terminology in UML.
Cite this article as follows: Gonzalo Génova, Carlos Ruiz del Castillo and Juan Llorens: “Mapping
UML Associations into Java Code”, in Journal of Object Technology, vol. 2, no. 5, September-
October 2003, pp. 135-162. http://www.jot.fm/issues/issue_2003_09/article4
MAPPING UML ASSOCIATIONS INTO JAVA CODE
CASE tools often provide some kind of code generation starting from design
models3, but limited to skeletal code involving only generalizations and classes, with
attribute and method signatures, but no associations at all4. The programmer has to
manually write the code to manage the associations in a controlled way, so that all
constraints and invariants are kept for correctness of the implementation. This is usually a
repetitive task that could be automated to a certain extent. Besides, the number of things
that the programmer should bear in mind when writing the code for the associations is so
large, that he or she continuously risks forgetting some vital detail. This is specially true
when dealing with multiple (with multiplicity higher than 1) or bidirectional (two-way
navigable) associations. Moreover, the final written code is frequently scattered over the
code of the participating classes, making it more difficult to maintain.
The aim of this work is two fold. First, write a series of code patterns that will help
programmers in mapping UML associations into a target object oriented programming
language. In this work, the language has been chosen to be Java, although the principles
we have followed may be applied to other close languages like C++ or the .NET
framework. Second aim, construct a tool that generates code for associations using these
patterns, the associations being read from a model stored in XMI format5. A third aim
will be to enable reverse engineering, that is, obtaining the associations between classes
by analyzing the code that implements them. Our tool does not presently accomplish this
task, although it is a very simple and straightforward procedure if the code has been
written with our patterns. A complete description of the patterns and the tool is outside
the scope of this paper, but can be found elsewhere [Ruiz 02].
Associations in UML can have a great variety of features. The present work is
limited to the analysis and implementation of multiplicity, navigability and visibility in
binary associations. It excludes, therefore, more complex kinds of associations such as
reflexive associations, whole/part associations (aggregations and compositions), qualified
associations, association-classes, and n-ary associations. It excludes, too, features such as
ordering, changeability, etc.
The three following sections of this article are devoted to studying the features of
multiplicity, navigability and visibility of associations, with a detailed analysis of the
possible problems and proposed solutions. Then, Section 5 contains the description of a
uniform interface for all kinds of associations from the point of view of the participating
classes, such as it is implemented by our patterns and tool. Finally, Section 6 describes
briefly how our tool works.
3
We distinguish here between analysis and design models. An analysis model is an abstraction of the problem (the real
world as it is before the proposed system is built) whereas a design model is an abstraction of the solution (the
proposed system's internal construction) [Kaindl 99], therefore code generation has sense only for a design model.
4
Some tools are an exception to this rule [Fujaba, Rhapsody].
5
XML Metadata Interchange [XMI], an XML-based format designed to store and interchange UML models between
different tools.
The multiplicity of a binary association, placed on an association end (the target end),
specifies the number of target instances that may be associated with a single source
instance across the given association, in other words, how many objects of one class (the
target class) may be associated with a given single object from the other class (the source
class) [RM, p. 348; UML, p. 2-23]6.
The classical example in Figure 1 illustrates binary multiplicity. Each instance of
Person may work for none or one instance of Company (0..1), while each company may
be linked to one or more persons (1..*). For those readers less familiarized with UML
notation, the symbol (*) stands for "many" (unbounded number), and the ranges (1..1)
and (0..*) may be abbreviated respectively as (1) and (*).
1..* 0..1
Person works for Company
The potential multiplicities in UML extend to any subset of nonnegative integers [RM, p.
346], not only a single interval as (2..*), or a comma-separated list of integer intervals as
(1..3, 7..10, 15, 19..*): specifications of multiplicity like {prime numbers} or {squares of
positive integers} are also valid, although there is no standard notation for them.
Nevertheless, in UML as in other modeling techniques, the most usual multiplicities are
(0..1), (1..1), (0..*) and (1..*). We are going to restrict our analysis to multiplicities that
can be expressed as a single integer interval in the form of (min..max) notation.
The multiplicity constraint is a kind of invariant, that is, a condition that must be
satisfied by the system. A possible practice when programming is: do not check always
the invariant, but only at the request of the programmer, after completing a set of
operations that are supposed to leave system in a valid state (a transaction). This practice
is more efficient in run-time, and gives the programmer more freedom and responsibility
in writing the code, with the corresponding risk that he or she forgets putting the
necessary checks and carelessly leaves the system in a wrong state. On the other side, we
think that checking multiplicity constraints is not very time consuming (inefficient),
especially when compared with the time required to manage collections or synchronize
bidirectional associations (see Section 3). Therefore, we think that it is worth doing as
much as we can for the programmer, so that our first target will be to analyze the
possibility of performing automatic checks for multiplicity constraints.
6
Other notations invert the placement of multiplicity values, following the near-end convention instead of the far-end
convention, which is the one used in UML. It has been well established that the semantics of both conventions are
equivalent for binary associations, but differ substantially when they are applied to associations of higher degree [Song
95, McAllister 98, Castellani 00, Génova 02, Génova 03b].
Nevertheless, we find new problems here. If the association with Company were
mandatory for Person too (that is, 1..1 multiplicity instead of the current 0..1), the
instance of Person could not delete the old link with a Company and then add the new
one, nor it could do it in the reverse order, "first add then delete", because it would go
through a wrong system state. An atomic change of links would be valid only for the
simplest cases, but not for more complex ones such as the following, rather twisted case
(see Figure 2): consider classes A and B, which are associated with multiplicity 1..1 on
both ends, and the corresponding instances a1, a2, b1 and b2. In the initial state, we
have the links a1-b1 and a2-b2. In the final state, we want to have the links a1-b2 and
a2-b1. Even if we can change atomically a1-b1 to a1-b2 without violating the
multiplicity constraints on a1, this would leave b1 without any links and b2 with two
links until the final state is reached. We should have to perform the whole change
atomically by means of an atomic switch implemented in a single operation.
1..1 1..1
A B
(a)
a1 : A b1 : B a1 : A b1 : B a1 : A b1 : B
a2 : A b2 : B a2 : A b2 : B a2 : A b2 : B
(b) (c) (d)
Figure 2. Multiplicity constraints can make very difficult changing links between instances without entering a wrong
system state: a) class diagram; b) initial state; c) intermediate wrong state; d) final desired state
Obviously, we cannot define a new operation to avoid any conceivable wrong state
involving several instances. In consequence, we think that mandatory associations pose
unsolvable problems regarding the creation and deletion of instances and links: we cannot
achieve with a few primitive operations that a mandatory association is obeyed at any
time, and we cannot isolate, inside atomic operations, the times when the constraint is not
obeyed. Therefore, we have to relax the implications of mandatory associations for the
implementation, as other methods do [Harrison 00]. Our proposal is as follows: do not
check the minimum multiplicity constraint when modifying the links of the association
(mutator methods, or setters), but only when accessing them (accessor methods, or
getters). The programmer will be responsible for using the primitives in a consistent way
so that a valid system state is reached as soon as possible.
For example, you will be allowed to create a Company without linking it to any
Person, and you will be allowed to delete all the links of a Company with instances of
Person; but before accessing, for other purposes, the links of that particular instance of
Company towards any instances of Person, you will have to restore them to a valid state,
otherwise you will get an invalid multiplicity exception, which shall be defined in the
code that implements the associations according to our proposal.
0..* 2..4
Game Player
(a)
q1 : Player
p1 : Player q2 : Player
g1 : Game g1 : Game g1 : Game
p2 : Player q3 : Player
q4 : Player
Figure 3. Precedence problems found when invoking the adder and the remover in succession: a) class diagram of
Game-Player association; b) initial state with players p1, p2; c) final desired state after removing players p1, p2
and then adding players q1, q2, q3, q4; d) final wrong state after unsuccessfully trying to add players q1, q2, q3, q4
and then removing players p1, p2
In the end, we have preferred to reject the addition if it violates the maximum allowed,
and ask the users of mutator methods to use them always in the right order, first remove
then add, so that we can get an analogous behavior for single and multiple associations.
Therefore, the remover does not check the minimum multiplicity constraint (possibly
leaving empty a mandatory association), the adder does check the maximum multiplicity
constraint, and the getter raises an exception if either constraint is not fulfilled.
Accessor methods of multiple associations have another peculiarity, when compared
with the accessors of single associations: they return a collection of objects, not a single
object, therefore the returned type is that of the collection, not that of the target class. In
our implementation, the returned type is the Java interface Collection, which is
implemented by all standard collections. Internally, we use a HashSet collection, which
ensures that there are no duplicate links in an association, as the UML requires [UML, p.
2-19]7.
Finally, the standard collections in Java are specified to contain instances of the
standard class Object, which is a superclass of every class in Java. You cannot
specialize these collections to store objects pertaining only to a particular class8. This
means that, if we use a HashSet inside Company to store the links to instances of
Person, we must ensure on our own that no one puts a link to an instance of another
class such as Dog or Report (this could happen if a collection of objects is passed as a
parameter to the add method). Therefore, the mutator methods must perform a run-time
7
In other places we have given conclusive arguments against the no-duplicates restriction in UML associations
[Génova 03b], but here we have respected the current specification of UML.
8
That is, you cannot specialize them to modify their storage structure, but you can modify their behavior so that they
store in effect only the required objects, precisely by means of the run-time type checking method we describe.
type checking by means of explicit casting. If the type-check fails, then the link is not set
to that object, and a class cast exception, which is predefined in Java, is raised.
0..* 1..1
Key open Door
(a)
0..1 0..1
Man Woman
husband wife
(b)
9
An alternate definition: the possibility for a source object to designate a target object through an association instance
(link), in order to manipulate or access it in an interaction with message interchanges. The Standard does not give a
clear definition of navigability, as we have shown in previous works where we have tried to clarify this topic [Génova
01, Génova 03a, Génova 03b]. In this paper, we take navigability and directionality as synonyms.
Unidirectional associations
A single unidirectional association is very similar to a single valued attribute in the
source class, of the type of the target class: an embedded reference, pointer, or whatever
you want to call it. The equivalence, however, is not complete. Whereas the attribute
value is "owned" by the class instance and has no identity, an external referenced object
has identity and can be shared by instances of other classes that have a reference to the
same object [Rumbaugh 96b] (see Figure 5). Anyhow, the equivalence is satisfactory
enough to serve as a basis for the implementation of this kind of associations. In fact, in
Java there is no difference at all: except for the case of primitive values, attributes in Java
are objects with identity, and if they are public you cannot avoid them to be referenced
and shared by other objects.
Person
birthdate: Date
(a)
0..1 1..1
Person Date Book
birthdate publication
(b)
10
UML allows multiplicity in attributes, thus multivalued attributes [UML, p. 2-50].
1..*
Recipe Aliment
ingredient
(a)
Recipe Aliment
1..1 ingredient
1..*
HashSet Object
element
(b)
Figure 6. Multiple unidirectional association: a) analysis diagram and b) design diagram. A new object must be
inserted to manage the collection of target objects. The standard collections in Java, such as HashSet, are defined for
the standard class Object, which is a superclass of every class; therefore, mutator methods must ensure that the
objects contained in the collection parameter are of the appropriate type before adding them to the collection attribute.
Therefore, the type of the attribute used to implement the association inside the source
class is not any more the target class itself, but the HashSet class or another convenient
collection class. The methods to manage the association will have to accomplish some
additional tasks. Mutators can add or remove not only single objects of the class target,
but also entire collections; thus, the type of the parameter will be either the target class of
the association or the intermediate collection class. In this case, mutator methods must
ensure that the objects contained in the collection parameter are of the appropriate type
before adding them to the collection attribute. Accessors, as we have already explained
(see Section 2), do not return a single object, but a collection of objects, even when the
collection is made up of only one element. The returned collection object is not
identically the same one that is stored inside the source class, but a clone (a new object
with a collection of references to the same target elements), because the original
collection object must remain completely encapsulated inside the source object
(represented by the composition in Figure 6).
As the diagrams in Figures 5 and 6 show, in our opinion the multiplicity constraint in
a design model can be specified only for a navigable association end11. Indeed, the
multiplicity is a constraint that must be evaluated within the context of the class that owns
the association end; if that class knows the constraint, then it knows the association end,
that is, the end is navigable. You cannot restrict the number of objects connected to a
given instance unless this instance has some knowledge of the connected objects, that is,
unless you make the association end navigable. Therefore, the need for a multiplicity
constraint other than 0..* (that is, unrestricted) is an indication that the association end
11
This principle does not apply to analysis models, which usually do not deal with navigability [Fowler 97, Stevens
00]. Obviously, code generation only has sense when starting from design models.
Bidirectional associations
The partial equivalence between attributes and unidirectional associations is not any more
found among bidirectional associations. Instead, an instance of a bidirectional association
is more like a tuple of elements [UML, p. 2-19]. Combining the multiplicities in both
association ends, we can have three cases: single-single, single-multiple, and multiple-
multiple.
Figure 7. Single-single bidirectional association: a) analysis diagram and b) design diagram. The implementation of the
association's mutators must ensure that the husband of the wife of a given man is that man himself, and vice versa
Figure 8. Sequence diagram illustrating the synchronization of a bidirectional association. The update of the attribute
Woman.husband to "John" (last operation) takes place only after the update of the attribute Man.wife to "Mary"
has been correctly accomplished. If the woman were already married, then she would not request the man to update the
marriage association on his side; if the update on the man's side fails (because he is already married), then the woman
does not update her side. To achieve this behavior, the add method returns a convenient result that is checked by the
client object
two halves must be preserved by the mutator methods on each side: every time an update
is requested on one side, the other side must be informed to perform the corresponding
update; the update is accomplished only if both sides agree that they can perform it while
keeping maximum multiplicity constraints12 (see Figure 8).
A single-multiple bidirectional association can be implemented in a similar way,
combining a single unidirectional association and a multiple unidirectional association.
And, finally, a multiple-multiple bidirectional association is achieved by means of two
multiple unidirectional associations (see Figure 9).
1..3 0..10
Person Book
author publication
(a)
1..1 0..10
HashSet Object
publication element
1..3 1..1
Object HashSet
element author
(b)
Synchronization becomes progressively a more and more complex issue when one or
both association ends are multiple. Consider the example given in Figure 9. Suppose you
want to add an author to a particular Book instance; you do this by issuing the add
method on the Book instance, and passing a Person instance as a parameter. If the Book
can have more authors without violating its maximum multiplicity (which is 3), then it
requests the author to add the Book itself to the collection of publications the Person
has; this can fail if the maximum multiplicity constraint for the number of publications
(in this case, 10) is violated. If the request to the author succeeds, then the Book updates
its side.
Now, you can try adding a collection of authors to a Book, too. As one can expect,
the Book requests each one of the authors to add the Book itself as a publication; if only
one of the authors fails to add the Book, then the whole operation must be undone, since
an update must be atomic: all or none.
12
We have already justified in Section 2 that mutators must be allowed to violate the minimum multiplicity constraint.
Similar considerations apply to the remove mutator, bearing in mind that the
remove method is performed even if the minimum multiplicity constraint is not kept,
therefore it can leave the source instance or any of the affected target instances in an
invalid state.
In UML, an association is defined as a "set of tuples" [UML, p. 2-19], meaning that
you cannot have twice the same tuple in the collection of links of an association13. This is
automatically safeguarded if we follow the implementation scheme explained above.
Anyhow, it suggests also a different kind of implementation that could have some
advantages. Instead of synchronizing two unidirectional associations to get a bidirectional
one, we can directly store the collection of bidirectional links (see Figure 10).
tuple 0..*
PersonBookLink
Figure 10. An alternative scheme for implementing bidirectional associations by means of a collection of "reified"
tuples
Within this alternative scheme the links are "reified" and become objects on their own
[Rumbaugh 87]. To manage the collection of links, or tuples, we need an object, which
will be the only instance of a class (in application of the "Singleton" design pattern
[Gamma 94]) representing the association itself. The main advantage of this approach is
that it avoids the dispersion of the information about the association instances (links), so
that updates are effected in only one place, without synchronization problems. It is easily
extended to implement association-classes and associations of higher degree (ternary
associations, etc.). However, these advantages have a high cost, as we can appreciate by
comparing Figures 9 and 10.
First, note that the original multiplicity constraints are not expressed in this scheme:
the multiplicity of roles Person.pbAssociation and Book.pbAssociation must be
obviously 1..1, since there is only one instance of the object that manages the association
considered as a collection of links; besides, a link is the connection of two instances,
therefore a link has exactly one "leg" on each side [Génova 02, Génova 03b], that is,
multiplicities must be 1..1 on the roles author and publication; finally, the role
tuple has multiplicity 0..* regardless of the multiplicity of the original association, even
if it was single-single, because it stores all the links that may exist between any instances
on each side. In consequence, multiplicity constraints become more difficult to keep,
since the control cannot consist simply in "counting links".
13
The convenience of this constraint, inherited from Entity-Relationship modeling, is disputed by many authors
[Genilloud 99, Stevens 02, Génova 03b].
Second disadvantage, the uniqueness of each tuple, required by the "set of tuples"
constraint, is not automatically safeguarded. Suppose we implement the collection of
tuples by means of a HashSet of objects, each object storing two references, author
and publication. As each tuple object has its own identity, two different tuples
referencing the same two targets would be considered as different objects, therefore the
14
HashSet collection would not check the uniqueness of each tuple for us .
Considering all these factors, in our implementation we have discarded the "reified
tuples" approach in favor of the previous "synchronized cross-references" scheme.
14
This is not anyway an unsolvable difficulty. For two “equal” tuples to be recognized and their uniqueness to be
warranted, you must redefine the equals and hashCode methods, inherited from Object and employed by
HashSet with this purpose [Eckel 00].
So far we have dealt only with the Java implementation of two features of UML
associations: multiplicity and navigability (directionality), but we are interested also in
the implementation of visibility. According to the Standard [UML, p. 2-23], the visibility
of an association end "specifies the visibility of the association end from the viewpoint of
the classifier on the other end". The Standard assimilates the visibility of an association
end to the visibility of an attribute, and gives the same four possibilities:
• public - Other classifiers may navigate the association and use the rolename in
expressions, similar to the use of a public attribute.
• protected - Descendants of the source classifier may navigate the association
and use the rolename in expressions, similar to the use of a protected attribute.
• private - Only the source classifier may navigate the association and use the
rolename in expressions, similar to the use of a private attribute.
• package - Classifiers in the same package (or a nested subpackage, to any level)
as the association declaration may navigate the association and use the rolename
in expressions15.
In Java we find the same four kinds of visibility for attributes and methods (not a chance,
of course), known as access control levels [Gosling 96, Arnold 00], although their
semantics is not exactly the same as in UML16. Package visibility is the default for
unspecified access control, usually known as friendly. Since we have implemented UML
associations by means of Java attributes and methods, it seems that we should not find
special problems with the implementation of visibility17; on the contrary, it should be
rather easy.
This is true for unidirectional associations: if we declare the Java attributes and
methods with the same access control as the UML association end we want to implement,
we automatically get the desired behavior. But the story runs differently for bidirectional
associations. In principle, it seems sensible to declare private one or both ends of a binary
association. We can think of an association with two private ends as a "secret"
relationship that is not known outside the participating classes, such as a Bank-Client
association, for example. Similarly, an association with one public and one private
association ends would be only partially known from the outside. But there are problems.
15
This last kind of visibility, appended in version 1.4 of the Standard, is ambiguously defined, since an association
could be declared between classifiers from two different packages. Which package does the association declaration
belong to, then? We suggest this wording instead (additions in italics): "Classifiers in the same package (or a nested
subpackage, to any level) as the source classifier may navigate the association and use the rolename in expressions,
similar to the use of an attribute with package visibility.
16
The protected access control means in Java the union of protected and package visibilities in UML, that is,
the protected element is visible for descendants as well as for other elements in the same package [Arnold 00, Eckel 00,
Gosling 96].
17
Except that protected will have the Java meaning, not the UML meaning. The Standard acknowledges that all
forms of nonpublic visibility are language-dependent [UML, p. 3-42].
0..* 0..*
Lecturer Subject
+lecturer -subject
Figure 11. A bidirectional association with public and private association ends
The public association end, lecturer, can be used by any other class in the model with
visibility to the Subject class, that is, the collection of lecturers that teach on a given
subject can be queried and updated directly by any class in the model that sees the
subject. Instead, the private association end, subject, meaning the collection of subjects
on which a given lecturer teaches, is known only to the lecturer itself, just as a private
attribute. The Lecturer class could declare other public methods that internally refer to
the subject rolename, thus providing indirect access to the private association end, but
direct access is restricted to the owner class itself. This is no more than the idea of
declaring something private.
Now, we have got a paradox here about the bidirectionality of the association. The
association end with the private rolename subject is known only to its owner, that is,
the Lecturer class. We repeat: only to its owner. That means that the Subject class
does not know the subject association end! The Subject class knows that it is
associated with the Lecturer class, but it does not know that the Lecturer class is
associated with it in return. Is this really a bidirectional association?
In our implementation, based on synchronized cross-references as explained above,
this paradox manifests itself in the impossibility to reciprocally update the association
ends. Remember that, when an instance of Subject tries to add an instance
(newLecturer) of the Lecturer class to its collection of lecturers
(lecturer.add(newLecturer)), it first has to invoke the add method on the
reciprocal side (lecturer.subject.add(self))18; but now this is impossible due to
the private visibility of the subject association end. The same happens with the
remove method. On the contrary, if an instance of Lecturer tries to update the
association, it can issue the update method on the opposite side, because it is public, and
it can update its own private side, thus the whole operation succeeds. At first sight, then,
it seemed that the association could be managed via the class that owns the public
association end (in this case, the Subject class), but this has turned to be false: in fact,
only the class that owns the private association end (Lecturer) can manage the
association, and direct access from outside the two participating classes is impossible.
However, as it has been explained above, the Lecturer class could declare public
methods to provide indirect access to the private association end from the outside.
18
An object refers to itself in UML by means of the self keyword, equivalent to Java’s this.
Even worse, if both association ends were private, as in the Bank-Client example,
the association would become inaccessible from both sides19. The approach based on
"reified tuples" does not solve the problem either, since it involves auxiliary classes that
cannot provide "private" access to the main classes, excluding all other classes.
Summing up, a public-private bidirectional association can be managed only from
the class that owns the private end, and other classes, including the class on the other end
of the association, can have only indirect access if this class provides the adequate public
methods. A private-private bidirectional association, on the contrary, cannot be managed
at all. Similar considerations can be made for package and protected visibility, which
behave in this case respectively like public and private visibility. In consequence,
bidirectional associations with visibility other than public or package in both ends must
be rejected in code generation. We think this result is not only a bias of our particular
implementation, but a real semantic difficulty of the definition of visibility in
bidirectional associations. Visibility in UML is not specified for associations but for
association ends, and it is assimilated to the visibility of attributes [UML, p. 2-23]. We
need in UML a definition of visibility that fits better with the concept of bidirectional
association.
19
A reflexive association (an association between instances of the same class) is an exception to this rule, since private
association ends are visible inside the class, that is, for both sides of the association.
5 A UNIFORM INTERFACE
Accessor methods
We have two accessor methods, test and get, with the following signatures:
• boolean test(%Target query_link);
• boolean test(Collection query_links);
• %Target get();
• Collection get();
The test method checks whether a given instance of the target class (the query_link
parameter) is linked with the instance of the source class that receives the method
invocation. The second version of this method is defined only when the association end is
multiple; in this case the method checks whether all the instances contained in the
collection parameter are linked to the source instance20.
The get method returns the target instances that are linked with the source instance.
The first version is for a single association end and it returns a unique value, the type of
which is the %Target class, whereas the second version is for a multiple association end,
so that it returns a value of type Collection. According to the justification given above
when dealing with the problems of multiplicity, mutator methods warrant that maximum
multiplicity is not violated, but regarding minimum multiplicity, it can happen that the
number of linked instances is smaller than the minimum required by the design, in which
case the invalid multiplicity exception is raised21.
Mutator methods
We have also two mutator methods, remove and add, with the following signatures:
• int remove();
• int remove(%Target old_link);
• int remove(Collection old_links);
• boolean add(%Target new_link);
• boolean add(Collection new_links);
The remove method deletes target instances from the opposite association end, and
returns a convenient error code. It can remove all instances (first parameterless version),
one instance (second version), or a collection of instances (third version, available only
when the opposite association end is multiple). In the third version, if any instance in the
collection parameter is not of type %Target, then no link is removed, following the
"none or all" semantics, and a class cast exception is raised. On the contrary, if any
instance in the collection (or the single instance, in the second version of the method) is
simply not linked to the source instance, then the operation proceeds without considering
it an error. In a bidirectional association, the method invokes a reciprocal remove on
each one of the instances to be deleted. The remove method can leave the source
20
The parameter type is defined as Collection to get more generality. Collection is an abstraction (technically,
an interface) realized by library classes such as ArrayList, HashSet and TreeSet.
21
In fact, the check is performed by the isValid method, which can be more elaborated than simply verifying that
the number of linked instances is not smaller than the minimum required; the programmer can modify manually the
code of isValid to implement a more complex constraint.
instance or some of the target instances in an invalid state regarding the minimum
multiplicity constraints, in which case an error code is returned, but no exception is
raised. If a subsequent get method were invoked, the invalid multiplicity exception
would be raised.
The add method appends a new target instance or a collection of target instances to
the opposite association end, and returns a boolean value to indicate whether the
operation was performed or not. The second version of this method is defined only when
the association end is multiple; if any instance in the collection parameter is not of type
%Target, then no link is added, following the "none or all" semantics, and a class cast
exception is raised. On the contrary, if any instance in the collection (or the single
instance, in the first version of the method) is already linked to the source instance, then
the operation proceeds without considering it an error (and, of course, without adding a
duplicate). In a bidirectional association, the method invokes a reciprocal add on each
one of the instances to be appended. The add method checks whether the source instance,
or any of the target instances, would be left in an invalid state regarding the maximum
multiplicity constraints, in which case the operation is cancelled, no link is added, and a
22
False value is returned .
If you want to substitute some target instances by other target instances, you must
invoke first the remove method and then the add method; otherwise the result could be
different from expected (see Section 2). Beware that this is valid even for single
associations: there is no implicit remove of the old instance when you add a new
instance (this is done this way in order to get the most similar behavior between single
and multiple associations).
22
As in the previous case, the check is performed by the isValid method, which can achieve a more general
behavior.
The numberOfLinks method returns the number of target instances linked to the
source instance.
In this section we are going to present briefly the tool we have developed: JUMLA (Java
code generator for Unified Modeling Language Associations). This tool reads a UML
model, stored in XMI format, and generates Java code to manage the UML associations
contained in the input model, according to the technique described in this paper. The tool
generates code for associations only: it ignores every other UML artifact that is not
directly related to associations, such as generalization between classes, class attributes
and methods, etc. The tool presents the classes and associations found in the model, and
the user can select which associations he or she wants to generated code for.
The tool creates output Java files for the involved classes and inserts into them the
code for the associations, with convenient labels to mark the start and the end of the
generated code. If the class file already exists, the code is inserted at the end of the class
file, respecting any other class code that the programmer may have written manually (on
the contrary, if the programmer changes the association code and then re-generates it, the
manual changes are lost).
Figures 12 shows a sample model and Figure 13 shows how it is presented in the
main window of the JUMLA tool. The left pane of the tool shows the classes contained in
the model, in a tree structure corresponding to the package structure of the model. The
right pane shows the associations contained in the model. For each association, the
following information is presented: source and target classes; rolename (optional),
multiplicity, navigability and visibility of source and target association ends; association
name (optional). The user can select with check boxes the associations he or she wants to
generated code for.
+course 0..3
-director 1..1
+teacher
Department Teacher
1..* 1..2 teaches >
+member 1..*
Figure 12. A sample model with some classes and associations between the classes
Figure 13. A snapshot of the JUMLA tool. The interface of the current version of the tool is in Spanish (Archivo =
File, Edición = Edit, Ayuda = Help).
The tool behaves according to five predefined options which can be disabled by the user
to get more flexibility in the generation of code or in dealing with the input model. Table
1 summarizes them.
The first two options refer to the automatic checking of multiplicity constraints in mutator
and accessor methods by means of the isValid auxiliary method. According to the
justification given in Section 2, the predefined behavior is: get methods raise an invalid
multiplicity exception, defined in the code that implements the association, if multiplicity
constraints are not satisfied; add methods reject the addition of new links if these
constraints are not satisfied, but they raise no exception; and remove methods don’t do
any checking. Changing the default value of these two options allows the generation of a
simplified code that omits these checks, so that the user assumes the responsibility of
controlling multiplicity.
The third tool option refers to the automatic type checking in mutator methods (add
and remove) for multiple associations, which deal with Collection parameters, by
means of run-time explicit casting. According to the justification given in Section 2, if the
type-check fails, then the links are not updated, and the Java predefined class cast
exception is raised. Changing the default value of this third option allows the generation
of a simplified code that does not check the type of objects received in a Collection
parameter, and does not raise this exception.
The last two options refer to the checking of the input model´s correctness. In the
predefined behavior, unidirectional associations with multiplicity constraints on the
nonnavigable association end are rejected (see Section 3), and bidirectional associations
with visibility other than public or package in both ends are also rejected (see Section
4). Changing the default value of the fourth option allows the generation of code without
checking the multiplicity on the nonnavigable end, instead of rejecting the association.
Changing the default value of the fifth option allows the generation of code, instead of
rejecting the association, when one of the ends is protected or private and the other
end is public or package, warning the user that he or she must provide an indirect
access via other methods. When both ends are protected or private, the association
is allways rejected, since the generated code could not work properly.
7 CONCLUSIONS
In this work we have developed a concrete way of mapping UML associations into Java
code: we have written specific code patterns, and we have constructed a tool that reads a
UML design model stored in XMI format and generates the necessary Java files. We have
paid special attention to three main features of associations: multiplicity, navigability and
visibility. Our analysis has encountered difficulties that may reveal some weaknesses of
the UML Specification [UML].
Regarding multiplicity, we have shown that it is impossible in practice with a few
primitive operations to keep the minimum multiplicity constraint at any moment on a
mandatory association end; our proposal is to check this constraint only when accessing
the links, but not when modifying them. The programmer will be responsible for using
the primitives in a consistent way so that a valid system state is reached as soon as
possible. On the contrary, it is possible to ensure the fulfillment of the maximum
multiplicity constraint during run-time, and so we enforce it in our implementation.
Single association ends are easily stored in attributes having the related target class as
type, but multiple association ends require the use of collections to store the
corresponding set of links; as collections in Java are based on the standard Object class,
it is necessary to perform run-time type-checking by means of explicit casting when using
collections as parameters in the mutator methods.
Regarding navigability, unidirectional associations are easier to implement by means
of attributes than bidirectional associations, because of the difficulties in synchronizing
both associations ends. An update to a bidirectional association must be performed
atomically on both ends to keep them consistent; this is achieved in the source object by
issuing a reciprocal update on the target object. We have considered the pros and cons of
an alternative implementation, based on the storage of “reified tuples”, and finally we
have discarded it in favor of our “synchronized cross-references” scheme. A side
consequence of our analysis is that the multiplicity constraint in a design model can be
specified only for a navigable association end.
Regarding visibility, in the case of unidirectional associations it can be implemented
rather easily by simply mapping the visibility of the association end onto the visibility of
the corresponding accessor and mutator methods, because UML and Java visibility levels
have the same semantics. However, bidirectional associations with one or two private (or
protected) ends behave paradoxically, because the reciprocal update becomes impossible.
Besides, we consider that package visibility is ill-defined for associations in the UML
Specification, and we have suggested a new definition.
The generated code for each association is easily localized inside the involved Java
classes. Each association end presents a uniform programmer's interface. The interface is
exactly the same for unidirectional and bidirectional association ends, but there are slight
differences for single and multiple association ends.
Our approach is rather check-exhaustive with regard to invariants. We think that it is
worth doing for the programmer as much as we can, so that our tool will insert code to
perform run-time multiplicity and type checking and, of course, to issue reciprocal
updates on bidirectional associations. However, different tool options will allow the user
to override the automatic multiplicity and type checks when generating code, in favor of
efficiency. Besides, we have argued that unidirectional associations should not have a
multiplicity constraint on the source end in a design model, and bidirectional associations
should not have both ends with private (or protected) visibility; therefore, the tool will
reject the generation of code for these associations. Again, the user will be able to disable
this model-correctness checking and issue the code generation at his/her own risk.
This work can be continued on several lines. First, implementation of other
association end properties, such as ordering, changeability, interface specifier, xored
associations, and so on. Second, specific implementation of particular kinds of binary
associations, such as reflexive associations, aggregations and compositions. Third,
implementation of more complex associations: qualified associations, associations
classes, and n-ary associations. Fourth, expand the tool to perform reverse engineering,
that is, obtaining the associations between classes by analyzing the code that implements
them. Our tool does not presently accomplish this task, although it is a very simple and
straightforward procedure if the code has been written with our patterns. Finally, adapt
the tool and the patterns so that they follow the new Java Metadata Interface (JMI)
Specification [JMI].
8 ACKNOWLEDGEMENTS
The authors wish to give thanks to Perdita Stevens, since this paper was written mainly
while the first author was visiting the Laboratory for Foundations of Computer Science
(LFCS), part of the Division of Informatics of the University of Edinburgh, invited by her
in February-April 2002; a preliminar version of this work was presented at the LFCS Lab
Lunch on April 23rd 2002. This research stay was accomplished with funding by the
Fundación Universidad Carlos III, Madrid, Spain. Thanks also to José Miguel Fuentes,
Víctor Quintana, David Fernández and Vicente Palacios for their valuable suggestions to
improve the paper.
REFERENCES
[Amble01] Scott W. Ambler. “An Overview of Object Relationships”, “Unidirectional
Object Relationships”, “Implementing One-to-Many Object
Relationships”, “Implementing Many-to-Many Object Relationships”. A
series of tips to be found at IBM Developer Works, http://www-
106.ibm.com/developerworks/.
[Arnol00] Ken Arnold, James Gosling, David Holmes. The Java Programming
Language. Addison-Wesley, 3rd ed., 1998.
[Caste00] Xabier Castellani, Henri Habrias, Philippe Perrin. "A Synthesis on the
Definitions and Notations of Cardinalities of Relationships", Journal of
Object Oriented Programming, 13(6):32-35 (2000)
[cUML] Financial Systems Architects (New York, U.S.A.). 3C-Clear Clean
Concise. Submission to OMG. Available at http://www.community-
ml.org/.