Showing posts with label uml. Show all posts
Showing posts with label uml. Show all posts

Sunday, April 27, 2014

Contributing to the Papyrus Editor Palette

Augmenting the Papyrus Palette

Goal

This entry is the first in a series of articles that details the customizations to the Eclipse Papyrus platform for the UML Testing Tool. These customizations, though specific to the needs of the UML Testing Tool community, can be generalized and used for other products with similar requirements.

The UML Testing Tool is a graphical workbench for the design, visualization, specification, analysis, construction, and documentation of the artifacts involved in testing. The language for modeling these artifacts is based on a UML Testing Profile. This profile defines a set of UML stereotypes that extend a subset of UML elements. There are Similar tools based on other UML profile specifications, such as the NIEM UML Profile.

Papyrus is an environment within Eclipse that enables users to configure graphical editors for UML-based DSLs.

The goal of this first entry is to describe how to register a UML profile with Papyrus and to augment the Papyrus editor palette with stereotyped elements defined in this profile.

tl;dr

The source code for the set of Eclipse plug-ins that contains the UML Testing Profile, its registrations with the Eclipse UML and Papyrus extensions and the test cases described in this post is located here. Feel free to use, explore, contribute to or fork this code for your own needs. A snapshot of the code specific to these examples at the time of writing is here.

Registering the UML Profile

Let's assume that we already have all the boilerplate configuration for an Eclipse plug-in, test fragment, feature and p2-repository in place. For this plug-in, we can use the tycho_new_plugin_project archetype, initially described here.

We will start with an acceptance test to verify that we have correctly registered the UML Testing Profile as a UML dynamic package.

PackageRegistrationTest.java
PackageRegistrationTest.java
package timezra.uml.testing.resources;

import static org.eclipse.uml2.uml.UMLPlugin.getEPackageNsURIToProfileLocationMap;
import static org.eclipse.uml2.uml.resources.util.UMLResourcesUtil.init;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.junit.Assert.assertThat;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.uml2.uml.Profile;
import org.junit.Test;

public class PackageRegistrationTest {
    @Test
    
public void should_register_the_uml_testing_package() {
        
final EObject utp = init(new ResourceSetImpl()).getEObject(
                getEPackageNsURIToProfileLocationMap().get(
"http://www.omg.org/spec/UTP/20120801/utp_1.2"), true);

        assertThat(utp, instanceOf(Profile.
class));
    }
}

In order to satisfy this test, we must include the utp_1.2.xmi and utptypes_1.2.xmi with our plug-in. We will modify the utp_1.2.xmi by adding the xmlns declaration xmlns:cmof="http://www.omg.org/spec/MOF/20110701/cmof.xmi" to the root header. We also need to define the profile through the Eclipse UML Editor.

With the UTP and its supporting type descriptions packaged into a plug-in, we can now register an extension for the UML dynamic package for the profile we just defined through the Eclipse UML Editor.

plugin.xml
plugin.xml
<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
    <extension point="org.eclipse.emf.ecore.uri_mapping">
        <mapping source="pathmap://UTP/"
            target="platform:/plugin/timezra.uml.testing.resources/src/main/resources/uml/">
        </mapping>
    </extension>
    <extension point="org.eclipse.emf.ecore.uri_mapping">
        <mapping source="http://www.omg.org/spec/UTP/20120801/utp_1.2.xmi"
            target="pathmap://UTP/utp_1.2.xmi">
        </mapping>
        <mapping source="http://www.omg.org/spec/UTP/20120801/utptypes_1.2.xmi"
            target="pathmap://UTP/utptypes_1.2.xmi">
        </mapping>
    </extension>
    <extension point="org.eclipse.uml2.uml.dynamic_package">
        <profile location="pathmap://UTP/utp_1.2.xmi#_iCl4wOVMEeG84fBOY39c0g"
            uri="http://www.omg.org/spec/UTP/20120801/utp_1.2">
        </profile>
    </extension>
</plugin>

Now, if we install our feature into Eclipse and create a new UML model, the UTP will be available for profile application through the UML Editor. UTP Registered With UML

Profile Registration With Papyrus

In order to register this profile with Papyrus, we can use similar steps: create a test to verify when the behavior we are trying to expose works and expose the profile location through an extension.

ProfileRegistrationTest.java
ProfileRegistrationTest.java
package timezra.uml.testing.papyrus.palettes;

import static org.eclipse.papyrus.uml.extensionpoints.profile.RegisteredProfile.getRegisteredProfile;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;

import org.junit.Test;

public class ProfileRegistrationTest {
    @Test
    
public void should_register_the_uml_testing_profile() {
        assertThat(getRegisteredProfile(
"UTP"), notNullValue());
    }
}

