в библиотеку

Model-Driven Architecture using JET2 in Rational Software Architect or Rational Software Modeler

Автор: Dev Bhattacharyya, Senior IT Architect, IBM
[ Электронный источник: читать ]

Introduction

But let's talk about it. Wouldn't it be nice…?
—The Beach Boys

Wouldn't it be nice if you were to take the initial requirements document for a project and be able to accurately estimate the design and development effort? This article tells you how to transform a Unified Modeling Language (UML) model created in IBM® Rational® Software Architect into a dynamic estimation and feedback engine. Thanks to the tremendous growth in Model-Driven Architecture (MDA) and design, static nondescript images, models, and diagrams are now close to the point they can be executed. Rational Software Architect and IBM® Rational® Software Modeler are key proponents of Java Emitter Templates (JET) and UML modeling. This article attempts to go beyond the traditional code generation to more useful systems, such as estimation.

The JET projects in this article describe these tasks:

  1. Estimating at the requirements and analysis stage
  2. Comparing estimations as the model matures, at the design stage
  3. Generating feedback to the model

Models provide vivid insight into how business problems can be expressed through notational UML. A typical requirements gathering includes these components:

Models evolve and mature over time. A typical model that started as just requirements may also include structural classifier models that express interactions. A use case model is a perfect place to start the estimation process.

About JET

