Objectives

Using the built-in code generator

Code generation for UML models

Code generation for OCL expressions

Code integration

Code patterns used and limitations



Objectives


The main objective of the source code generator is to produce a completely runnable application from the UML model and the attached OCL specifications. Using the built-in code generator, you can turn classes, interfaces, datatypes into their corresponding Java source code equivalents. In its current version, OCLE also generates the code required for the runtime management of associations. Additionally, attached OCL constraints are turned into runnable code, which is integrated in the code for the UML model, producing a Java application that checks its own constraints. Thus, apart from the verification capabilities provided by the evaluation system (see model checking and debugging), you may also check your constraints at runtime. Unlike in other state-of-the-art tools, you can generate the code for the operations specified in OCL (using the def-let mechanism). The code generated for OCL specifications reduces object allocation to the maximum extent possible, using built-in Java types wherever this is possible.

The code generator produces Java source code that is compilable, though not complete. Once you have generated the code, you still have to write the body of generated methods.



Using the built-in code generator


To invoke the built-in source code generator, use the Generate code... option in the Tools menu. Please note that this option is only active if you have an active user model loaded in OCLE. For details about UML models and OCLE projects, you may want to refer to UML models and project management. The code generation wizards starts, showing the window in figure 1.

Code generation wizard window
Fig. 1 Code generation wizard

You first have to specify the destination directory. The destination directory is where your source files are placed, in fact the base directory for the classpath of the resulting application. Use the Browse button to locate a specific directory. The value of the destination directory property remains unchanged between two generation operations, so for the same project you will have to specify this directory only once. However, you will probably have to change it when you switch to another OCLE project in the same session. At first code generation operation, the supplied default directory is <STARTUP_DIRECTORY>/output.

In the code generation wizard you may also provide details about the properties of the generated code. Thus, select the Put opening brace on new line option if you want the opening brace '{' to be placed on the next line for class, method and block declarations. This checkbox is not selected by default, to follow the Java coding conventions.

When generating code, OCLE inserts one tab as the indentation level increases. Therefore, you may also specify if tabs must be converted to spaces and the number of blank characters for a tab (the default is 4 spaces).

The most important option provided by the code generation wizard is the validation of business constraints (the Validate business constraints option). This checkbox can only be selected if there is an active project. Therefore, it is advisable that you define an OCLE project containing the UML model and some business constraints before generating code. If this checkbox is selected, OCLE will translate the associated business constraints into equivalent Java code. Moreover, this code will be injected in the code for the active UML model, thus creating a running application that checks its own constraints. Unselect this checkbox if you are interested only in the code for the UML model.



Code generation for UML models


The source code generator allows you to turn design models into skeleton code. Skeleton code consists of the structure code for all model entities that have an equivalent in the target programming language (Java in this case). The goal of the generator is to translate most of the model elements in their corresponding source code equivalents. Different tools allow for different types of model elements to be turned into code. This version of the code generator considers only the elements composing the static view of the model. Therefore, no code is generated for actors, object diagrams or state machines. Instead you may generate code for the classes, interfaces, data types and enumerations in the active user model. An outstanding capability is the management of accessor methods for associations, even qualified.

The code generation process itself has two phases. The first consists in the validation of the active UML model against a set of profile rules. Currently, two profiles are considered: the UML profile and the Java profile. The UML profile specifies the rules that the model must fulfill in order to be correct against the definition of the UML (well formedness rules). The Java profile imposes additional, Java specific rules, such as for example no feature may have a Java reserved word as its name. The model checking process is basically a batch evaluation process, since profile rules are expressed in OCL. Therefore, any model errors are reported in the same manner as with plain batch evaluation. If at least one error is identified, you will be prompted to confirm that you want to go further with code generation, because the resulting Java code may have syntactic / semantic errors. However, at this step you may choose to stop the code generation process.

Below you can find some details regarding the way in which the code generator handles different types of model elements.


Classes and interfaces