plugin.xml
plugin.xml
....
   <extension
         point="org.eclipse.papyrus.uml.extensionpoints.UMLProfile">
      <profile
            description="UML Testing Profile"
            iconpath="icons/full/obj16/utp.png"
            name="UTP"
            path="http://www.omg.org/spec/UTP/20120801/utp_1.2.xmi#_iCl4wOVMEeG84fBOY39c0g"
            provider="omg.org">
      </profile>
   </extension>
   <extension
         point="org.eclipse.papyrus.uml.extensionpoints.UMLLibrary">
      <library
            description="UML Testing Profile Types"
            iconpath="icons/full/obj16/utptypes.png"
            name="UTP Types"
            path="http://www.omg.org/spec/UTP/20120801/utptypes_1.2.xmi"
            provider="omg.org">
      </library>
   </extension>
....

The UTP is now available in the Papyrus repository, and it can be applied from the Profile tab of the Properties View. UTP Registered With Papyrus

Palette Customization

Once we are able to apply the UTP to a Model, we can specify tests with UML elements extended by this profile. Suppose we begin by creating a TestContext from the Papyrus palette.

ClassDiagramPaletteTest.java
ClassDiagramPaletteTest.java
package timezra.uml.testing.papyrus.palettes;

import static org.eclipse.papyrus.uml.diagram.common.util.URIUtil.getFile;
import static org.eclipse.papyrus.uml.tools.model.UmlUtils.getUmlResource;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;

import org.eclipse.gef.EditDomain;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.gef.Tool;
import org.eclipse.gef.palette.PaletteContainer;
import org.eclipse.gef.palette.PaletteEntry;
import org.eclipse.gef.palette.ToolEntry;
import org.eclipse.gef.ui.palette.PaletteViewer;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.parts.IDiagramWorkbenchPart;
import org.eclipse.papyrus.editor.PapyrusMultiDiagramEditor;
import org.eclipse.papyrus.infra.core.editor.IMultiDiagramEditor;
import org.eclipse.papyrus.uml.diagram.clazz.CreateClassDiagramCommand;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.widgets.Event;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.Element;
import org.eclipse.uml2.uml.Package;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
import org.junit.rules.TestRule;

import timezra.uml.testing.papyrus.palettes.rules.AppliesTheUTP;
import timezra.uml.testing.papyrus.palettes.rules.CreatesAPapyrusModel;
import timezra.uml.testing.papyrus.palettes.rules.CreatesAProject;
import timezra.uml.testing.papyrus.palettes.rules.CreatesAServicesRegistry;
import timezra.uml.testing.papyrus.palettes.rules.OpensEditor;
import timezra.uml.testing.papyrus.palettes.rules.SavesAModel;

public class ClassDiagramPaletteTest {

    
private CreatesAServicesRegistry theServicesRegistry;
    
private CreatesAProject theProject;
    
private CreatesAPapyrusModel thePapyrusModel;
    
private OpensEditor<IMultiDiagramEditor> theEditor;

    @Rule
    
public final TestRule ruleChain = RuleChain
            .outerRule(theServicesRegistry = 
new CreatesAServicesRegistry())
            .around(theProject = 
new CreatesAProject(getClass().getSimpleName()))
            .around(thePapyrusModel = 
new CreatesAPapyrusModel(theServicesRegistry, theProject, "model.di",
                    
new CreateClassDiagramCommand()))
            .around(
new AppliesTheUTP(thePapyrusModel))
            .around(
new SavesAModel(thePapyrusModel))
            .around(theEditor = 
new OpensEditor<IMultiDiagramEditor>(
                    () -> getFile(getUmlResource(thePapyrusModel.get()).getURI()), PapyrusMultiDiagramEditor.EDITOR_ID));

    @Test
    
public void can_create_a_test_context() {
        
final IGraphicalEditPart theActivePart = ((IDiagramWorkbenchPart) theEditor.get().getActiveEditor())
                .getDiagramEditPart();
        
final EditPartViewer theEditPartViewer = theActivePart.getViewer();
        doubleClick(findThePaletteTool(theEditPartViewer, 
"UTP/Test Context (Class)"), theEditPartViewer);

        
final Element theTestContext = ((Package) theActivePart.resolveSemanticElement())
                .getPackagedElement(
"TestContext1");

        assertThat(theTestContext, instanceOf(Class.
class));
        assertThat(theTestContext.getAppliedStereotype(
"utp::TestContext"), notNullValue());
    }

    
private Tool findThePaletteTool(final EditPartViewer theEditPartViewer, final String toolPath) {
        
final EditDomain theDomain = theEditPartViewer.getEditDomain();
        
final PaletteViewer thePaletteViewer = theDomain.getPaletteViewer();
        
final ToolEntry toolEntry = findByLabel(thePaletteViewer.getPaletteRoot(), toolPath);
        thePaletteViewer.setActiveTool(toolEntry);

        
final Tool theTool = toolEntry.createTool();
        theTool.setViewer(theEditPartViewer);
        theTool.setEditDomain(theDomain);

        
return theTool;
    }

