Skip to main content

Extending the UML to Java transformation with Rational Software Architect

An example explained

Dave Ruest (druest@ca.ibm.com ), Software developer, IBM
Dave Ruest is a software developer working for the IBM Software Group, supporting the Rational brand.

Summary:  IBM Rational Software Architect's UML to Java transformation analyzes UML models and generates Java code. This article illustrates how the transformation may be extended by walking through a working example.

Date:  02 Aug 2005
Level:  Introductory
Activity:  882 views

Introduction

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:

  1. Basic, adds a simple rule
  2. Slightly improves the rule's behavior
  3. 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:

  1. Extract them to your plugins directory
  2. Restart IRSA
  3. 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.


Example 1.0.0: The basics

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:

  1. Start IRSA
  2. Extract the source model project (see Download section)
  3. Import it into your workspace
  4. Switch to the modeling perspective and open the model

    As shown in Figure 1, you should see the com.ibm.example package, the Element interface, and its accept() method.


    Figure 1. Opened model in the modeling perspective
    Opened model in the modeling perspective
  5. 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.

  1. Extract example 1.0.0 (see Download section) to your plugins directory
  2. Restart IRSA
  3. 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 extension

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:

  1. Extract example 1.0.1 (see Download section) to your plugins directory
  2. Restart IRSA
  3. 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
    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.

  4. Select the exception parameter in the model explorer
  5. Now, in the properties view, click the Advanced tab and look for the IsException property.

    It's set to true, whereas visitor's is set to false.

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:

  1. Extract example 1.0.2 (see Download section) to your plugins directory
  2. Restart IRSA
  3. 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
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.


Summary

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!


Appendix: Transforms

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
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

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
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

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
RootTransform details

ModelTransform

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
ModelTransform details

PackageTransform

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
PackageTransform details

ClassTransform

ClassTransform generates an IDOMType (a class) populated with fields, methods, inheritance, and implementions.


Figure 9. ClassTransform details
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

InterfaceTransform generates an IDOMType (an interface) populated with fields, operations, and inheritance.


Figure 10. InterfaceTransform details
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

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
GeneralizationTransform details

ImplementationTransform

ImplementationTransform adds implements statements to generated IDOMTypes (classes). It also adds an import statement to the compilation unit if required.


Figure 12. ImplementationTransform details
ImplementationTransform details

PropertyTransform

PropertyTransform adds IDOMFields to its IDOMType container (and an import statement to the compilation unit, if required).


Figure 13. PropertyTransform details
PropertyTransform details

OperationTransform

OperationTransform adds IDOMMethods to its IDOMType container (and import statements to the compilation unit, if required).


Figure 14. OperationTransform details
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

ParameterTransform adds parameters to its IDOMMethod container (and an import statement to the compilation unit, if required).


Figure 15. ParameterTransform details
ParameterTransform details


Downloads

DescriptionNameSizeDownload method
source modelsourceModelProject.zip2KBHTTP
transformexample_1.0.0.zip9 KBHTTP
transformexample_1.0.1.zip9 KBHTTP
transformexample_1.0.2.zip10 KBHTTP

Information about download methods


Resources

About the author

Dave Ruest is a software developer working for the IBM Software Group, supporting the Rational brand.

Comments (Undergoing maintenance)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Rational
ArticleID=90773
ArticleTitle=Extending the UML to Java transformation with Rational Software Architect
publish-date=08022005
author1-email=druest@ca.ibm.com
author1-email-cc=clarkega@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers