Extending the UML to C# transformation with Rational Software Architect Version 7 and later

The UML to C# transformation capability in IBM® Rational® Software Architect (V7 and later) transforms UML models into C# sources. While the functionality of this transform is complete, you may wish to extend this transformation to include custom elements or conventions. This article introduces the basic elements of creating an extension to this transformation, using a simple example.

Anil Bhatia (AnilBhatia@in.ibm.com), Senior Staff Software Engineer, IBM

Anil Bhatia is a senior developer with IBM working on Rational UML Modeling tools. His main focus is on Model-Driven Architecture (MDA), specifically for the C# language.



16 September 2008

Also available in Chinese

Introduction

IBM® Rational® Software Architect includes a number of predefined transformations. Some are used to transform a UML model into source code. The UML-to-C# transform, like some of the other model-to-code transformations, can be extended. This article lists the steps necessary to create a simple example extension, one that demonstrates the core capabilities of this functionality.

As with most features in Rational Software Architect, to extend it you use the standard Eclipse extension mechanisms, create a plug-in and implement specific extension points. This article does not address general plug-in development; if you need more information, see the Resources section of this article. Instead, the article covers just those plug-in development skills necessary to create and test a transformation extension, and focuses on customizing the C# transformation.

Reasons that you may need to extend the transformation

Following are several possible reasons that you might need to extend the transformation. You may want to:

  • Add automated documentation to your methods, documentation which lists the parameters and return types.
  • Add author information to each generated type.
  • Change the default method body generated by the transform.

In each case, extending the transform helps you generate your own customized sources.

Getting started

As Figure 1 shows, when the UML to C# transformation is invoked, it begins with the UML model that is configured as the source for the transformation.

Figure 1. Overall transformation structure
UML to C# transformation elements

This transformation operates on the entire model, as shown in Figure 2. The transformation walks through the source, and constructs a C# Abstract Syntax Tree (AST), which is another Eclipse modeling framework (EMF) model. The transformation then used Java<trade/> Emitter Template 2 (JET2) to transform the AST to C# sources. An extension targets at least one part of the overall transformation. A complete list of targets can be found at the end of this article.

Figure 2: Configuring transformation extension elements
UML model

For this simple example, you will define a simple, class-level extension that gets invoked each time a transformation is ready to transform a UML Class element into a C# source file. This extension will do nothing more than look for any keywords defined for this class, and use them to add a new documentation tag associated with the class in the source code.

The overall steps that you will perform are:

  1. Create a new plug-in project for your transformation extension. This project can be combined with other extensions. However, to make the example simple, you will create a new project from scratch.
  2. Add a set of required plug-in dependencies. These plug-ins define the extension point that you are implementing, and provide the APIs that you will need to invoke our extension.
  3. In the plug-in descriptor, define the extension point that identifies the transformation that you want to extend, and the point within it that you want to make a contribution. For more details on the description of an extension point (and how to contribution to it), refer to Notes on the Eclipse Plug-in Architecture in the Resources section.
  4. Provide a Java<trade/> implementation of the Keyword Documentation Rule that will be invoked each time the transformation encounters the element type that you registered an interest in.
  5. Test the transformation by invoking a runtime workbench and executing a UML to C# transformation.

Creating and configuring a new plug-in project

Your transformation extension begins with a new plug-in project.

  1. Select File > New > Project.
  2. Select Plug-in Project from the list of project types, and click Next.
  3. On the first page of the New Plug-in Project wizard, enter a valid project name. The Eclipse convention for plug-ins is to use a URI domain like the one shown in Figure 3.
Figure 3. New Plug-in Project wizard
Project settings and target platform
  1. On the second page (Figure 4) you should update the default Plug-in Name field. Enter Sample UML to CS Extension. The remaining defaults are acceptable.
Figure 4. New Plug-in Project wizard properties page
Plug-in properties and options

On the final page it is not necessary to select a plug-in template, because there are no templates for transformation extensions, only new complete transformations.

  1. Clear the template option and click Finish, as shown in Figure 5.
Figure 5. New Plug-in Project wizard final page
list of available templates

When the wizard finishes, it creates a new plug-in project and opens the Overview page of the project descriptor, as shown in Figure 6.

Figure 6. New project structure
Package Explorer on left, overview on right

The next step is to add a few plug-ins (after switching to the Plug-in Development perspective) as dependencies. These plug-ins contain the extension point definitions to which you are going to provide an implementation, plus APIs that will be used in the implementation of the extension.

  1. Switch to the dependencies tab (found at the bottom of this editor) and add the following plug-ins as a dependency.
    • com.ibm.xtools.modeler
    • com.ibm.xtools.transform.core
    • com.ibm.xtools.transform.uml2.cs
    • com.ibm.xtools.cli.model

The resulting list should look similar to Figure 7.

Figure 7. Dependent plug-ins
tabs along bottom of window

Defining and implementing the extension

The next step is to declare that you are going to provide an implementation to one or more extension points.

  1. Switch to the Extensions tab
  2. Click the Add button to add a new extension. The dialog box shown in Figure 8 displays.
  3. Enter the extension point com.ibm.xtools.transform.core.transformationExtensions.
  4. When it is found, select it and click Finish button.
Figure 8. New Extension dialog
Extension points, wizards, and templates

There is more information that you need to supply in the extension point definition. You can enter these values through the user interface on the page of the Extension tab, but most people find it easier to just edit the raw XML in the plugin.xml file.

This file is created the first time that an extension is defined (before that, it was not necessary, so it was not created by the New Project wizard). Switch to the plugin.xml tab and you will see the XML source, which you can edit.

For this example, add the detail shown in Listing 1 to the extension point.

Listing 1. Extension declaration
   <extension point="com.ibm.xtools.transform.core.transformationExtensions">
      <TransformationExtension
            enabled="true"
            id="com.ibm.rsa.cs.extension.sample.transformationExtensions.id"
            name="My UML to CS Transformation Extension"
            targetTransformation=
                "com.ibm.xtools.transform.uml2.cs.internal.UML2CSTransform"
            version="1.0.0">

         <RuleDefinition
               class="com.ibm.rsa.cs.extension.sample.ClassKeywordDocRule"
               id="com.ibm.rsa.cs.extension.sample.ClassKeywordDocRule.id"
               name="Class Keyword Doc Rule">
         </RuleDefinition>

         <ExtendTransform
               targetTransform="com.ibm.xtools.transform.uml2.cs.internal.ClassTransform">
            <AddRule
                  id="com.ibm.rsa.cs.extension.sample.ClassKeywordDocRule.id">
            </AddRule>
         </ExtendTransform>
      </TransformationExtension>
   </extension>

The body of an extension element is defined by the extension point provider, in this case the Rational Software Architect Core Transformation component (com.ibm.xtools.transform.core.transform). This extension expects TransformationExtension child elements (one for each type of transformation to extend). Each one of these elements defines which transformation is being extended.

  1. For this simple example, specify a targetTransformation with the id com.ibm.xtools.transform.uml2.cs.internal.UML2CSTransform.
  2. You should apply appropriate values for the id, version number, name, and the Boolean value for enabled.

Each transformation extension defines as child elements a rule definition, which specifies a class that implements a rule interface. This class does most of the work of contributing to the transformation, providing you with a place to put your customized processing logic. It is defined as a separate element, because it is possible for a single class to contribute to multiple parts of a transformation (that is, class, property, and operation).

Once all of the rules have been defined, they are matched up with the part of the transformation that they will contribute to with ExtendTransform elements. This element defines the part of the transformation to which a rule will contribute. In other words, once the rules have been created, we need to specify when they should be used.

For this simple example, you want to contribute to the extension each time a new class is being transformed. The value of the targetTransform should be com.ibm.xtools.transform.uml2.cs.internal.ClassTransform. A complete listing of target transform ids can be found in the Appendix section at the end of this article.

Because more than one rule can contribute to any particular part of a transformation, they are collected here as child elements of the ExtendTransform element. You only have one rule defined in your project, so there is a clear and simple mapping. In case of multiple plug-ins (or multiple instances of transformationExtensions, TransformExtension, RuleDefinition, ExtendTransform, or AddRule elements), it would be helpful to make a diagram similar to that shown in Figure 2. The id of the rule in the RuleDefinition element must match that of the AddRule element.

When you have completed your editing, save the file. You should notice a small error icon in the left hand margin next to the line in the plugin.xml file that specifies the implementation class. This indicates that the class could not be found in the project. You can invoke the New Class wizard and use it to create the class file. You can also manually create the class with the File > New menu.

  1. Whichever way you prefer to create Java classes in Eclipse, create one in the designated package, and with the correct name.

You will extend the Rational Software Architect transformation framework class com.ibm.xtools.transform.core.AbstractRule. With this class as a super class, there are only three methods that should be overridden.

  • canAccept: acts like a filter to determine if this rule can accept this source element
  • createTarget: responsible for creating the target element from the source element
  • isSourceConsumed: allow or disable further rules execution on this source element

Each of these methods accepts a transformation context object, which you can query to gain access to the source and target objects. A transformation context object is a collection of various properties related to the transformation, which collection includes overall transformation configuration properties (from Preferences) and properties for the currently executing transform.

With this information, you can decide whether you want to process this particular class (canAccept), or whether further process on this class should stop (isSourceConsumed). If this extension accepts the context, then the createTarget() method is used to make the actual contribution to the transformation. In other words, you use the canAccept() method to further filter when your rule should be executed. The logic for your extension will appear in the createTargetMethod(). You can also decide if you want processing to continue for the element associated with the rule.

For this simple transformation extension, return false in the isSourceConsumed method, to indicate that further processing should continue (as shown in Listing 2). This also indicates that the extension does not mean that all of the work of the transformation of this particular element was completed.

Continuing with the example, indicate in the canAccept() method that you can accept a particular class when the source element is of type UML Class, and there is at least one keyword defined. If there are no keywords, then there is nothing for you to do, so indicate that you are not interested. Access the source UML class through the transformation context that is passed in as an argument.

The getSource() method on the context returns a reference to a UML class, because you specified in the extension point that you were interested only in Class transforms. Type the source object to a UML class, then get the keyword list from it. If the list is empty, then return false. If there are keywords, then you can expect to have the createTarget() method called. This is where you are expected to make your contribution to the transformation.

Listing 2. Java rule class implementation
package com.ibm.rsa.cs.extension.sample;

import java.util.Iterator;

import com.ibm.xtools.cli.model.CompositeTypeDeclaration;
import com.ibm.xtools.transform.core.AbstractRule;
import com.ibm.xtools.transform.core.ITransformContext;
import org.eclipse.uml2.uml.Class;

publicclass ClassKeywordDocRule extends AbstractRule {

	privatestaticfinal String NEW_LINE = System.getProperty("line.separator");
	
	publicboolean canAccept(ITransformContext context) {
		Object source = context.getSource();
		if (!(source instanceof Class))
			returnfalse;
		
		Class umlClass = (Class)source;
		return (umlClass.getKeywords().size() > 0);
	}
	
	publicboolean isSourceConsumed(ITransformContext context) {
		returnfalse;
	}

	protected Object createTarget(ITransformContext context) throws Exception {
		CompositeTypeDeclaration target = 
                            (CompositeTypeDeclaration)context.getTarget();

		if (target == null)
			returnnull;
		
		String documentation = target.getDocumentation();
		
		Class sourceClass = (Class)context.getSource();
		Iterator keywords = sourceClass.getKeywords().iterator();
		while (keywords.hasNext()) {
			String keyword = (String)keywords.next();
			documentation = " @" + keyword + NEW_LINE + documentation;
		}
		target.setDocumentation(documentation);
		return target;
	}
}

The first step you usually perform when implementing the createTarget() method is to access the source and target objects for this part of the transformation. Because you defined this extension to be invoked when the transformation is processing a UML class, you can safely type the target object to an Abstract Syntax Tree (AST) called CompositeTypeDeclaration object.

The AST is the metamodel for the C# elements. So, a C# program can be represented by an instance of the AST. For example, a class can be represented by an instance of CompositeTypeDeclaration with kind as com.ibm.xtools.cli.model.TypeConstants.CLASS. The com.ibm.xtools.cli.model plug-in provides all of the APIs for manipulating C# source code.

Since your intention is to add the keyword as a tag in the documentation, you have to get the already processed documentation first. Extract the processed documentation from the existing target. Note that if there is any existing C# source code, it is not made available in the target object. The merging of the newly generated code with any existing code happens after all of the model elements have been transformed.

This article will not discuss the AST in any detail here, except to say that when the extension needs to add new elements to the generated source it does so by calling methods on the AST objects. For this example, you want to add a new tag to the class’s documentation. The CompositeTypeDeclaration object stores the documentation as a String. You can modify this documentation as you want, and then set it back.

To determine what keyword to add, access the source object from the transformation context and cast it to a UML Class. From this object, get the list of keywords (similar to how you did so when implementing the canAccept() method). With this list, iterate over the keywords and, for each, create a new @keyword line to be prefixed to the documentation.

The method must return a target object. By default, thisextension Rule gets added as the last rule in the targetTransformation (refer again to the diagram in Figure 2). Therefore, it operates upon the "so far" target of this transformation. Unless the extension rule modifies the return element from the target transform (say an object of a different AST class, or a different object of the same AST class), the extension rule should return the same object that was passed in as a target.

This completes the creation and implementation of our transformation extension.


Testing the extension

Now that the extension is defined and implemented, you need to test it. This is done by creating a debug configuration that opens up a new runtime workbench.

  1. To create this configuration, select Run > Debug from the main menu.
  2. In the dialog, create a new Eclipse Application configuration, and name it appropriately.
  3. You can change the location of the workspace: it is usually a good idea to choose a new location so as to avoid inadvertently affecting an existing workspace.
  4. The remaining defaults are satisfactory (Figure 9).
Figure 9. New Debug configuration
tree view on left, tabs on right
  1. When you finish editing the configuration, press the Debug button to start a new instance of Eclipse.

This new instance will have the transformation extension (which you just authored) installed as a plug-in.

To test the extension, you will need to create a new UML project, and import a Visual Studio C# project into the new instance of the Eclipse shell. You can also import The Visual Studio C# project by clicking the Create New Target Container button while creating a new transformation configuration.

  1. Within the UML model create a test package and class. The resulting workspace should look something like that shown in Figure 10.
Figure 10. Test project in runtime workbench.
palette on right, views on left

Next, you need to add a keyword to the class, because your extension will only work on classes that have a keyword defined.

  1. Select the class (either in the diagram or in the Project Explorer view), and then select the Stereotypes sub-tab in the Properties view.
  2. Enter some text in the Keywords field, as shown in Figure 11.
Figure 11. Setting the keywords property.
diagram on top, tabs and data below

With the test project and model set, you need to create a new transformation configuration that defines the main parameters of the UML to C# transformation.

  1. Select File > New from the main menu.
  2. Then, under the Transformations category, select a new Transformation Configuration and click Next, as shown in Figure 12.
Figure 12. Creating a new transformation configuration
select a wizard from the list

There are many transformation types that are included with Rational Software Architect.

  1. Select the UML to C# transformation, because this is the one to which you created an extension.
  2. Give it an appropriate name, as shown in Figure 13 (this will be used as the filename of the configuration file in the UML project).
Figure 13. Selecting the transformation
list of forward transformations in tree view
  1. The first thing that you need to specify in a transformation configuration is the source model and the target project. Be sure to select the UML model as it appears under the Models folder, and not the actual .emx file.
  2. The target is created by clicking the Create New Target Container button, as shown in Figure 14.
Figure 14. Setting the source and creating the target of the transformation
source on left, target on right

This will launch the.Net Solution Import wizard shown in Figure 15.

  1. Ensure that the .NET Solution is opened in VS 2005, and use the import wizard to import the .NET Solution into Rational Software Architect workspace.
Figure 15. Import .NET solution
Enter the solution file path
  1. Use the Browse button to select the correct Visual Studio C# project to be imported. Once the Visual Studio C# project is imported, it shows up in the target list.
  2. Select the imported C# project as the Selected target for the transform (Figure 16), and click Next.
Figure 16. Select the transformation target
source on left and target on right

You will not get into the details of the other transformation configuration options here.

  1. Click Finish button to create the transformation.

Now that the configuration has been created, it can be invoked. For the life of this project it does not have to be created again.

  1. To invoke the transformation, and your extension to it, right-click the transformation configuration in the Project Explorer view, and select Transform > UML to C# (Figure 17).
Figure 17. Invoking the transformation.
menu command
  1. To examine the results of the transformation, switch to the Visual Studio IDE (Figure 18). You can see how a keyword got added in the class’s documentation.
Figure 18. Results of generated code.
code on left, tree view on right

What you have learned

This simple example of a UML-to-C# extension was meant to demonstrate the basic mechanism for creating extensions. Your extensions would most likely be more intelligent, using information in the model and making more interesting changes to the generated code, or even making additional companion artifacts to the source code.

The important point to take away from this article is that creating transformation extensions to existing transforms makes sense when you want to leverage all of the existing work, and just need to make small changes or contributions to the output. Having one common transformation that generates the code, as well as other artifacts (for instance, deployment descriptors, companion files, and so on) also makes the development process simpler: the developer only has to run one transformation instead of many.


Appendix A. Transform target IDs and source/target types

Depending on what you want to contribute to during the UML to CS Transformation, you will want to contribute to different parts of the existing transform. Each of these parts has a unique ID that must be used as the target transform ID in the plugin.xml file. The following table lists these IDs and the object types that you would expect to see in the transformation configuration.

Model

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.cs.internal.
ModelTransform
Source org.eclipse.uml2.uml.Model
Target com.ibm.xtools.cli.model.Project
Target Container Null


Package

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.cs.internal.
PackageTransform
Source org.eclipse.uml2.uml.Package
Target null
Target Container null

Class and Structure

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.cs.internal.
ClassTransform
Source org.eclipse.uml2.uml.Classor
org.eclipse.uml2.uml.Classwith <<CSharp Struct>>
Target com.ibm.xtools.cli.model.CompositeTypeDeclaration with
CompositeTypeDeclaration::kind= com.ibm.xtools.cli.model.TypeConstants.CLASSor
com.ibm.xtools.cli.model.TypeConstants.STRUCT
Target Container com.ibm.xtools.cli.model.CompilationUnitorcom.ibm.xtools.cli.model.NamespaceDeclarationor
com.ibm.xtools.cli.model.CompositeTypeDeclaration

Interface

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.cs.internal.
InterfaceTransform
Source org.eclipse.uml2.uml.Interface
Target com.ibm.xtools.cli.model.CompositeTypeDeclaration with
CompositeTypeDeclaration::kind= com.ibm.xtools.cli.model.TypeConstants.INTERFACE
Target Container com.ibm.xtools.cli.model.CompilationUnitorcom.ibm.xtools.cli.model.NamespaceDeclarationor
com.ibm.xtools.cli.model.CompositeTypeDeclaration

Delegate

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.cs.internal.
DelegateTransform
Source org.eclipse.uml2.uml.Classwith <<CSharp Delegate>>
Target com.ibm.xtools.cli.model.DelegateDeclaration
Target Container com.ibm.xtools.cli.model.CompilationUnitorcom.ibm.xtools.cli.model.NamespaceDeclarationor
com.ibm.xtools.cli.model.CompositeTypeDeclaration

Enumeration Literal

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.cs.internal.
EnumerationLiteralTransform
Source org.eclipse.uml2.uml.EnumerationLiteral
Target com.ibm.xtools.cli.model.EnumLiteral
Target Container com.ibm.xtools.cli.model.EnumDeclaration

Generalization

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.cs.internal.
GeneralizationTransform
Source org.eclipse.uml2.uml.Generalization
Target null
Target Container com.ibm.xtools.cli.model.CompositeTypeDeclaration

Interface Realization

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.cs.internal.
InterfaceRealizationTransform
Source org.eclipse.uml2.uml.InterfaceRealization
Target null
Target Container com.ibm.xtools.cli.model.CompositeTypeDeclaration

Property

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.java5.internal.
PropertyTransform
Source org.eclipse.uml2.uml.Propertywith <<CSharp Property>>
Target com.ibm.xtools.cli.model.Property
Target Container com.ibm.xtools.cli.model.CompositeTypeDeclaration

Operation

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.cs.internal.
OperationTransform
Source org.eclipse.uml2.uml.Operation
Target com.ibm.xtools.cli.model.Methodor
com.ibm.xtools.cli.model.Operator
Target Container com.ibm.xtools.cli.model.CompositeTypeDeclarationor
com.ibm.xtools.cli.model.DelegateDeclaration

Field

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.java5.internal.
FieldTransform
Source org.eclipse.uml2.uml.Propertyor
org.eclipse.uml2.uml.Propertywith <<CSharp Field>>
Target com.ibm.xtools.cli.model.Field
Target Container com.ibm.xtools.cli.model.CompositeTypeDeclaration

Parameter

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.cs.internal.
ParameterTransform
Source org.eclipse.uml2.uml.Parameter
Target null
Target Container com.ibm.xtools.cli.model.Method

Event

Transform target IDs and source/target typesXML
Target id
com.ibm.xtools.transform.uml2.cs.internal.
EventTransform
Source org.eclipse.uml2.uml.Parameter with <<CSharp Event>>
Target com.ibm.xtools.cli.model.Event
Target Container com.ibm.xtools.cli.model. CompositeTypeDeclaration

Indexer

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.cs.internal.
IndexerTransform
Source org.eclipse.uml2.uml.Operation with <<CSharp Indexer>>
Target com.ibm.xtools.cli.model.Indexer
Target Container com.ibm.xtools.cli.model. CompositeTypeDeclaration

Realization

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.cs.internal. RealizationTransform
Source org.eclipse.uml2.uml.Realization
Target null
Target Container com.ibm.xtools.cli.model.CompositeTypeDeclaration

PartialDependency

Transform target IDs and source/target typesXML
Target ID
com.ibm.xtools.transform.uml2.cs.internal. PartialDependencyTransform
Source org.eclipse.uml2.uml.Dependency with <<CSharp Partial>>
Target null
Target Container com.ibm.xtools.cli.model.CompositeTypeDeclaration

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Rational software on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Rational
ArticleID=337068
ArticleTitle=Extending the UML to C# transformation with Rational Software Architect Version 7 and later
publish-date=09162008