An interface in your UML model is turned into a Java interface. If the interface is created with OCLE (not imported from some other tool through XMI), you can only add operations to it. These operations are translated to method prototypes in the generated Java interface. Note that any existing visibility modifier is ignored, since methods in Java interfaces are by default public (please see the Java language specification). If your interface happens to contain some attributes (if it is imported from another tool), these attributes are turned into the corresponding Java fields. No visibility modifier is inserted either, since fields in Java interfaces are by default public, static and final. Therefore, we strongly suggest that you pay special attention to the semantics of the fields declared in interfaces when you want to generate source code for them.

For classes, the following rules apply. First, each attribute is translated into the corresponding Java field. The owner scope and visibility properties of the attribute specify whether the resulting field is static and public/protected/private/friendly. For operations, besides the prototype, a default return statement is generated if any non-void type is specified as the operation's return type.

Associations have a special treatment. Each navigable opposite association end is turned into a Java field. For association ends with multiplicity greater than one, the java.util.Set type is used as the declared type of the field. Note that this behavior is not customizable as of this version of OCLE, which means that you cannot change the container type for association ends.


Association classes


If an opposite association end of a class or interface is part of an association class, the following rules apply:

Thus, in the Company example (provided with the distribution), for the (partial) class diagram in figure 2, you will see the following sources (only fragments are included, for brevity)

Class diagram for code generation
Fig. 2 Example class diagram for code generation

	//File Person.java

	//the declaration for the opposite end 'employer'
	public Set employer;
	...
	public final Set getEmployer() {
		if (employer == null) {
			return java.util.Collections.EMPTY_SET;
		}
		return java.util.Collections.unmodifiableSet(employer);
	}

	public final Set directGetEmployer() {

		java.util.Set temp = new LinkedHashSet();
		if (employer != null) {
			Iterator it = employer.iterator();
			while (it.hasNext()) {
				temp.add(((Job)it.next()).getEmployer());
			}
		}
		return temp;
	}

	public final void addEmployer(Job arg) {

		if (arg != null) {
			if (employer == null) {
				employer = new LinkedHashSet();
			}
			if (employer.add(arg)) {
				arg.setEmployee(this);
			}
		}

	}

	public final void removeEmployer(Job arg) {

		if (employer != null && arg != null) {
			if (employer.remove(arg)) {
				arg.setEmployee(null);
			}
		}

	}

No matter the multiplicity of the opposite association end, the Java class generated for the association class will also contain two fields having the name and type corresponding to its connections as a plain association. In the diagram above, it is the case of the Job class.

	//File Job.java

	public Company employer;
	public Person employee;

	
	public final Person getEmployee() {
		return employee;
	}

	public final void setEmployee(Person arg) {
		if (employee != arg) {
			Person temp = employee;
			employee = null; //to avoid infinite recursions
			if (temp != null) {
				temp.removeEmployer(this);
			}
			if (arg != null) {
				employee = arg;
				arg.addEmployer(this);
			}
		}
	}

Qualified associations


If an opposite association end of a class is qualified (has at least one qualifier), the resulting Java field will be declared of type java.util.Map. This type cannot be changed in this version of OCLE.


Accessor methods for association ends


For each association end, besides the corresponding Java field, OCLE also generates the minimum required accessor methods. Their purpose is to enable the developer maintain the consistency of links between the objects composing the runtime configuration. Basically, this is about updating links in both directions, irrespective of the accessor method that triggers the update operation. For example, the removeEmployer(Job) method in class Person also calls setEmployee(null) on the argument object.

For simple, non-qualified association ends, only two methods are generated. The getter method returns either the value of the association end or an unmodifiable view of the set that contains the objects at the opposite link end, depending on the end's multiplicity. The setter has the form set<EndName> or add<EndName> if the end is 0..1/1 or multiple respectively. If the association end is multiple, an additional remove method is automatically generated.

For an association end that is part of an association class, the setter methods have the same characteristics. However, a supplementary getter is generated that obtains the real opposite end, skipping the association class object. This is the case of the directGetEmployer() method in class Person. In the case of association classes, connections are mapped to fields of multiplicity 0..1/1, and appropriate setter / getter methods are generated (see the Job class code snippet above).

For qualified association ends, the signature of generated accessor methods also includes the qualifier attributes. Particularly, two getter methods are generated. One of them has no arguments and returns the entire set of objects at the opposite association end. The other returns a single object from the set. This object is retrieved based on specific values for the qualifier attributes. Therefore, this second getter method has the qualifiers as formal arguments. For example, the structure displayed in the diagram in figure 3 turns into the following code (only an excerpt included, for brevity)

	//File Bank.java (class Bank)

	public final Set getCustomer() {
		java.util.Set temp = new LinkedHashSet();
		if (customer != null) {
			temp.addAll(customer.values());
		}
		return temp;
	}
	
	public final Person getCustomer(int accountNumber) {
		if (customer == null) return null;
		ArrayList key = new ArrayList();
		key.add(Integer.toInteger(accountNumber));
		return (Person)customer.get(key);
	}
	
	public final void addCustomer(int accountNumber, Person arg) {
		if (arg != null) {
			ArrayList key = new ArrayList();
			key.add(Integer.toInteger(accountNumber));
			if (customer == null) customer = new HashMap();
			Person temp = (Person)customer.put(key, arg); //the previous value, if any
			if (temp != arg) {
				arg.setBank(this);
				if (temp != null) {
					temp.setBank(null);
				}
			}
		}
	}
	
	public final void removeCustomer(int accountNumber) {
		if (customer != null) {
			ArrayList key = new ArrayList();
			key.add(Integer.toInteger(accountNumber));
			Person temp = (Person)customer.remove(key);
			if (temp != null) {
				temp.setBank(null);
			}
		}
	}
	
	public final void removeCustomer(Person arg) {
		if (customer != null || arg != null) {
			if (customer.values().remove(arg)) {
				arg.setBank(null);
			}
		}
	}
Class diagram for qualified associations
Fig. 3 Example class diagram for qualified associations

A qualified association end inherently has a multiplicity greater than one. Therefore, additional add and remove methods are generated for it. The generated add method has the qualifiers specified as formal arguments, the last argument being the object to add, declared with the appropriate type. There are two remove methods generated. One of them also has the qualifiers specified as formal arguments. The other specifies the exact object to remove. Please see the above code snippet for the qualified association between class Bank and Person.


Datatypes


Some UML CASE tools (Rational Rose, for example) create Datatypes behind the scenes and the resulting models refer to these datatypes. Therefore, a proper mapping is required for datatypes too. When it encounters a Datatype, OCLE first checks if it represents a primitive type. If it is a non-primitive type, a Java class is generated for it, using the mapping for plain UML classes.


Enumerations and enumeration literals


Since enumerations are very useful artifacts both in modeling and in programming languages, OCLE also defines a mapping for the Enumeration metaclass. This mapping is also used for classes that are <<enumeration>> stereotyped. For an explicit enumeration, a class is generated that has the literals as public static final fields. These fields are initialized in a static initializer using a private constructor. This behavior is the same for <<enumeration>> stereotyped classes; the only difference is that the fields are generated from the features of the class (instead of the enumeration literals). Please note that the resulting fields are declared as having the class / enumeration itself as their type. Moreover, for <<enumeration>> stereotyped classes, any operations and any field type/ visibility specifications are ignored. Therefore, we stronly suggest that you pay special attention to which classes you apply the <<enumeration>> stereotype when you intend to generate Java code from them.

Consider the enumeration Sex in figure 4 (this is from the Company example included in this distribution of OCLE).

Enumerations in the model browser
Fig. 4 Enumerations in the model browser

Below you find the generated source code for enumeration Sex.

	public class Sex {

		private Sex() {

		}
	
		public static final Sex male;
	
		public static final Sex female;
	
		static{

			male = new Sex();
			female = new Sex();

		}
	}

As you may see, the standard equals() method is not generated for the corresponding Java class. This means that enumeration literals will be compared using object identity. This is not a problem, however, since the corresponding class has a private constructor, and the only instances are those created in the static initializer - in fact the enumeration literals.



Code generation for OCL expressions


If the Validate business constraints checkbox is selected, OCLE will also generate source code equivalents for the OCL expressions attached to the active user model. Moreover, the resulting code fragments are properly integrated in the source code for the structure of the model. Note that only business constraints are taken into account, which means that no code is generated for OCL specifications expressed at the metamodel level.

