If you've tried IBM® Rational® Software Architect's (IRSA) Java transformation and thought "useful, but if only it also generated [fill in the blank]," then this tutorial is for you. This article will walk you through a set of working examples. Each example uses the transformation framework extensibility to add functionality to the Java transformation. The examples function as follows:
- Basic, adds a simple rule
- Slightly improves the rule's behavior
- Adds "
throws Exception" to generated method declarations
I encourage you to try each example as it is explained. They are ready to install and run. Simply:
- Extract them to your plugins directory
- Restart IRSA
- Transform.
Sources are included for reference purposes; however, this article will not cover writing plugins, running transformations, or basic transformation authoring. Refer to IRSA Help for more information on those subjects.
Let's see just what example 1.0.0 does before we delve into the details of how it is accomplished. To understand the example's effect, you must first see the standard Java™ transformation behavior. To do so, follow these steps:
- Start IRSA
- Extract the source model project (see Download section)
- Import it into your workspace
- Switch to the modeling perspective and open the model
As shown in Figure 1, you should see the
com.ibm.examplepackage, theElementinterface, and itsaccept()method.
Figure 1. Opened model in the modeling perspective
- Run the Java transformation on the package.
Note that
Element.accept()is generated with two parameters:public void accept(Visitor visitor, Object exception)
Now let's see what the example does.
- Extract example 1.0.0 (see Download section) to your plugins directory
- Restart IRSA
- Run the transformation again
This time,
Element.accept()is generated without any parameters at all:public void accept()
It may not be much, but if one class with barely any behavior can make this change, imagine what could be done with a little more effort. Let's see how it worked.
The transformation framework extensibility is based on the Eclipse plug-in architecture. The examples are therefore plugins, which declare extensions to points defined by the transformation core. Specifically, in order to add rules or transforms to an existing transformation, you must declare a transformationExtension. Look at the extension defined in version 1.0.0.
<extension point="com.ibm.xtools.transform.core.transformationExtensions">
<TransformationExtension
version="1.0.0"
name="%plugin.extensionName"
enabled="true"
targetTransformation="com.ibm.xtools.transform.uml2.java.internal.UML2JavaTransform"
id="com.ibm.xtools.transform.uml2.java.exception.extension">
<!-- RuleDefinition and ExtendTransform tags discussed below-->
</TransformationExtension>
</extension>
|
The extension defines a localized name and an id to differentiate it from other extensions. Most important, however, is the targetTransformation attribute. This specifies the transformation to which rules and transforms will be added. The example uses the Java transformation id (defined in com.ibm.xtools.transform.uml2.java/ plugin.xml) as its target. Its single rule is therefore added to the Java transformation.
The example defines one rule. Here is the RuleDefinition from plugin.xml:
<RuleDefinition
name="%exceptionRuleName"
class="com.ibm.xtools.transform.uml2.java.exception.
ExceptionRule"
id="com.ibm.xtools.transform.uml2.java.exception.Exc
eptionRule">
</RuleDefinition>
|
The definition has a localized name, used to provide a progress message while the rule executes. It also has an id to distinguish it from other rules. Finally, it has a class, the fully qualified class name of a rule. Transform core will attempt to load this class and add an instance of it to the previously specified transformation. We will see later that ExceptionRule is, as required, a com.ibm.xtools.transform.core.AbstractRule subclass.
Note that the RuleDefinition does not specify where the rule should be inserted. The Java transformation is a complex structure of extractors, transforms, and rules. The specific transform to which the rule should be added -- and its order in any existing rules there -- is defined with ExtendTransform and AddRule elements. From the example plugin.xml:
<ExtendTransform
targetTransform="com.ibm.xtools.transform.uml2.java.internal.
ParameterTransform">
<AddRule
id="com.ibm.xtools.transform.uml2.java.exception.
ExceptionRule"
index="0">
</AddRule>
</ExtendTransform>
|
ExtendTransform elements define the transform to which rules are added. Its targetTransform property is the id of a specific transform within the extended transformation. In this case, the targetTransform is the Java transformation's parameter transform. The parameter transform is the set of rules which processes org.eclipse.uml.Parameter. Like all transforms in the Java transformation, its id is defined in:
com.ibm.xtools.transform.uml2.java.IUMLJava.TransformId
AddRule elements specify a rule and where it should be inserted. Their id determines which rule is added. The id must match that of an existing RuleDefinition (ExceptionRule in this case). An AddRule's index dictates where the rule is inserted among existing rules. As the example uses index 0, ExceptionRule will be added before any existing rules.
So we have seen how the example rule was added to the Java transformation. But how did this simple example prevent the transformation from generating method parameters? The final piece of the puzzle is in the rule class. The source for com.ibm.xtools.transform. uml2.java.exception. ExceptionRule is included; let's have a look.
ExceptionRule's isConsumed() method
At first glance, the rule class doesn't seem like it would have any effect. ExceptionRule has two stub constructors and two very short methods. The createTarget() method is a stub that does nothing and has no effect on the Java transformation. The isConsumed() method, on the other hand:
/** Return true. As explained in the link below, all
sibling rules, extractors and
* transforms are skipped. Since the rule is inserted
first, the parameter
* transform and rule will not execute, and no
parameters are generated.
* @see com.ibm.xtools.transform.core.AbstractRule#isSourceConsumed(
com.ibm.xtools.transform.core.ITransformContext)
*/
public boolean isSourceConsumed(ITransformContext
context) {
return true;
}
|
As described in the @see link above, this simple method instructs the transformation engine whether or not it should further process the current source element. The default implementation returns false, and the engine continues executing any further rules and extractors. ExceptionRule.isConsumed(), however, returns true. Since it is inserted before the default parameter rules (remember that index=0), those rules are not executed. Specifically, the rule which normally adds parameters to method signatures does not execute.
And that is how a single rule with one overridden method can change the behavior of the Java transformation. Of course, preventing parameter generation isn't very useful. In anticipation of a more constructive example, let's look at one that is at least a little better behaved.
Example 1.0.1: Slightly better behaved
ry out example 1.0.1 to see what it does:
- Extract example 1.0.1 (see Download section) to your plugins directory
- Restart IRSA
- Run the transformation on the same source model project.
Looking at the newly generated Element interface, you can see (Figure 2) that
accept()is now generated with one parameter.
Figure 2. Newly generated Element interface
default: public void accept(Visitor visitor, Object exception) 1.0.0: public void accept() 1.0.1: public void accept(Visitor visitor)
Why was the first parameter generated, but not the other? There is a clue in the properties of the model.
- Select the exception parameter in the model explorer
- Now, in the properties view, click the Advanced tab and look for the
IsExceptionproperty.It's set to
true, whereas visitor's is set tofalse.
Example 1.0.1 prevents generation of parameters whose IsException property is true. While only slightly better behaved, this example is an important step to adding special parameter processing (that is, adding an exception to the throws statement for each parameter marked as an exception). First, let's see how example 1.0.1 works.
ExceptionRule's canAccept() method
ExceptionRule 1.0.1 is identical to its predecessor except for one important addition. It has overridden AbstractRule.canAccept(), as shown jin the following listing. As described in the @see link, canAccept() returns whether or not a rule can process a source element. If this method returns false, the transformation engine will not execute the rule.
isException property is true. As
* explained by the link below, this means our rules
createTarget() and
* isSourceConsumed() are executed only if the parameter
is an exception.
* They are skipped otherwise.
* @see com.ibm.xtools.transform.core.AbstractTransformElement#canAc
cept(com.ibm.xtools.transform.core.ITransformContext)
*/
public boolean canAccept(ITransformContext context) {
Parameter source = (Parameter) context.getSource();
return source.isException();
}
|
In the implementation, notice that your rule retrieves the current source object from ITransformContext. Since rules are stateless by design, each time a transform is run the data for it is stored in a state object. The context is a dictionary with some convenience methods, like getSource() above. The getSource() method is untyped, since -- depending on the transform source and current extractor -- anything could be returned. In this case, since the rule is in the parameter transform, the source is org.eclipse.uml2.Parameter. The rule can now determine whether the source parameter represents an exception.
If the parameter is not an exception, make installcanAccept() returns make installfalse. The transformation engine, therefore, does not execute the rule or ask if the source was consumed. Since make installisSourceConsumed() is not called in this case, the engine proceeds to execute subsequent rules in the parameter transform. The default parameter rule is executed, and these parameters are later generated.
However, if the parameter is an exception, canAccept() returns true. The transformation engine executes the rule (that is, it calls createTarget(), which does nothing yet), then asks if the source was consumed. Since ExceptionRule.isSourceConsumed() always returns true, the engine does not process subsequent rules. The default parameter rule does not execute, and this parameter is not generated later.
Example 1.0.1 carefully avoids generating only those parameters marked as an exception. Only one step remains: adding a throws statement to the generated method.
Example 1.0.2: Generating a throws statement
Time to try out example 1.0.2 be performing the following steps:
- Extract example 1.0.2 (see Download section) to your plugins directory
- Restart IRSA
- Run the transformation on the source model project
Looking at the newly generated Element class, you can see that accept() is now generated with one parameter. In addition, it throws an exception!
Figure 3. Throwing an exception
default: public void accept(Visitor visitor, Object exception) 1.0.0: public void accept() 1.0.1: public void accept(Visitor visitor) 1.0.2: public void accept(Visitor visitor) throws Exception |
The latest example goes beyond the passive behavior of its predecessors (preventing parameter generation) and actually adds to the generated output. How? The secret is the createTarget() method.
ExceptionRule's createTarget() Method
ExceptionRule 1.0.2 builds on the previous examples by adding an implementation of createTarget(). As stated in the @see link below, the createTarget() method is where rules are expected to generate a target object corresponding to the current source. Note that the UML to Java transformation uses JDOM to build an in-memory model before modifying anything on disk (this simplifies team and merge support). JDOM does not have an object that represents parameters, but our rule can still manipulate the transformation's JDOM model, as we shall see.
/** ...
* @see com.ibm.xtools.transform.core.AbstractRule#createTarget(com.
ibm.xtools.transform.core.ITransformContext)
*/
protected Object createTarget(ITransformContext context)
{
IDOMMethod container = (IDOMMethod)
context.getTargetContainer();
container.setExceptions(EXCEPTIONS);
return null;
}
|
The first thing the method does is retrieve the target container. In the case of the parameter transform, this is an IDOMMethod, created by the enclosing operation transform. The rule proceeds to call IDOMMethod.setExceptions() with the constant EXCEPTIONS (previously initialized to {"Exception"}). This instructs the method that its throws clause should contain one exception called Exception. When the generate rule later retrieves a source representation for this method, it will contain the throws clause we saw earlier.
That's it:
- Some XML
- Three overrides in one rule, added to the parameter transform
- Exceptions added to the Java transformation
Imagine what could be accomplished by a more comprehensive extension. You can add rules to each and every one of the transforms. If that is not sufficient, you can add entire new transforms. Do you have an idea how enumerations should be transformed? Add an EnumerationTransform to the RootTransform!
Of course, to write such an extension, you will need more information about the Java transformation. Which transforms are available? What are their IDs? What are the expected sources, containers, and targets? This information is critical to adding rules to the right transform, and to manipulating sources and targets appropriately. The answers are in the following Appendix. Have fun writing your extension!
Figure 4 is an overview of the transformation. Transforms are represented by colored boxes, while rules are white boxes. The top-level (or "glue") UML2JavaTransform is shown in red at the top left. The next level down RootTransform is in orange to its right. The remaining levels of transforms are similarly shown in their own colored boxes.
Figure 4. Available Transforms
The following sections describe each transform in more detail. Along with a brief overview and any important notes, important standard information is provided in a table. The identifier is used in ExtendTransform elements to specify the transform to which rules are added. The source, target container, and target are important information for rules:
- The transform's source is the UML object for which the rule is expected to create a new target
- The target container is the target generated by a parent transform
- The target is the object generated by the transform's rules
UML2JavaTransform iterates over the UML source objects and builds an in-memory JDOM model. It retrieves a source representation, formats it, merges it with any existing source, and saves the merged source to disk.
Figure 5. UML2JavaTransform details
InitializeRule creates a type map, retrieves the source root from the target project, and stores these in the context for the use of the rest of the transform. GenerateRule iterates over the compilation units in the JDOM model -- retrieving, formatting, merging, and saving source for each.
TeamTransform is placed after the JDOM model is populated, but just before any files modification. It uses the Eclipse team API to ensure that files are checked out if necessary. Note that if your extension creates files, it should also ensure that those files are checked out. It is as easy as adding a rule to the team transform. The rule can call com.ibm.xtools.transform.core.ValidateEditRule.addAffectedFiles(ITransformContext, List) and the team transform will take care of the rest.
UML2MapTransform creates a mapping model (when the transform is in this mode). The mapping transform is not discussed in detail, as it is likely to change in the near future.
RootTransform (or UML2JDOMTransform) iterates over the UML source objects, building the in-memory JDOM model. To add support for other UML objects (for example, Enumeration or Artifact), you would add a transform for that object here. That is, once this transforms, validation is refactored.
Figure 6. RootTransform details
ModelTransform does relatively little, as there is no JDOM or Java representation for a model. It does create a default package as a container for nested transforms. The generated package is ignored by classifier rules, because the mapping feature can override it. The model transform remains for any extensions that may need to add rules.
Figure 7. ModelTransform details
PackageTransform also does very little: it creates a package with a name corresponding to the source package. This generated package is also ignored by classifier rules, again because the mapping feature can override it.
Figure 8. PackageTransform details
ClassTransform generates an IDOMType (a class) populated with fields, methods, inheritance, and implementions.
Figure 9. ClassTransform details
ClassRule generates an IDOMCompilationUnit containing a stub IDOMType. Inheritance and implementations are added by the generalization and implementation transforms. Fields are added by the property transform, and methods are added by the operation transform.
InterfaceTransform generates an IDOMType (an interface) populated with fields, operations, and inheritance.
Figure 10. InterfaceTransform details
InterfaceRule generates an IDOMCompilationUnit containing a stub IDOMType. Inheritance is added by the generalization transform, fields by the property transform, and methods by the operation transform.
GeneralizationTransform adds extends statements to generated IDOMTypes (classes and interfaces). It adds an import statement to the compilation unit if required.
Figure 11. GeneralizationTransform details
ImplementationTransform adds implements statements to generated IDOMTypes (classes). It also adds an import statement to the compilation unit if required.
Figure 12. ImplementationTransform details
PropertyTransform adds IDOMFields to its IDOMType container (and an import statement to the compilation unit, if required).
Figure 13. PropertyTransform details
OperationTransform adds IDOMMethods to its IDOMType container (and import statements to the compilation unit, if required).
Figure 14. OperationTransform details
OperationRule creates the method stub (access, return type, and name). ConstructorRule checks to see if the source is stereotyped with Basic::Creates. If so, it calls IDOMMethod.setConstructor(true); JDOM takes care of the remaining details. OperationBodyRule chooses a stub method body based on the return type, evaluates the Java method template, and sets the body of the method.
ParameterTransform adds parameters to its IDOMMethod container (and an import statement to the compilation unit, if required).
Figure 15. ParameterTransform details
| Description | Name | Size | Download method |
|---|---|---|---|
| source model | sourceModelProject.zip | 2KB | HTTP |
| transform | example_1.0.0.zip | 9 KB | HTTP |
| transform | example_1.0.1.zip | 9 KB | HTTP |
| transform | example_1.0.2.zip | 10 KB | HTTP |
Information about download methods
- The article Introducing IBM Rational Software Architect: Improved usability makes software development easier (developerWorks, February 2005) is a basic introduction to the Rational Software Architect product.
- Get the evaluation version of Rational Software Architect from the Trials and betas page
- For technical resources about Rational's products, visit the developerWorks Rational content area
. You'll find technical documentation, how-to articles, education, downloads, product information, and more. For specific information about Rational Software Architect, visit the RSA technical resources page.
- Ask questions about Rational Software Architect in
the Rational Software Architect, Software Modeler, Application Developer and Web Developer forum.
Comments (Undergoing maintenance)