    @SuppressWarnings(
"unchecked")
    
private <T extends PaletteEntry> T findByLabel(final PaletteContainer thePaletteContainer, final String theLabel) {
        
final String[] path = theLabel.split("/");
        PaletteEntry nextEntry = thePaletteContainer;
        NEXT_SEGMENT: 
for (final String segment : path) {
            
if (nextEntry instanceof PaletteContainer) {
                
for (final Object o : ((PaletteContainer) nextEntry).getChildren()) {
                    
final PaletteEntry paletteEntry = (PaletteEntry) o;
                    
if (segment.equals(paletteEntry.getLabel())) {
                        nextEntry = paletteEntry;
                        
continue NEXT_SEGMENT;
                    }
                }
                
return null;
            } 
else {
                
return null;
            }
        }
        
return (T) nextEntry;
    }

    
private void doubleClick(final Tool theTool, final EditPartViewer theEditPartViewer) {
        
final Event theEvent = new Event();
        theEvent.widget = theEditPartViewer.getControl();
        
final MouseEvent mouseEvent = new MouseEvent(theEvent);
        mouseEvent.button = 1;
        theTool.mouseDoubleClick(mouseEvent, theEditPartViewer);
    }
}

Now we will describe a palette entry for the Class Diagram Editor that is in a UTP drawer and that will apply the utp::TestContext stereotype to a UML class.

UTP.clazz.palette.xml
UTP.clazz.palette.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<paletteDefinition>
    <content>
        <drawer iconpath="/icons/drawer.gif" id="UTP_Drawer" name="UTP">
            <aspectTool description="Create a Test Context (Class)"
                iconpath="platform:/plugin/timezra.uml.testing.papyrus.palettes/icons/full/obj16/test_context_class.png"
                id="clazz.tool.class_UTP_TEST_CONTEXT" name="Test Context (Class)" refToolId="clazz.tool.class">
                <postAction id="org.eclipse.papyrus.applystereotypeactionprovider">
                    <stereotypesToApply>
                        <stereotype stereotypeName="utp::TestContext" />
                    </stereotypesToApply>
                </postAction>
            </aspectTool>
        </drawer>
    </content>
</paletteDefinition>

Finally, we will register this palette configuration with a Papyrus extension.

plugin.xml
Copy of plugin.xml
....
<extension point="org.eclipse.papyrus.uml.diagram.common.paletteDefinition">
    <paletteDefinition ID="timezra.uml.testing.papyrus.palettes.UTP_clazz"
        class="org.eclipse.papyrus.uml.diagram.common.service.PluginPaletteProvider"
        icon="icons/full/obj16/utp_palette.png" name="UTP Class Diagram Elements"
        path="palettes/UTP.clazz.palette.xml">
        <Priority name="Medium">
        </Priority>
        <editor id="org.eclipse.papyrus.uml.diagram.clazz">
        </editor>
    </paletteDefinition>
</extension>
....

Once we register the palette definition with Papyrus, the test will pass and the TestContext contribution will be visible for UML Models that have the UML Testing Profile applied. TestContext palette contribution

Conclusion

In this article, we expose the following behaviors from the Eclipse workbench: the ability to apply the UML Testing Profile within the UML and Papyrus Editors and the ability to create UML elements extended by UML stereotypes defined in the UTP directly from a Papyrus editor palette. We describe these behaviors through tests and define them with plug-in extensions and external configuration. The code for these examples is here.

Saturday, May 22, 2010

Testing Stereotype Attribute RegEx Constraints

Goal:


Suppose that we have a UML profile that contains a stereotype with an attribute of a custom data type constrained by a regular expression. This tutorial steps through one way to automate tests that exercise this restriction.

Create a UML Profile:


We can use the instructions from a previous tutorial to create a UML profile with a restricted data type. In our case, a Contact is an Actor who has a US phone number. We can call our profile Contact.profile.uml:

