Skip to content

rALF API Usage

lunkpeter edited this page Sep 29, 2015 · 9 revisions

#rALF API Usage

Introduction

The EMDW-MC project uses a simplified version of the ALF language to define the dynamic behavior of xtUML models. The rALF parser and generator API can be used to parse these action code segments, and generate C++ code fragments based off of them.

The usage of these API classes, similar to Xtext, relies on the Google Guice dependency injection framework. This framework is used to define the UML model used as context for resolution; the basic level API aims at hiding this structure.

rALF API classes

Tha rALF API at this stage, consists of two main main interfaces. Each one of these interfaces has one reference implementation. Naturally, custom implementations can be defined by the user.

IReducedAlfParser

The IReducedAlfParser interface enables the user to create an ALF AST (Abstract Syntax Tree) based on a given ALF code segment, or behavioral unit containing the aforementioned code segment. The reference implementation (ReducedAlfParser.class) creates a synthetic resource for the rALF AST, containing the rALF AST. This approach is similar to the way Xtext based editors parse domain specific languages.

public interface IReducedAlfParser {

	static final String LANGUAGE_NAME = "rALF";

	/**
	 * Creates a rALF AST based on the specified rALF code. This AST cannot
	 * refer to UML types except the primitive types.
	 * 
	 * @param behavior
	 * @return
	 */
	ParsingResults parse(String behavior);

	/**
	 * Creates a rALF AST based on the specified rALF code and an UML context
	 * provider
	 * 
	 * @param behavior
	 * @param contextProvider
	 * @return
	 */
	ParsingResults parse(String behavior, IUMLContextProvider contextProvider);

	/**
	 * Extracts the rALF code from a specified OpaqueBehavior instance, and
	 * creates the corresponding rALF AST. The UML context provider is
	 * initialized based on the behavior parameter. It will use an
	 * IncQuery-based context provider using the managed engine on the
	 * resourceset of the behavior
	 * 
	 * @param behavior
	 * @return
	 */
	ParsingResults parse(OpaqueBehavior behavior);

	/**
	 * Extracts the rALF code from a specified OpaqueBehavior instance, and
	 * creates the corresponding rALF AST. The UML context provider is
	 * initialized based on the behavior parameter.
	 * 
	 * @param behavior
	 * @param engine
	 *            a shared IncQueryEngine instance
	 * @return
	 */
	ParsingResults parse(OpaqueBehavior behavior, IncQueryEngine engine);

	/**
	 * Extracts the rALF code from a specified OpaqueExpression instance, and
	 * creates the corresponding rALF AST. The UML context provider is
	 * initialized based on the behavior parameter.
	 * 
	 * @param expression
	 * @param engine
	 *            a shared IncQueryEngine instance
	 * @return
	 */
	ParsingResults parse(OpaqueExpression expression, IncQueryEngine engine);

}

The recommended parse method to use is the third one, that receives directly an opaque behavior. The other two approaches are mostly for parser testing.

The returned ParsingResults class stores both the created AST (if it could be created), and a set of diagnostics (errors, warnings).

IReducedAlfGenerator

The IReducedAlfGenerator interface provides support for the creation of C++ code snippets based on rALF ASTs or AST fragments. It also has methods for generating C++ snippets from rALF snippets in one step, using a specified IReducedALFParser instance.

public interface IReducedAlfGenerator {
    
    /**
     * Creates a C++ snippet based on the defined rALF code using the provided rALF parser.
     * @param behavior String containing the rALF code
     * @param parser Parser used for parsing the rALF code
     * @return
     */
    Snippet createSnippet(String behavior, IUMLContextProvider contextProvider, IReducedAlfParser parser, ReducedAlfSnippetTemplateCompiler templateCompiler) throws SnippetCompilerException;
    
    /**
     * Creates a C++ snippet based on the the rALF code, which is contained by the specified opaque behavior 
     * using the provided rALF parser.
     * @param behavior Opaque behavior containing the rALF code
     * @param parser Parser used for parsing the rALF code
     * @return
     */
    Snippet createSnippet(OpaqueBehavior behavior, IUMLContextProvider contextProvider, IReducedAlfParser parser, ReducedAlfSnippetTemplateCompiler templateCompiler) throws SnippetCompilerException;
    
    /**
     * Creates a C++ snippet based on a given rALF AST.
     */
    Snippet createSnippet(ParsingResults result, IUMLContextProvider contextProvider, ReducedAlfSnippetTemplateCompiler templateCompiler) throws SnippetCompilerException;
}

Examples

During the usage of the rALF API the following main steps need to be undertaken:

  • Define an UML Context Provider, that is used to connect the rALF action code fragment being processed, to a UML model instance.
  • Create API class instances
  • Use the API classes.