The Eclipse Modeling Framework (EMF), which is an integral part of Rational Software Architect (and Rational Software Modeler, contains powerful tools for generating source code and documents. One of them is JET (Java Emitter Templates). With JET, you can use a syntax that is somewhat like JavaServer™ Pages (JSP) to write templates and run them against models.

Prerequisites

The article is intended for readers who have a reasonable working knowledge of Rational Software Architect. It is also based on the assumption that the reader understands XML, Java™ technology, and XPATH scripting syntax and semantics.


First transformation: project estimation from use cases

A portion of this article is based on the findings that Capers Jones describes in Chapter 7 of his book, Estimating Software Costs: Bringing Realism to Estimating, Second Edition (see Resources). See Chapter 7, "Manual Estimating Methods Derived from Agile Projects and New Environments." He states that a use case represents an average of roughly 35 function points, although it can vary between 15 and 75 function points. A typical application of 1500 function points (roughly 75,000 Java statements), would require about 42 use case diagrams. A single use case roughly defines the usage pattern of about 1785 Java statements.

Use case estimation was baselined in 1998-2001. Much of thought leadership came from Schneider and Winters in1998 and Alistair Cockburn in 2001 (see Resources). This article assumes that a project team that wants to estimate with use case points writes their use cases at goal levels. Each use case therefore has a goal. The goal of a user goal-level use case is analogous to a unit of business value.

Use case points are an accumulation of total numbers of actors, relationships, and other aspects of use case methodology that indicate a value for the project being estimated, thus forming the basis for the JET2 estimation engine. After the size in use case points is determined, effort and schedule algorithms can then be applied.

Complexity of actors and use cases

Each use case represents an average of 35 function points. Figure 1 shows a simple project as outlined and created in Rational Software Architect.


Figure 1. Use case example
use case diagram

You can use Table 1 to determine unadjusted weighted values of use cases and actors.


Table 1. Computing unadjusted use case points (actors)
Actor type Description Weighting factor Number of actors Total
Simple External system with well-defined API 1 WF * Number
Average External system using a protocol-based interface: HTTP or TCP/IP or database 2 WF * Number
Complex Human 3 WF * Number
Unadjusted actor weight total Total


Table 2. Computing unadjusted use case points (use cases)
Use case type Description Weighting factor Number of use cases Total
Simple 1-3 transactions 5 WF * Number
Average 4-7 transactions 10 WF * Number
Complex More than 7 transactions 15 WF * Number
Unadjusted use case weight total Total

To analyze a use case by using this table, the actors and use cases are stereotyped with keys: simple, average, or complex.

A separate JET project must be created to run against the model JET transformation. Start from the New Project wizard:

  1. From main menu, click File > New > Project.
  2. In the JET Transformations wizard, select JET Transformation Project.
  3. Enter a project name. For this example, use Estimation.
  4. Click Finish.

The JET project created has a template folder that contains two files:

Because the project will be addressing different types of estimations, create two separate JET templates: one for use case point analysis and another for class point analysis.

The counters for the different types of actors and use cases are enclosed in the Java scriplet section: <% %>

The code scans the model for use cases and actors. When it encounters them in the model, it checks for the stereotype key and increments the respective counters accordingly, as code Listing 1 illustrates.


Listing 1. Use case point analysis
 
<%--
  // Initialize Variables.
--%>
<%
int countUCComplex = 0;
int countUCAverage = 0;
int countUCSimple = 0;
int countActorComplex = 0;
int countActorAverage = 0;
int countActorSimple = 0;
%>
  
<%-- 
  // Get the name of the model 
--%>
<c:setVariable var="model" select="/Model" />
Project Estimates for Model: <c:get select="$model/@name" />
 
<%-- 
  // iterate through all packages 
--%>
<c:iterate select="//Package" var="package" >
  <c:if test = "//Package[self::Package]" >
Packages: <c:get select="$package/@name" />
  <c:setVariable select="$package"  var="source"/>
  <%-- 
    // iterate through all use cases in the package 
  --%>
    <c:iterate select="$package/packagedElement[self::UseCase]" var="useCase" >
  Use Case: <c:get select = "$useCase/@name" />
    <%-- get the stereotypes --%>
      <c:iterate select="$useCase/eAnnotations" var="stype" >
        <c:iterate select="$stype/details" var="details" >
        <c:setVariable select="$details/@key"  var="key"/>
    Type: <c:get select = "$key" />
        <c:choose>
          <c:when test = "$key = 'complex'">
            <% ++countUCComplex; %>
          </c:when>
          <c:when test = "$key = 'average'">
            <% ++countUCAverage; %>
          </c:when>
          <c:otherwise>
            <% ++countUCSimple; %>
          </c:otherwise>
        </c:choose> 
        </c:iterate>
      </c:iterate>    
    </c:iterate>
  <%-- 
    // Iterate through all use case actors in the package 
  --%>
    <c:iterate select="$package/packagedElement[self::Actor]" var="actor" >
  Actor: <c:get select = "$actor/@name" />
    <%-- get the stereotypes --%>
      <c:iterate select="$actor/eAnnotations" var="stype" >
        <c:iterate select="$stype/details" var="details" >
        <c:setVariable select="$details/@key"  var="key"/>
    Type: <c:get select = "$key" />
        <c:choose>
          <c:when test = "$key = 'complex'">
            <% ++countActorComplex; %>
          </c:when>
          <c:when test = "$key = 'average'">
            <% ++countActorAverage; %>
          </c:when>
          <c:otherwise>
            <% ++countActorSimple; %>
          </c:otherwise>
        </c:choose> 
        </c:iterate>
      </c:iterate>    
    </c:iterate>
  </c:if>
</c:iterate>
**Totals**
 
Simple Use Cases: <%= countUCSimple %>, UC Weight: <%= countUCSimple*5 %>
Average Use Cases: <%= countUCAverage %>, UC Weight: <%= countUCAverage*10 %>
Complex Use Cases: <%= countUCComplex %>, UC Weight: <%= countUCComplex*15 %>
Simple Actors: <%= countActorSimple %>, Actor Weight: <%= countActorSimple*1 %>
Average Actors: <%= countActorAverage %>, Actor Weight: <%= countActorAverage*2 %>
Complex Actors: <%= countActorComplex %>, Actor Weight: <%= countActorComplex*3 %>

Computing total unadjusted points and person hours

The total unadjusted points are the sum of all individual totals:

Unadjusted use case points (UUCP) = Unadjusted actor weight (UAW) + Unadjusted use case weight (UUCW), thus:

UUCP = UAW + UUCW

To calculate the person hours, you must compute the technology complexity and the environment, as Listing 2 shows.


Listing 2. Computing unadjusted use case points
 
 <% int totalWeight = countUCSimple*5 + 
			countUCAverage*10 +
			countUCComplex*15 +
			countActorSimple*1 +
			countActorAverage*2 +
			countActorComplex*3;
%>
Unadjusted Use Case Points = <%= totalWeight %>

Assuming a Technical Complexity Factor of 1.075, an Experience Factor of 0.545, and Experience / Stability Index of 7, the unadjusted person hours can be computed as Listing 3 shows.


Listing 3. Unadjusted person hours
 
<% double personHours = (20 * totalWeight) * 1.075 * 0.545; %>
Unadjusted Person Hours = <%= personHours %>

You can allocate the Person Hours to the different IBM® Rational Unified Process® (RUP®) phases, as the example in Listing 4 shows.


Listing 4. Allocation to different phases
 
Project Life Cycle
Inception (5%) = <%= personHours*0.05 %> Hours
Elaboration (20%) = <%= personHours*0.2 %> Hours
Construction (65%) = <%= personHours*0.65 %> Hours
Transition (10%) = <%= personHours*0.1 %> Hours


Extended transformation: class point analysis

The class point approach provides system-level estimation. The basic ideas of the Class Point method were first proposed by Costagliola, Ferrucci, Tortora, and G. Vitiello (see Resources). The aim of the proposed approach is to provide a method that we can use to refine estimates throughout the development and to exploit new information as it becomes available.

Because function point counters frequently have to deal with specifications that contain use cases, some preliminary data is also available from them.

Identification and classification of user classes

During the first step of the class point counting, the design specifications are analyzed in order to identify and classify the classes.

Determining class complexity

External Methods tell you the size of the interface of a class. It is determined by the number of locally defined public methods. Services Requested provide a measure of the interconnection of different system components. It is again applicable to a single class and is determined by the number of different services requested from other classes.

Computing total unadjusted class points

The Class model and the Interaction diagram trace back to the use case described here previously, as Figures 2 and 3 illustrate.


Figure 2. Class model
UML diagram of a Class model

Figure 3. Interaction diagram
UML model of an interaction diagram

Table 3. Complexity and weighting for external methods and services requested
Weighting Complexity
Up to 8 External Methods and Single Service Request Simple
More than 8 on a Single Service Request Average
Up to 4 External Methods on less than 4 Service Requests Simple
Between 5 and 8 External Methods on less than 4 Service Requests Average
More than 8 External Methods on less than 4 Service Requests High
Up to 4 External Methods for 4 or more Service Requests Average
All others High

The next steps involve computing from the different models described above - Use Case, Class model and Interaction diagram showing the flow of messages. Listing 5 is the JET template that does the work. To compute the External Methods and Service Requests, one will have to iterate multiple times. A Java vector has been instantiated that does the carry-over of necessary data. A couple of interesting ways to share data between the template and Java code have also been described. As Listing 5 illustrates, the code traverses first through the Class model and then through the Interaction model, computing the external methods and service requests as it progresses.


Listing 5. Class point analysis
 
<%@jet imports="java.util.*" %> 
<% 
class Additions {
  public String className;
  public int nem;
  public int nsr;
  public String lifeLine;
}
 
java.util.Vector modelClass = new java.util.Vector();
java.util.Vector l = new java.util.Vector();
 
%>
<c:setVariable select="/Model" var="modelName" />
Model Name: <c:get select="$modelName/@name" />
<c:iterate select="//Package" var="package">
  Package Name: <c:get select="$package/@name" />
  <%-- Iteration through all classes --%>
  <c:iterate select="$package/packagedElement[self::Class]" var="class" >
    Class: <c:get select="$class/@name" />
    <c:setVariable var="clName" select="$class/@name" />
    <% int nems = 0; %>
    <c:iterate select="$class/ownedOperation" var="oper" >
      Operation: <c:get select="$oper/@name" />
      Visibility: <c:get select = "$oper/@visibility" />
        <c:if test="$oper/@visibility='public'" >
        <% ++nems; %>
        </c:if>
    </c:iterate>
    <c:if test="$class/ownedOperation">
    <%
      Additions a = new Additions();
      a.className = org.eclipse.jet.xpath.XPathUtil.xpathString
	(context.getVariable("clName"));
      a.nem = nems;
      modelClass.addElement(a);
    %>
    </c:if>
  </c:iterate>
  <%-- Iteration through all Interactions --%>
  <c:iterate select="$package/packagedElement[self::Collaboration]" var="collab" >
    Collaboration: <c:get select="$collab/@name" />
    <c:iterate select="$collab/ownedBehavior[self::Interaction]" var="interact" >
      Interaction: <c:get select="$interact/@name" />
      <c:iterate select="$interact/lifeline" var="lline" >
        <c:setVariable var="llName" select="$lline/@name" />
        Lifeline: <c:get select="$lline/@name" />
          Represents: <c:get select="$lline/represents/@name" />, 
		 <c:get select="$lline/represents/type/@name" />
          <c:setVariable var="repClass" select="$lline/represents/type/@name" />
        <%
          boolean notFound = true;
          for (int i = 0; i < modelClass.size(); i++) {
            Additions a = (Additions) modelClass.elementAt(i);
            String repClassName = org.eclipse.jet.xpath.XPathUtil.xpathString
			(context.getVariable("repClass"));
            if (repClassName.equals(a.className)) {
              a.lifeLine = org.eclipse.jet.xpath.XPathUtil.xpathString
			(context.getVariable("llName"));
              modelClass.setElementAt(a, i);
              notFound = false;
            } 
          }
          if (notFound) {
            Additions a = new Additions();
            a.className = org.eclipse.jet.xpath.XPathUtil.xpathString
		(context.getVariable("repClass"));
            a.lifeLine = org.eclipse.jet.xpath.XPathUtil.xpathString
		(context.getVariable("llName"));
            modelClass.addElement(a);
          }
        %>
      </c:iterate>
      <c:iterate select="$interact/fragment" var="frag" >
        Fragment (covered): <c:get select="$frag/covered/@name" />
          <c:setVariable var="llname" select="$frag/covered/@name" />
          <c:if test="$frag/message">
          Message: <c:get select="$frag/message/@name" />
            Type: <c:get select="$frag/message/@messageSort" />
            <c:if test="$frag/message/@messageSort='synchCall'">
              <%
              String lifeLine = null;
                for (int i = 0; i < modelClass.size(); i++) {
                  Additions a = (Additions) modelClass.elementAt(i);
                  lifeLine = org.eclipse.jet.xpath.XPathUtil.xpathString
			(context.getVariable("llname"));
                  if (lifeLine.equals(a.lifeLine)) {
                    ++a.nsr;
                    modelClass.setElementAt(a, i);
                  } 
                }
              %>
            </c:if>
          </c:if>
          <c:if test="$frag/start">
          Start: <c:get select="$frag/start/message/@name" />
          </c:if>
          <c:if test="$frag/finish">
          Finish: <c:get select="$frag/finish/message/@name" />
          </c:if>
      </c:iterate>
      <c:iterate select="$interact/message" var="msg" >
        Message: <c:get select="$msg/@name" />
          Receive Event: <c:get select="$msg/receiveEvent/message/@name" />
          Send Event: <c:get select="$msg/sendEvent/message/@name" />
      </c:iterate>
    </c:iterate>
  </c:iterate>
</c:iterate>
 
Totals:
<%
int countSimple = 0;
int countAverage = 0;
int countComplex = 0;
for (java.util.Enumeration e = modelClass.elements(); e.hasMoreElements();) {
  Additions a = (Additions) e.nextElement();
%>
Class: <%= a.className %>, NEMS <%= a.nem %>, Lifeline <%= a.lifeLine %>, 
				NSR <%= a.nsr %>
<% 
  if ((a.nem <= 8) && (a.nsr <= 1)) ++countSimple;
  else if ((a.nem <= 4) && (a.nsr >= 2) && (a.nsr <= 3)) ++countSimple;
  else if ((a.nem <= 4) && (a.nsr > 3)) ++countAverage;
  else if ((a.nem >= 5) && (a.nem <= 8) && (a.nsr >= 2) && (a.nsr <= 3)) ++countAverage;
  else if ((a.nem > 8) && (a.nsr >= 2) && (a.nsr <= 3)) ++countAverage;
  else ++countComplex;
} 
%>
 
Counts:
Simple:  <%= countSimple %>
Average: <%= countAverage %>
Complex: <%= countComplex %>
 
<%
  int tucp = (countSimple * 5) + (countAverage * 8) + (countComplex * 13);
%>  
TUCP (Total Unadjusted Class Points) = <%= tucp %>
</c:log>

After you know the total unadjusted points, it is easy to add the other factors to arrive at the unadjusted person hours, just as you did for the use case point analysis.


Summary and other options

This article walked you through most of the key areas of a UML model and how you can extract, transform, and create artifacts that add to intelligent reporting. You can do similar extractions by using BIRT (Business Intelligence and Reporting).