<?xml version="1.0" encoding="UTF-8"?>
<xmi:XMI xmi:version="2.1" xmlns:xmi="http://schema.omg.org/spec/XMI/2.1" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:Ecore="http://www.eclipse.org/uml2/schemas/Ecore/5" xmlns:ecore="http://www.eclipse.org/emf/2002/Ecore" xmlns:uml="http://www.eclipse.org/uml2/3.0.0/UML" xsi:schemaLocation="http://www.eclipse.org/uml2/schemas/Ecore/5 pathmap://UML_PROFILES/Ecore.profile.uml#_z1OFcHjqEdy8S4Cr8Rc_NA">
 <uml:Profile xmi:id="_-8vWsEz2Ed-q5fB8PwONew" name="ContactProfile" metaclassReference="_Q-rN8Ez-Ed-q5fB8PwONew">
  <eAnnotations xmi:id="_TjG80GI3Ed-E2et4hPKZEg" source="http://www.eclipse.org/uml2/2.0.0/UML">
   <contents xmi:type="ecore:EPackage" xmi:id="_TjJZEGI3Ed-E2et4hPKZEg" name="RegexValidation" nsURI="http:///schemas/RegexValidation/_Til_cGI3Ed-E2et4hPKZEg/0" nsPrefix="RegexValidation">
    <eClassifiers xmi:type="ecore:EClass" xmi:id="_TjJZEWI3Ed-E2et4hPKZEg" name="Contact">
     <eAnnotations xmi:id="_TjJZEmI3Ed-E2et4hPKZEg" source="http://www.eclipse.org/uml2/2.0.0/UML" references="_KxZQgEz3Ed-q5fB8PwONew"/>
     <eStructuralFeatures xmi:type="ecore:EReference" xmi:id="_TjJZE2I3Ed-E2et4hPKZEg" name="base_Actor" ordered="false" lowerBound="1">
      <eType xmi:type="ecore:EClass" href="http://www.eclipse.org/uml2/3.0.0/UML#//Actor"/>
     </eStructuralFeatures>
     <eStructuralFeatures xmi:type="ecore:EAttribute" xmi:id="_TjJZFWI3Ed-E2et4hPKZEg" name="PhoneNumber" ordered="false" lowerBound="1" eType="_TjJZF2I3Ed-E2et4hPKZEg"/>
    </eClassifiers>
    <eClassifiers xmi:type="ecore:EDataType" xmi:id="_TjJZF2I3Ed-E2et4hPKZEg" name="USPhoneNumberType" instanceClassName="java.lang.String">
     <eAnnotations xmi:id="_TjJZGGI3Ed-E2et4hPKZEg" source="http://www.eclipse.org/uml2/2.0.0/UML" references="_i7slcE0EEd-q5fB8PwONew"/>
     <eAnnotations xmi:id="_TjJZGWI3Ed-E2et4hPKZEg" source="http:///org/eclipse/emf/ecore/util/ExtendedMetaData">
      <details xmi:id="_TjJZGmI3Ed-E2et4hPKZEg" key="baseType" value="http://www.eclipse.org/emf/2003/XMLType#string"/>
      <details xmi:id="_TjJZG2I3Ed-E2et4hPKZEg" key="pattern" value="1?\W*([2-9][0-8][0-9])\W*([2-9][0-9]{2})\W*([0-9]{4})(\se?x?t?(\d*))?"/>
      <details xmi:id="_TjJZHGI3Ed-E2et4hPKZEg" key="name" value="USPhoneNumberType"/>
     </eAnnotations>
    </eClassifiers>
    <eClassifiers xmi:type="ecore:EDataType" xmi:id="_TjJZHWI3Ed-E2et4hPKZEg" name="UMLPrimitiveTypes_String" instanceClassName="UMLPrimitiveTypes_String">
     <eAnnotations xmi:id="_TjJZHmI3Ed-E2et4hPKZEg" source="http://www.eclipse.org/uml2/2.0.0/UML">
      <references xmi:type="uml:PrimitiveType" href="pathmap://UML_LIBRARIES/UMLPrimitiveTypes.library.uml#String"/>
     </eAnnotations>
    </eClassifiers>
   </contents>
  </eAnnotations>
  <elementImport xmi:id="_-_cFYEz2Ed-q5fB8PwONew">
   <importedElement xmi:type="uml:PrimitiveType" href="pathmap://UML_LIBRARIES/UMLPrimitiveTypes.library.uml#Boolean"/>
  </elementImport>
  <elementImport xmi:id="_-_koQEz2Ed-q5fB8PwONew">
   <importedElement xmi:type="uml:PrimitiveType" href="pathmap://UML_LIBRARIES/UMLPrimitiveTypes.library.uml#String"/>
  </elementImport>
  <elementImport xmi:id="_-_koQUz2Ed-q5fB8PwONew">
   <importedElement xmi:type="uml:PrimitiveType" href="pathmap://UML_LIBRARIES/UMLPrimitiveTypes.library.uml#UnlimitedNatural"/>
  </elementImport>
  <elementImport xmi:id="_-_koQkz2Ed-q5fB8PwONew">
   <importedElement xmi:type="uml:PrimitiveType" href="pathmap://UML_LIBRARIES/UMLPrimitiveTypes.library.uml#Integer"/>
  </elementImport>
  <elementImport xmi:id="_Q-rN8Ez-Ed-q5fB8PwONew">
   <importedElement xmi:type="uml:Class" href="pathmap://UML_METAMODELS/UML.metamodel.uml#Actor"/>
  </elementImport>
  <packagedElement xmi:type="uml:Stereotype" xmi:id="_KxZQgEz3Ed-q5fB8PwONew" name="Contact">
   <ownedAttribute xmi:id="_kIBdYkz-Ed-q5fB8PwONew" name="base_Actor" association="_kIBdYEz-Ed-q5fB8PwONew">
    <type xmi:type="uml:Class" href="pathmap://UML_METAMODELS/UML.metamodel.uml#Actor"/>
   </ownedAttribute>
   <ownedAttribute xmi:id="_XBEfIE0HEd-q5fB8PwONew" name="PhoneNumber" type="_i7slcE0EEd-q5fB8PwONew"/>
  </packagedElement>
  <packagedElement xmi:type="uml:Extension" xmi:id="_kIBdYEz-Ed-q5fB8PwONew" name="Actor_Contact" memberEnd="_kIBdYUz-Ed-q5fB8PwONew _kIBdYkz-Ed-q5fB8PwONew">
   <ownedEnd xmi:type="uml:ExtensionEnd" xmi:id="_kIBdYUz-Ed-q5fB8PwONew" name="extension_Contact" type="_KxZQgEz3Ed-q5fB8PwONew" aggregation="composite" association="_kIBdYEz-Ed-q5fB8PwONew"/>
  </packagedElement>
  <packagedElement xmi:type="uml:PrimitiveType" xmi:id="_i7slcE0EEd-q5fB8PwONew" name="USPhoneNumberType">
   <eAnnotations xmi:id="_xj3IsE0EEd-q5fB8PwONew" source="http:///org/eclipse/emf/ecore/util/ExtendedMetaData">
    <details xmi:id="_BRrscE0FEd-q5fB8PwONew" key="name" value="USPhoneNumberType"/>
    <details xmi:id="_EECfUE0FEd-q5fB8PwONew" key="baseType" value="http://www.eclipse.org/emf/2003/XMLType#string"/>
    <details xmi:id="_GVdvYE0FEd-q5fB8PwONew" key="pattern" value="1?\W*([2-9][0-8][0-9])\W*([2-9][0-9]{2})\W*([0-9]{4})(\se?x?t?(\d*))?"/>
   </eAnnotations>
   <generalization xmi:id="_oZx-wE0EEd-q5fB8PwONew">
    <general xmi:type="uml:PrimitiveType" href="pathmap://UML_LIBRARIES/UMLPrimitiveTypes.library.uml#String"/>
   </generalization>
  </packagedElement>
  <profileApplication xmi:id="_NlBQ4E0GEd-q5fB8PwONew">
   <eAnnotations xmi:id="_NlBQ4U0GEd-q5fB8PwONew" source="http://www.eclipse.org/uml2/2.0.0/UML">
    <references xmi:type="ecore:EPackage" href="pathmap://UML_PROFILES/Ecore.profile.uml#_z1OFcHjqEdy8S4Cr8Rc_NA"/>
   </eAnnotations>
   <appliedProfile href="pathmap://UML_PROFILES/Ecore.profile.uml#_0"/>
  </profileApplication>
 </uml:Profile>
 <Ecore:EDataType xmi:id="_ZFV5cE0GEd-q5fB8PwONew" instanceClassName="java.lang.String" base_PrimitiveType="_i7slcE0EEd-q5fB8PwONew"/>
 <Ecore:EDataType xmi:id="_QE9r0E0LEd-q5fB8PwONew"/>