The usage of this programming interface, is demonstrated via two example JUnit tests located in the following project: Example test location. Each one of these unit tests aims to show different scenarios the API can be used in.

  • JUnitPrimitiveTypeExampleTest: This basic JUnit test shows the usage of the API, without connecting it to an existing UML model. Because of this, only primitive types (Integer, Real, String, Boolean) can be used.
  • PluginUMLTypeExampleTest: This JUnit Plug-In test uses UML types, classes and signals specified by an example uml model located in its /model folder.
  • PluginThisExampleTest: This JUnit Plug-In test uses UML types, and the fully qualified name of the Operation to which the action code snippet belongs can be specified. This way 'this' expressions can be used.

Usage with Primitive Types Only

At first, a dummy UML context provider needs to be implemented:

/**
 * Simplified UML context, where only primitive types are available from UML model.
 */
@Singleton
class SimpleUMLContextProvider extends AbstractUMLContextProvider {

    val Resource containerResource
    
    new() {
        val set = new ResourceSetImpl()
        UMLResourcesUtil.init(set)
        containerResource = set.getResource(URI.createURI(UMLResource.UML_PRIMITIVE_TYPES_LIBRARY_URI), true)
    }

    override getPrimitivePackage() {
        EcoreUtil.getObjectByType(containerResource.getContents(), UMLPackage.Literals.PACKAGE) as Package
    }
    

    override getPropertiesOfClass(Classifier cl) {
        return newArrayList
    }

    override getAssociationsOfClass(Classifier cl) {
        return newArrayList
    }
    
    override getOperationsOfClass(Classifier cl) {
        return newArrayList
    }
    
    override getStaticOperations() {
        return newArrayList
    }

    override getKnownTypes() {
        containerResource.allContents.filter(typeof(Type)).toSet
    }

    override getKnownClassesSet() {
        containerResource.allContents.filter(typeof(Class)).toSet
    }

    override getKnownSignals() {
        containerResource.allContents.filter(typeof(Signal)).toSet
    }

    override getKnownAssociations() {
        containerResource.allContents.filter(typeof(Association)).toSet
    }
    
    override protected getContextObject() {
        null
    }
    
    override getDefinedOperation() {
        null
    }
    
    override getOperationCandidatesOfClass(Classifier cl, String name) {
        newHashSet()
    }
    
    override getConstructorsOfClass(Class cl) {
        newHashSet()
    }
    
    override getLibraryOperations() {
        newArrayList()
    }
    
    
}

Usage in Test Cases

Once the context provider is implemented, it is time to use the API classes. As shown in the example test case below. Instantiate the parser and generator classes, then use the API to parse a rALF code fragment, and the generator to generate the C++ code snippets. The test case below is a parameterized snippet compiler test which can determine whether the created c++ snippet is applicable to the input action code.

@RunWith(Parameterized)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
class AbstractUnitTest {
	@Parameter(0)
    public String name;

    @Parameter(1)
    public String input;
    
    @Parameter(2)
    public String expectedOutput;
    
	@BeforeClass
	def static void init(){
		if (!Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().containsKey("ecore"))
            Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("ecore",
                    new EcoreResourceFactoryImpl());
        if (!Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().containsKey("xmi"))
            Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xmi",
                    new XMIResourceFactoryImpl());
        if (!Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().containsKey("xtextbin"))
            Resource.Factory.Registry.INSTANCE.getExtensionToFactoryMap().put("xtextbin",
                    new BinaryGrammarResourceFactoryImpl());
        if (!EPackage.Registry.INSTANCE.containsKey(XtextPackage.eNS_URI))
            EPackage.Registry.INSTANCE.put(XtextPackage.eNS_URI,
                    XtextPackage.eINSTANCE);
	}
	     
    @Test 
    def void t01_createSnippet() {
    	//Initiate components
    	//Create parser
    	val parser = new ReducedAlfParser
    	//Create uml context provider
    	//It is responsible for supplying the primitive and user defined UML types
	    val context = new SimpleUMLContextProvider()
	    //Snippet compiler that creates a snippet template tree based on the parsed code
	    //It needs an UML valueDescriptor factory which is used for determining which CPP element can be traced 
	    //back to what UML element
	    val compiler = new ReducedAlfSnippetTemplateCompiler(new DummyUmlValueDescriptorFactory())
	    //Serializer component
	    val serializer = new ReducedAlfSnippetTemplateSerializer
	    //API class
	    val generator = new ReducedAlfGenerator
	    
	    //Parse the action code
       	val result = parser.parse(input, context)
       	//Create the snippet template based on the parsed abstract syntax tree
       	val snippet = generator.createSnippet(result, context, compiler)
       	//Create the snippet code based on the snippet template
       	val serializedSnippet = serializer.serialize(snippet)
       	//compare results
    	assertEquals("The created snippet does not match the expected result",expectedOutput,serializedSnippet)
	
    }
}

Usage with UML models

Context Provider

In this case, a more complex UML context provider is created, that loads the model located at a given path and establishes a connection between it and the input rALF snippet.