In its current version, OCLE translates the following entities:

OCL 2.0 tuple types are also treated in a special manner by the code generator.

Following paragraphs describe the way in which different types of OCL specifications are integrated into the generated structural code. The extensions to the OCL grammar proposed by us (see them here) and implemented in the integrated OCL compiler are not taken into account by the source code generator.



Code integration


This section describes the ways in which the source code generated for different kinds of OCL expressions is integrated into the generated structural code.


Code integration for class invariants


If a class in your UML model has at least one invariant attached to it, its generated source code is modified in the following way:

Thus, wherever the Java compiler allows it, you may trigger the verification of one or more class invariants using the code template given here:

	new ConstraintChecker().<checker_method>();

where <checker_method> is either checkConstraints for all constraints or a specific method for checking a particular invariant. Since the ConstraintChecker class is public, you may trigger the evaluation of class invariants even outside the body of the constrained class.


Code integration for method pre/post conditions


For operation constraints, OCLE uses the same code pattern as for class invariants. Some differences exist, however. The constraint checker class is inserted at the beginning of the body code for the constrained method. It has no base class. It contains two public methods, checkPreconditions and checkPostconditions. Both methods have the same arguments as the constrained method itself, so that the names of the formal parameters are available to the code that checks the constraints. The constraint checker class also contains a private checker method for each method constraint. The resulting private methods are called by either checkPreconditions or checkPostconditions.

The code for a constraint method looks like this:

	
	public boolean hireEmployee(Person p) {

		class ConstraintChecker {
			public void checkPreconditions(Person p) {
				check_precondition(p);
			}

			public void checkPostconditions(Person p) {
			}

			public void check_precondition(Person p) {
				Set setEmployee = Company.this.directGetEmployee();
				boolean bIncludes = CollectionUtilities.includes(setEmployee, p);
				boolean bNot = !bIncludes;
				if (!bNot) {
					System.err.println("precondition 'precondition' failed for object "+Company.this);
				}
			}
			boolean result;
		}

		ConstraintChecker checker = new ConstraintChecker();
		checker.checkPreconditions(p);
		checker.result = internal_hireEmployee(p);

		checker.checkPostconditions(p);
		return checker.result;
	}

As we can see, the real body of the constrained method is moved in another method (in this case internal_hireEmployee()). Moreover, the constraint checker class also has a public field, result which maps to the result variable available in OCL postconditions. This variable is properly assigned before triggering the verification of any postcondition. However, some postcondition specific constructs, such as @pre and the oclIsNew operation in OclAny are not properly mapped in the generated source code. If the code generator encounters such constructs, it issues adequate warning messages.


Code integration for observers specified in OCL


All OCL observers are mapped to Java methods in the generated source code. The corresponding method for such an observer (somertimes called a definition constraint ) is public and is placed in the class in the context of which the definition constraint is specified. The following example (Finance Business, also included in the distribution) illustrates the translation of definition constraints.

		context Portofolio
		def prices:
         	let priceOfOrders: Real = orders.security.price->sum()

In this case, class Portofolio would contain the following code (completely generated by OCLE):

		public float priceOfOrders() {
		
			Set setOrders = Portofolio.this.getOrders();
			
			//evaluate 'collect(security)':
			List bagCollect = CollectionUtilities.newBag();
			final Iterator iter = setOrders.iterator();
			while (iter.hasNext()) {
				final Order decl = (Order)iter.next();
				Security securitySecurity = decl.getSecurity();
				bagCollect.add(securitySecurity);
			}
			bagCollect = CollectionUtilities.flatten(bagCollect);

			//evaluate 'collect(price)':
			List bagCollect0 = CollectionUtilities.newBag();
			final Iterator iter0 = bagCollect.iterator();
			while (iter0.hasNext()) {
				final Security decl = (Security)iter0.next();
				float fPrice = decl.price;
				bagCollect0.add(Real.toReal(fPrice));
			}
			bagCollect0 = CollectionUtilities.flatten(bagCollect0);

			float fSum = CollectionUtilities.sum(bagCollect0);
			return fSum;
		}