</xmi:XMI>



Load the Profile Programmatically:


Suppose we need to access our profile from another Eclipse plug-in to create Contacts programmatically. In a plugins parent directory, we will then create a new Java Plug-in project, here called timezra.blog.uml_regex_validation.resources, and it will provide access to this profile from its plug-in Activator.
We can specify metadata for the plug-in in our META-INF/MANIFEST.MF:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: UML Regex Validation Resources
Bundle-SymbolicName: timezra.blog.uml_regex_validation.resources
Bundle-Version: 1.0.0.qualifier
Bundle-Activator: timezra.blog.uml_regex_validation.resources.Activator
Bundle-Vendor: Timezra
Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.5.0,4.0.0)",
 org.eclipse.emf.ecore;bundle-version="[2.5.0,3.0.0)"
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Bundle-ActivationPolicy: lazy



We also want to create an additional source directory called profiles. We can put our Contact.profile.uml file in this directory so that it can be loaded from the classpath in our tests.

Our timezra.blog.uml_regex_validation.resources.Activator.java will handle loading the profile from a resource set:

package timezra.blog.uml_regex_validation.resources;

import org.eclipse.core.runtime.Plugin;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.osgi.framework.BundleContext;

public class Activator extends Plugin {

  public static final String PLUGIN_ID = "timezra.blog.uml_regex_validation.resources";