/**
 * This context provider loads an UML model from a .uml file, returns the known classes, 
 * types, signals of a given UML model. Also provides an interface for querying the 
 * associations and properties of a given class
 * 
 */
 @Singleton
class TestModelUMLContextProvider extends UMLContextProvider {

    var Model model
    var Operation definedOperation
    val ResourceSet resourceSet

    new(String location) {
        resourceSet = new ResourceSetImpl
        resourceSet.getResource(URI.createURI(UMLResource.UML_PRIMITIVE_TYPES_LIBRARY_URI),
                true) => [
                    load(#{})    
                ]
        val resource = resourceSet.createResource(URI.createPlatformPluginURI(location, true)) => [
            load(#{})
        ]
        model =  resource.allContents.filter(typeof(Model)).findFirst[true]
    }
    
    public def setDefinedOperation(String elementFQN) {
        definedOperation = model.allOwnedElements.filter(Operation)
           .findFirst[qualifiedName == elementFQN]
    }
    
    override protected getContextObject() {
        definedOperation
    }
    
    override protected doGetEngine() {
        IncQueryEngine.on(new EMFScope(resourceSet));
    }
    
    override getDefinedOperation() {
        definedOperation
    }
    
}

Usage in Test Cases

The usage of these classes in the test cases is similar to the previous case. This time however, the model location is handed to the new UML context provider.

if a 'this' operator is used, we need to specify to which UML element the action code to be parsed belongs to. This can be done e.g., via handing the fully qualified name (FQN) of the given element to the uml context provider (as in case of test projects).

#####Plugin tests without 'this' expressions

@RunWith(Parameterized)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
abstract class AbstractPluginTest {
    @Parameter(0)
    public String name;

    @Parameter(1)
    public String input;
    
    @Parameter(2)
    public String expectedOutput;
    
    @Parameter(3)
    public String thisName="";
		

    @Test 
    def void t01_createSnippet() {
    	//Initiate components
    	//Create parser
    	val parser = new ReducedAlfParser
    	//Create uml context provider
    	//It is responsible for supplying the primitive and user defined UML types
    	//in this case th UML model is loaded from an external resource
    	//Its path needs to be specified here
	    val context =  new TestModelUMLContextProvider("/com.incquerylabs.uml.ralf.tests.examples/model/model.uml");
	    //Snippet compiler that creates a snippet template tree based on the parsed code
	    //It needs an UML valueDescriptor factory which is used for determining which CPP element can be traced 
	    //back to what UML element
	    val compiler = new ReducedAlfSnippetTemplateCompiler(new DummyUmlValueDescriptorFactory())
	    //Serializer component
	    val serializer = new ReducedAlfSnippetTemplateSerializer
	    //API class
	    val generator = new ReducedAlfGenerator
	    
	    //As in this test case there is no editor attached to the UML model, the qualified name of the current type needs to be specified.
	    if(!thisName.equals("")){
    		context.definedOperation = thisName
    	}
    	
    	//Parse the action code
       	val result = parser.parse(input, context)
       	//Create the snippet template based on the parsed abstract syntax tree
       	val snippet = generator.createSnippet(result, context, compiler)
       	//Create the snippet code based on the snippet template
       	val serializedSnippet = serializer.serialize(snippet)
       	//compare results
    	assertEquals("The created snippet does not match the expected result",expectedOutput,serializedSnippet)
	
    }
}

#####With 'this' expression

class PluginThisExampleTest extends AbstractPluginTest{
	@Parameters(name = "{0}")
	def static Collection<Object[]> testData() {
		newArrayList(
			#[  "Plug-In Test: Send signal using the 'this' object",
				//This example test case uses the model of the Ping-Pong example. 
				//It parses the action code describing a ping signal being sent to the "ping" attribute (association end) of the current object.
			    '''send new ping_s() to this->ping;''',
				'''this->ping->generate_event(new model::Comp::Pong::ping_s());''',
				//As in this test case there is no editor attached to the UML model, the qualified name of the current operation needs to be specified.
				//Hand the name of the current type to the context provider
				"model::Comp::Pong::doIntegerVoid"
			]
		)
	}
	
	@Test
	def void testType() {
	           //Initiate components
        val parser = new ReducedAlfParser
        val context =  new TestModelUMLContextProvider("/com.incquerylabs.uml.ralf.tests.examples/model/model.uml");
        val result = parser.parse(input, context)
        val signalExpression = (result.model.statement.get(0) as SendSignalStatement).signal
        // The typesystem can calculate the type of any expression
        // The returned result might hold information about type errors
        // Finally, an internal IUMLTypeReference is used to be able to store extra information, e.g. about collections
        val signalType = result.typeSystem.type(signalExpression).value.umlType
        assertEquals("ping_s", signalType.name)
	}
}

Project Locations

Clone this wiki locally