Code integration for tuple types


If your business constraints use OCL tuple types, OCLE will turn each identified (distinct) tuple type into a class. Tuple parts are converted to public fields with the names deduced from the names of the involved tuple parts. This mapping is in fact similar to C/C++ structs. The equals() method is also generated, to ensure proper comparison between tuples (tuple type instances). Let us for example consider the following OCL tuple type:

		TupleType(array:String, length:Integer)

OCLE will generate the following code for it:

		//File TupleType1.java
		package tupleTypes;

		publc class TupleType1 {
			public boolean equals(Object arg) {
				if (!(arg instanceof TupleType1)) {
					return false;
				}
				boolean result = true;
				TupleType1 local = (TupleType1)arg;
				result&= (array != null ? array.equals(local.array) : local.array == null);
				result&= (length == local.length);
				return result;
			}
			
			public String array;
			public int length;
		}

Please note that the name of the resulting class is generated by OCLE, since OCL does not allow for the specification of tuple type names.

All tuple types identified in the OCL expressions are placed in a dedicated package, named tupleTypes and this behaviour cannot be changed in this version of OCLE - this means that you cannot change the location for a class corresponding to a tuple type.



Code patterns used and limitations


The translation from OCL to Java follows a syntax-directed approach. Support for metamodeling (OclAny, allInstances and so on) is provided by means of a simple library shipped with the distribution. The library, named OCLFramework.jar is located in the /lib subdirectory of your OCLE installation directory. Since the generated code contains calls to classes in the framework, you will have to include this archive in the classpath of the generated application. Basically, the entire OCL 2.0 grammar is supported. There are however two notable drawbacks. First of them has already been mentioned: the @pre construct is not properly mapped. Moreover, this version provides limited degree of customization as far as constraint violations are concerned. More precisely, the same pattern, based on System.err.println is used whenever a constraint is violated. Additionally, although supported by the compiler, messaging expressions are not translated in equivalent source code.


OCL datatypes


The source code generator was designed with efficiency in mind. Therefore, primitive OCL types are mapped to primitive types provided by the Java programming language, thus avoiding excessive wrapping. More precisely, Boolean maps to boolean, Integer maps to int and Real maps to float. The String type provided by OCL is translated using the java.lang.String type. Please note that these mappings are not customizable as of this version of OCLE.


The OclAny metaclass


The OclAny metaclass is equated with the root class java.lang.Object. The operations in OclAny, including the navigation to OclType are translated by redirecting them to the Ocl class in the above mentioned library. Each method in this class has an object as its first argument. This argument is a placeholder for self. The rest of the arguments are those mentioned in the OCL specification. Whenever an object is expected and the previous result is of primitive type, the expected object is obtained by wrapping the primitive value. Please note, however, that some operations are not properly implemented in this version (this is the case for oclIsNew() and oclInState()).


The OclType metaclass and OCL collection types


The OclType metaclass in OCL is mapped to a class OclType specifically designed for this purpose in the OCLFramework library. Basically, OclType objects are wrappers around java.lang.Class objects. However, OclType can be used to model nested collection types, such as Set(Bag(Integer)). The following table lists the mappings used for OCL collection types.



OCL Collection type Java equivalent type
Set java.util.Set
OrderedSet ro.ubbcluj.lci.codegen.framework.dt.OrderedSet
Sequence java.util.List
Bag java.util.List

Type casts and conversion to primitive values are generated whenever necessary, so that the resulting source code is semantically correct from the point of view of the Java compiler

Operations in OCL collection types are translated by redirecting them to the framework class CollectionUtilities, as in the case of OclAny. As in that case, the first of argument of the methods in the CollectionUtilities class is a placeholder for self - the collection on which the operation is called.


OCL Undefined values


This version of the code generator does not handle OCL Undefined values properly. The biggest problem we encountered when modeling the Undefined value was its type, OclVoid. The OCL specification asserts that this type must conform to all types in the user model or in the OCL datatype system. Therefore, the code generator does not map the OclVoid type explicitly. Instead, the semantics mentioned in the OCL grammar is partially ensured using the following conventions:



Back to main index