  private static Activator plugin;

  @Override
  public void start(final BundleContext context) throws Exception {
    super.start(context);
    plugin = this;
  }

  @Override
  public void stop(final BundleContext context) throws Exception {
    plugin = null;
    super.stop(context);
  }

  public static Activator getDefault() {
    return plugin;
  }

  public Resource loadProfile(final ResourceSet resourceSet) {
    return resourceSet.getResource(createURIFor("profiles/Contact.profile.uml"), true);
  }

  public URI createURIFor(final String resource) {
    return URI.createPlatformPluginURI(String.format("/%s/%s", PLUGIN_ID, resource), true);
  }
}



Test with a Fragment:


Now that we have a way to load the UML profile programmatically, we can create a test Fragment as a sibling to the functional bundle in the plugins directory.
We will call this Fragment timezra.blog.uml_regex_validation.resources.tests, and we will again declare plug-in metadata in the META-INF/MANIFEST.MF:

Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: UML Regex Validation Test
Bundle-SymbolicName: timezra.blog.uml_regex_validation.resources.tests;singleton:=true
Bundle-Version: 1.0.0.qualifier
Bundle-Vendor: Timezra
Fragment-Host: timezra.blog.uml_regex_validation.resources;bundle-version="[1.0.0,2.0.0)"
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Require-Bundle: org.junit4;bundle-version="[4.5.0,5.0.0)",
 org.eclipse.uml2.uml;bundle-version="[3.0.0,4.0.0)",
 org.eclipse.uml2.uml.resources;bundle-version="[3.0.0,4.0.0)",
 org.eclipse.uml2.uml.edit;bundle-version="[3.0.0,4.0.0)"



The test suite will present a few challenges. For example, suppose we want to run our tests outside OSGi. We will need a way to simulate plug-in activation so that a singleton instance of our Activator is available. We will also need to initialize the resource set used to load the profile, and to redirect requests for platform resources to some other mechanism. Let's dig in our heels and get started by creating a new JUnit file, here called timezra.blog.uml_regex.validation.resources.tests.ContactTest.java:

We can start the framework and activate the resources bundle before each test runs, and dispose of them after each test finishes:

package timezra.blog.uml_regex.validation.resources.tests;

import java.util.Collections;
import org.eclipse.osgi.launch.EquinoxFactory;
import org.junit.After;
import org.junit.Before;
import org.osgi.framework.BundleException;
import org.osgi.framework.launch.Framework;
import timezra.blog.uml_regex_validation.resources.Activator;

public class ContactTest {

  private Framework theFramework;
  private Activator thePlugin;

  @Before
  public void setUp() throws Exception {
    startTheFramework();
    startThePlugin();
  }

  @After
  public void tearDown() throws Exception {
    thePlugin.stop(theFramework.getBundleContext());
    theFramework.stop();
  }

  private void startThePlugin() throws Exception {
    thePlugin = new Activator();
    thePlugin.start(theFramework.getBundleContext());
  }

  private void startTheFramework() throws BundleException {
    theFramework = new EquinoxFactory().newFramework(Collections.emptyMap());
    theFramework.init();
  }
}



As well, we will initialize the resource set during setup;

....
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.resource.impl.URIHandlerImpl;
import org.eclipse.uml2.uml.UMLPackage;
import org.eclipse.uml2.uml.resource.UMLResource;

public class ContactTest {

  ....
  private ResourceSet theResourceSet;

  public void setUp() throws Exception {
    ....
    initializeTheResourceSet();
  }

  ....
  
  private void initializeTheResourceSet() {
    theResourceSet = new ResourceSetImpl();
    theResourceSet.getPackageRegistry().put(UMLPackage.eNS_URI, UMLPackage.eINSTANCE);
    theResourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(UMLResource.FILE_EXTENSION,
        UMLResource.Factory.INSTANCE);
    final Map<URI, URI> uriMap = theResourceSet.getURIConverter().getURIMap();
    final URI uri = URI.createURI("classpath:/");
    uriMap.put(URI.createURI(UMLResource.LIBRARIES_PATHMAP), uri.appendSegment("libraries").appendSegment(""));
    uriMap.put(URI.createURI(UMLResource.METAMODELS_PATHMAP), uri.appendSegment("metamodels").appendSegment(""));
    uriMap.put(URI.createURI(UMLResource.PROFILES_PATHMAP), uri.appendSegment("profiles").appendSegment(""));

    uriMap.put(thePlugin.createURIFor(""), uri);

  }
}



We can use a simple mechanism that redirects platform resource and UML-specific URI requests to the classpath for loading the standard profiles as well as our custom profile:

....
import java.io.IOException;
import java.io.InputStream;
import org.eclipse.emf.ecore.resource.impl.URIHandlerImpl;

public class ContactTest {

  ....
  private void initializeTheResourceSet() {
    ....
    theResourceSet.getURIConverter().getURIHandlers().add(0, new ClasspathURIHandler());
  }

  private static final class ClasspathURIHandler extends URIHandlerImpl {

    @Override
    public boolean canHandle(final URI uri) {
      return "classpath".equals(uri.scheme());
    }

    @Override
    public InputStream createInputStream(final URI uri, final Map<?, ?> options) throws IOException {
      return getClass().getResourceAsStream(uri.path());
    }
  }
}



We are now ready to write a few test cases. We can use the org.eclipse.emf.ecore.util.Diagnostician to perform validation for values set on the stereotype application itself or to check whether a value satisfies the stereotype attribute's restrictions. Our final class might look something like this:

package timezra.blog.uml_regex.validation.resources.tests;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Map;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.resource.impl.URIHandlerImpl;
import org.eclipse.emf.ecore.util.Diagnostician;
import org.eclipse.osgi.launch.EquinoxFactory;
import org.eclipse.uml2.uml.Actor;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.Profile;
import org.eclipse.uml2.uml.Stereotype;
import org.eclipse.uml2.uml.UMLFactory;
import org.eclipse.uml2.uml.UMLPackage;
import org.eclipse.uml2.uml.resource.UMLResource;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.osgi.framework.BundleException;
import org.osgi.framework.launch.Framework;
import timezra.blog.uml_regex_validation.resources.Activator;

public class ContactTest {

  private static final String STEREOTYPE_QUALIFIED_NAME = "ContactProfile::Contact";
  private static final String PHONE_NUMBER_ATTRIBUTE = "PhoneNumber";

  private static final String VALID_PHONE_NUMBER = "555-555-5555";
  private static final String INVALID_PHONE_NUMBER = "555-555";

  private Framework theFramework;
  private Activator thePlugin;
  private ResourceSet theResourceSet;

  private Profile theProfile;
  private Actor aContact;

  @Before
  public void setUp() throws Exception {
    startTheFramework();
    startThePlugin();
    initializeTheResourceSet();
    loadTheProfile();
    createAContact();
  }

  @After
  public void tearDown() throws Exception {
    thePlugin.stop(theFramework.getBundleContext());
    theFramework.stop();
  }

  @Test
  public void aValidPhoneNumberPassesTheDiagnostics() throws Exception {
    setThePhoneNumber(VALID_PHONE_NUMBER);

    assertEquals(Diagnostic.OK, diagnose(getTheStereotypeApplication()).getSeverity());
  }

  @Test
  public void anInvalidPhoneNumberFailsTheDiagnostics() throws Exception {
    setThePhoneNumber(INVALID_PHONE_NUMBER);

    assertEquals(Diagnostic.ERROR, diagnose(getTheStereotypeApplication()).getSeverity());
  }

  @Test
  public void theDataTypeRestrictsInvalidValues() throws Exception {
    assertEquals(Diagnostic.ERROR, diagnose(INVALID_PHONE_NUMBER).getSeverity());
  }

  @Test
  public void theDataTypeAllowsValidValues() throws Exception {
    assertEquals(Diagnostic.OK, diagnose(VALID_PHONE_NUMBER).getSeverity());
  }

  private EDataType getTheDataType() {
    return (EDataType) getTheStereotypeApplication().eClass().getEStructuralFeature(PHONE_NUMBER_ATTRIBUTE)
        .getEType();
  }

  private Diagnostic diagnose(final String thePhoneNumber) {
    return Diagnostician.INSTANCE.validate(getTheDataType(), thePhoneNumber);
  }

  private Diagnostic diagnose(final EObject theStereotypeApplication) {
    return Diagnostician.INSTANCE.validate(theStereotypeApplication);
  }

  private void setThePhoneNumber(final String thePhoneNumber) {
    aContact.setValue(getTheStereotype(), PHONE_NUMBER_ATTRIBUTE, thePhoneNumber);
  }

  private EObject getTheStereotypeApplication() {
    return aContact.getStereotypeApplication(getTheStereotype());
  }

  private Stereotype getTheStereotype() {
    return aContact.getAppliedStereotype(STEREOTYPE_QUALIFIED_NAME);
  }

  private void createAContact() {
    final Resource theResource = theResourceSet.createResource(URI.createURI("Contacts.uml"));
    final Package contacts = UMLFactory.eINSTANCE.createPackage();
    theResource.getContents().add(contacts);
    contacts.applyProfile(theProfile);
    aContact = UMLFactory.eINSTANCE.createActor();
    contacts.getPackagedElements().add(aContact);
    aContact.applyStereotype(aContact.getApplicableStereotype(STEREOTYPE_QUALIFIED_NAME));
  }

  private void loadTheProfile() {
    theProfile = (Profile) thePlugin.loadProfile(theResourceSet).getContents().get(0);
  }

  private void initializeTheResourceSet() {
    theResourceSet = new ResourceSetImpl();
    theResourceSet.getPackageRegistry().put(UMLPackage.eNS_URI, UMLPackage.eINSTANCE);
    theResourceSet.getResourceFactoryRegistry().getExtensionToFactoryMap().put(UMLResource.FILE_EXTENSION,
        UMLResource.Factory.INSTANCE);
    theResourceSet.getURIConverter().getURIHandlers().add(0, new ClasspathURIHandler());
    final Map<URI, URI> uriMap = theResourceSet.getURIConverter().getURIMap();
    final URI uri = URI.createURI("classpath:/");
    uriMap.put(URI.createURI(UMLResource.LIBRARIES_PATHMAP), uri.appendSegment("libraries").appendSegment(""));
    uriMap.put(URI.createURI(UMLResource.METAMODELS_PATHMAP), uri.appendSegment("metamodels").appendSegment(""));
    uriMap.put(URI.createURI(UMLResource.PROFILES_PATHMAP), uri.appendSegment("profiles").appendSegment(""));

    uriMap.put(thePlugin.createURIFor(""), uri);
  }

  private void startThePlugin() throws Exception {
    thePlugin = new Activator();
    thePlugin.start(theFramework.getBundleContext());
  }

  private void startTheFramework() throws BundleException {
    theFramework = new EquinoxFactory().newFramework(Collections.emptyMap());
    theFramework.init();
  }

  private static final class ClasspathURIHandler extends URIHandlerImpl {

    @Override
    public boolean canHandle(final URI uri) {
      return "classpath".equals(uri.scheme());
    }

    @Override
    public InputStream createInputStream(final URI uri, final Map<?, ?> options) throws IOException {
      return getClass().getResourceAsStream(uri.path());
    }
  }
}



Test With Tycho:


Suppose we want to automate our plug-in build and unit-tests. There are a few different options in the Eclipse eco-system, but my personal preference lately has been tycho. Currently, I am using tycho-0.8.0 and Maven 3-alpha-7 with Eclipse-3.5.2. Be aware that the latest version as of this first posting requires Maven 3-beta-1 and Eclipse-3.6-M7. My environment is configured according to the instructions provided by Mattias Holmqvist.

For our particular plugins, we can generate the poms for the functional resources plug-in, for the test fragment and for the parent plugins directory.

  export M2_HOME=/path/to/apache-maven-3.0-alpha-7
  export MAVEN_OPTS="-Xmx512m"
  export TYCHO_TARGET_PLATFORM=/path/to/eclipse_3.5.2
  cd /path/to/timezra.blog.uml_regex_validation.resources
  mvn org.sonatype.tycho:maven-tycho-plugin:generate-poms -DgroupId=timezra.blog -Dtycho.targetPlatform=$TYCHO_TARGET_PLATFORM
  cd /path/to/timezra.blog.uml_regex_validation.resources.test
  mvn org.sonatype.tycho:maven-tycho-plugin:generate-poms -DgroupId=timezra.blog -Dtycho.targetPlatform=$TYCHO_TARGET_PLATFORM
  cd ..
  mvn org.sonatype.tycho:maven-tycho-plugin:generate-poms -DgroupId=timezra.blog -Dtycho.targetPlatform=$TYCHO_TARGET_PLATFORM


We can modify the pom generated in our fragment to include the following section, which will enable tycho to run microtests outside OSGi:

<?xml version="1.0" encoding="UTF-8"?>
<project ....>
  ....
  <packaging>eclipse-test-plugin</packaging>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <executions>
          <execution>
            <id>test</id>
            <phase>test</phase>
            <configuration>
              <testClassesDirectory>${project.build.outputDirectory}</testClassesDirectory>
            </configuration>
            <goals>
              <goal>test</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.7</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
</project>



Finally we can run our unit-tests from the parent plugins directory:

  mvn clean test -Dtycho.targetPlatform=$TYCHO_TARGET_PLATFORM


Conclusion:


The primary purpose of this tutorial has been to present a way to test Stereotype attributes that use UML Data Types constrained by regular expressions. Along the way, we have also developed techniques for simulating bundle activation outside OSGi, for loading platform plugin resources and resources registered through Eclipse extensions without Eclipse running, and for running microtests with Maven via tycho.