lunes, 14 de septiembre de 2015

Atlas Transformation Language ATL

Atlas Transformation Language (ATL) es un lenguaje de transformación de modelos que posee un conjunto de herramientas libres (toolkit) para Eclipse. Desarrollado por la empresa OBEO y en el ámbito de MDE permite a los desarrolladores un medio para producir modelos destino a partir de un conjunto de modelos origen, a través de sus respectivos metamodelos.

En este artículo estudiaremos las características más relevantes de este lenguaje, el proceso de transformación de modelos definido en ATL, los módulos que soporta, los tipos de reglas de emparejamiento, métodos helpers y querys. Asi mismo se revisara la instalación de este framework en Eclipse Luna Modeling Tool.

Finalmente expondremos un tutorial paso a paso para realizar un proceso de transformación de clases UML a clases Java que lo denominaremos UML2Java.

Características de ATL


ATL es un lenguaje hibrido con programación declarativa e imperativa. En el estilo declarativo se expresar mapeos simples entre un elemento del modelo origen y su correspondiente elemento en el modelo destino. Sin embargo, ATL también provee constructores imperativos a fin de facilitar la especificación de mapeos que pueden expresarse con mucho trabajo declarativamente.

Un programa de transformación ATL está compuesto de reglas que definen emparejamientos de elementos del modelo origen, creando e instanciado elementos del modelo destino. Desarrollado bajo la plataforma Eclipse, el ATL Integrated Development Environment (ATL IDE) provee una seria de herramientas de desarrollo estándares (verificador de sintaxis, depurador, etc.) cuyo objetivo es el de diseñar transformaciones ATL de manera simple. El ambiente de desarrollo de ATL también ofrece un número de facilidades adicionales dedicadas al manejo de modelos y metamodelos.

El lenguaje ATL provee a los desarrolladores diseñar diferentes tipos de unidades ATL, una unidad ATL cualquiera sea su tipo se almacena en un único archivo con extensión "atl".

Las transformaciones de modelos en ATL, se las realiza a través de módulos, además el lenguaje ATL permite a los desarrolladores crear programas que desplieguen los datos primitivos de los modelos, estas unidades se denominan ATL queries. El objetivo de un query es el de computar un valor primitivo como un string o un entero desde los modelos origen. Finalmente el lenguaje ATL también ofrece la posibilidad de desarrollar librerías independientes que pueden ser importadas en diferentes tipos de unidades ATL, inclusive en las librerías mismas. Esto propiedad permite modularizar convenientemente el código ATL que se usa en múltiples unidades.

Proceso de transformación ATL

Para ilustrar el proceso de transformación ATL consideremos un pequeño ejemplo de transformación, donde se requiere transformar un metamodelo de Autores (Author) a un metamodelo de Personas (Person), la siguiente figura ilustra el modelado del ejemplo:
Metamodelo Autor y Persona

Como se observa, en ambos modelos se tiene el mismo número y nombre de propiedades (name, surname, gender y age). El objetivo será el de diseñar una transformación ATL que permita generar un modelo para Person a partir del modelo Author. La transformación se diseñara implementando las siguientes reglas semánticas:
  • Cada elemento Persona (Person) se genera a través de un elemento Autor (Author)
  • El nombre (name) de la Persona generada tiene que ser inicializada con el nombre del Autor
  • El apellido (surname) de la Persona generada tiene que ser inicializada con el apellido del Autor.
El proceso de transformación del modelo Author al modelo Person, se ilustra de mejor manera en el siguiente diagrama:

Proceso de transformación ATL de Author a Person.

La figura muestra un resumen de la transformación ATL (Author2Person) que permite generar el modelo Person, ajustándose al metamodelo MMPerson, desde el modelo Author que está conformada por el metamodelo MMAuthor. La transformación diseñada, el cual se expresa en el lenguaje ATL, se ajusta al metamodelo ATL. En este ejemplo, los tres metamodelos (MMAuthor, MMPerson y ATL) son expresados usando la semántica del metametamodelo Ecore.

Módulos ATL


Un módulo ATL corresponde a la transformación a realizarse de un modelo a otro. Este tipo de unidad permite a los desarrolladores especificar la forma de producir un conjunto de modelos destino a partir de un conjunto de modelos origen. Ambos modelos “origen” y “destino” de un módulo ATL deben corresponder a su respectivo metamodelo. Por otra parte, un módulo ATL acepta un número fijo de modelos de entrada, y retorna un número fijo de modelos destino.

La estructura de un módulo ATL está compuesta de los siguientes elementos:

  • Una sección de encabezado (header section) que define algunos atributos que serán utilizados en el módulo de transformación;
  • Una sección de importación opcional que permite importar librerías ATL.
  • Un conjunto de asistentes (helpers), que son equivalentes a métodos Java, que definen variables globales y/o funciones escritas en OCL;
  • Un conjunto de reglas que definen la manera como los modelos destino son generados desde los modelos fuente.

module module_name;
create output_models [from|refining] input_models;
uses library_file_name1;
uses library_file_name2;

helper [context context_type] def : helper_name(parameters):
return_type = exp;


rule Rule1{

}
rule Rule2{

}
Estructura genérica de un Módulo ATL.

Reglas

En ATL existen dos tipos de reglas que corresponden a los dos diferentes modos de programación (declarativo e imperativo): reglas de emparejamiento (“matched rules”) utilizadas en la programación declarativa y reglas de invocación (“called rules”) utilizadas en la programación imperativa.

Las reglas de emparejamiento constituyen el núcleo de las transformaciones declarativas en ATL ya que permite especificar desde que clase de elementos origen se generan los elementos destino y la manera en que los elementos destino generados deben ser inicializados. Una regla de emparejamiento se identifica por su nombre, se empareja a un determinado elemento de un modelo origen dado y genera una o más clases de elementos del modelo destino.

La definición de una regla de emparejamiento comprende un patrón origen y un patrón destino, un bloque imperativo opcional y un bloque para definición de variables también opcional. El patrón origen consiste de un conjunto de elementos, cada uno de ellos define una variable asociada al elemento del modelo local de un tipo dado (por ejemplo una metaclase). El conjunto de emparejamientos potenciales de la regla es el producto cartesiano de los conjuntos emparejados por cada elemento del patrón origen. Este conjunto puede ser opcionalmente restringido por una condición de guarda expresado en lenguaje OCL. Cada emparejamiento de la regla genera elementos destino por cada elemento origen. Para el ejemplo anterior, la siguiente regla de emparejamiento ATL puede transformar el metamodelo MMAuthor a MMPerson:


rule Author {
from
a : MMAuthor!Author
to
p : MMPerson!Person (
name<- a.name,
surname<- a.surname
)
}
Regla de emparejamiento ATL para transformar el metamodelo MMAuthor a MMPerson.

La sintaxis completa de una regla de emparejamiento se define de la siguiente forma:

rule Name
{
	using -- optional
	{
	variable : type = OCL-expression;
	...
	}
	from s : Source-Metamodel!Source-Metaclass [( OCL-expression )] -- optional guard
	[, ...]
	to t : Target-Metamodel!Target-Metaclass
	(
	target-meta-property<- OCL-expression [, ...]
	)
	[, ...]
	do -- optional
	{
	imperative part of the rule
	}
}
Sintaxis de una Regla de Emparejamiento ATL.

Las reglas de ejecución proveen a los desarrolladores facilidades para la programación imperativa. Estas reglas pueden verse también como un caso particular de helpers, deben ser explícitamente invocados para ser ejecutados y pueden aceptar parámetros, son muy similares a un procedimiento con parámetros de la programación tradicional. Sin embargo, en contraposición a los helpers, estas reglas pueden generar elementos del modelo destino, al igual que las reglas de emparejamiento lo hacen.  Una regla de ejecución puede ser llamada desde la sección de código imperativo de una regla de emparejamiento o de otra regla de ejecución.

Estas reglas son usadas frecuentemente para tratar con variables globales o para generar elementos de salida independientes desde el patrón de emparejamiento origen. En vez de usar patrones orígenes se debe definir una lista de parámetros, es por esta razón que deben ser invocados desde bloques imperativos de otras reglas.


rule NewPerson (na: String, s_na: String) {      
to 
   p : MMPerson!Person ( 
       name <- na 
   ) 
do { 
      p.surname <-s_na 
    } 
}
Ejemplo de Regla de Ejecución ATL.

Del anterior ejemplo se puede mencionar lo siguiente:
  • Permite generar un elemento destino de tipo Persona (“Person”)
  • Acepta dos parámetros que corresponden al nombre (“na”)  y al apellido (“s_na”) de la Persona
  • El patrón destino crea la clase Persona cada vez que la regla se ejecuta e inicializa el atributo “name” del elemento del modelo destino.
  • La sección del código imperativo se ejecuta después de la inicialización del elemento creado.

Helpers

ATL permite a los desarrolladores definir métodos entre las diferentes unidades ATL, a estos métodos se los conoce como métodos helpers. Una de sus ventajas en su uso, es que permiten factorizar (modularizar) el código ATL, los cuales pueden ser invocados desde diferentes puntos de un programa.

Existen dos tipos de helpers: helpers de tipo funcional que pueden aceptar parámetros y helpers de atributos. Ambos tipos de helpers deben ser definidos en el contexto de un tipo de datos específico.
La sintaxis general de un método helper es la siguiente:

helper [context context_type] def : helper_name(parameters) : return_type = exp;

Un ejemplo de un método helper sería el siguiente:


helper def : averageLowerThan(s : Sequence(Integer), value : Real) : Boolean =
	let avg : Real = s->sum()/s->size() 
in avg < value;
Ejemplo de método helper ATL.

Queries


Un query ATL consiste en una transformación a datos primitivos de un modelo. Puede verse como una operación que calcula un valor primitivo desde un conjunto del modelo origen. El uso más común de query’s ATL es la generación de salida textual (codificado en un valor de tipo String) desde el modelo fuente. Sin embargo, un query ATL no está limitado al cómputo de valores String y puede también retornar valores numéricos o booleanos. La estructura de un query ATL, comienza con una sección de importación opcional, luego se debe definir la instanciación del query. Un query se define usando la palabra reservada query y especifica la forma de cómo se calcula el resultado utilizando una expresión ATL, la sintaxis de un query es la siguiente:
query query_name = expresion_ATL;

El uso de métodos helpers es bastante recomendado para simplificar los procesos de cálculo de un query, además que ayuda a modularizar su contenido, el siguiente código muestra un ejemplo de un query ATL:


query XQuery2Code = XQuery!XQueryProgram.allInstances()->
collect(e | e.toString().writeTo('C:/test.xquery'));

helper context XQuery!XQueryProgram def: toString() : String =
self.expressions->iterate(e; acc : String = '' |
acc + e.toString() + '\n');

helper context XQuery!ReturnXPath def: toString() : String =
'{' + self.value + '}';

helper context XQuery!BooleanExp def: toString() : String =
self.value;
Ejemplo de query ATL.

Librerías ATL


Las librerías ATL permiten definir un conjunto de métodos helpers que pueden ser invocados desde otras unidades ATL.
 
Como otro tipo de unidades ATL, una librería puede incluir opcionalmente una sección de importación, debajo de dicha sección, luego se definen los métodos helpers que estarán habilitados para su uso en las unidades ATL que importaran la librería.

Una librería ATL no puede ejecutarse independientemente, ni tampoco puede definirse un elemento por defecto. Un ejemplo de una librería ATL seria el siguiente:


library Strings;

helper context String def : firstToUpper : String =
	self.substring(1, 1).toUpper() + self.substring(2, self.size());

helper context String def : startsWith(begin : String) : Boolean =
	if (self.size() < begin.size()) then
		false
	else
		self.substring(1, begin.size()) = begin
	endif;

helper context String def : endsWith(end : String) : Boolean =
	if (self.size() < end.size()) then
		false
	else
		self.substring(self.size() - end.size() + 1, self.size()) = end
	endif;
Ejemplo de librería ATL.

Instalación del Plug-in ATL para Eclipse

La instalación del Plug-in para Eclipse de ATL, puede realizarse mediante la opción Help Install Modeling Components, de la ventana Eclipse Modeling Components Discovery seleccione la opción ATL y presione “Finish”.

Luego siga los pasos de la instalación por defecto, hasta que el plug-in se instale completamente en su IDE y el sistema le pida reiniciarlo.

Proyecto de Transformación ATL UML2Java

El proyecto de Transformación ATL UML2Java, describe la transformación de un modelo UML a un modelo Java. El modelo Java permite almacenar información de clases Java, especialmente en lo que concierne a la estructura de las clases, el paquete de referencia de la clase, sus atributos y sus métodos.

Metamodelo de Clases Java

En este tutorial consideramos al metamodelo de clases Java como  metamodelo Destino que consiste principalmente en elementos de Java (JavaElements) que tienen un nombre (name). Una clase Java (JavaClass) tiene métodos (Methods) y atributos (Fields) que pertenecen a un paquete (Package).  Los métodos, atributos y clases Java son subclases de modificadores Java (Modifiers) que indican si son elementos públicos, estáticos o finales (public, static, final). Las clases metodos pueden contener un conjunto de parámetros (MethodParameter) de un tipo indicado. Las clases Java y los métodos se declaran con el atributo isAbstract que permite definir si son abstractos o no.  Las clases Java y la clase PrimitiveTypes son del tipo Type. Por otro lado un método define un tipo Type de retorno y sus parámetros son del tipo Type, un atributo también es del tipo Type. Finalmente en un paquete pueden definirse un conjunto de tipos Enumerativos Java (Enumeration) que poseen un grupo de literales (EnumerationLiteral) que son del tipo JavaElement.

Puede recurrir al artículo “Creación de Meta Modelos con EMF y Ecore”, para la creación paso a paso del proyecto EMF de un metamodelo.

La siguiente figura ilustra el metamodelo Java (el archivo Ecore puede descargarlo de aquí).

Metamodelo UML

Este metamodelo se lo considera como metamodelo Origen y esta descrito íntegramente en OMG, para este tutorial solo ilustramos una porción limitada del mismo.

Creación del Proyecto ATL

El primer paso es la creación de un proyecto de Transformación ATL en el espacio de trabajo de Eclipse (Workspace). En el IDE seleccione las opciones “File” → “New” → “Other…” y elija “ATL → ATL Project”.

Presione “Next”, ingrese el nombre del proyecto “UML2Java”, y luego presione “Finish”:


El proyecto ATL se creara vacío, en el mismo crearemos los siguientes folders para estructurar de mejor manera el proyecto:
  • config: almacenara archivos de librerías ATL
  • models: almacenera los modelos UML de ejemplo que se crearan posteriormente para ser transformados
  • metamodels: en este folder se guardaran los metamodelos origen y destino para las transformaciones ATL.
  • profile: folder que almacenara perfiles UML siempre y cuando nuestros metamodelos tengan adicionados esta característica.
  • run: folder que almacenara archivos XMI relacionados a los metamodelos destino obtenidos por la ejecución de las transformaciones ATL.
  • transformations: almacenara los módulos de transformación ATL.
  • src: almacenara las clases Java serializadas en archivos desde el modelo destino generado, por módulos query de ATL.

La parte fundamental del proyecto es la creación de la transformación ATL.  Presione click derecho en el folder transformations y luego seleccione las opciones  “New” → “Other…” → “ATL → ATL File” → “Next”, luego ingrese el nombre del archivo ATL “UML2Java.atl”.


A continuación, se deberán especificar los metamodelos de entrada, salida y módulos de librerías (opcional) necesarias para ejecutar la transformación.


Para definir el metamodelo origen, presione el botón “Add…” de la sección “Input Models”, ingrese en el campo “Metamodel Name” el valor “UML” y en el campo “Resource URI” el valor “http://www.eclipse.org/uml2/5.0.0/UML”, puede utilizar la opción “Browse Registered Packages” para buscar el metamodelo UML de los metamodelos registrados en el IDE Eclipse, luego presione el botón “Ok”.


Para definir el metamodelo destino, presione el botón “Add…” de la sección “Output Models”, ingrese en el campo “Metamodel Name” el valor “Java” y en el campo “Resource URI” el valor “platform:/resource/JavaMetamodel/model/Java.ecore”, puede utilizar la opción “Browse Workspace” para elegir desde espacio de trabajo de Eclipse el archivo Ecore del metamodelo Java, luego presione el botón “Ok”.


Una vez definidos los metamodelos presione el botón “Finish”, el sistema le preguntara si desea abrir la perspectiva ATL en este momento, elija la opción “Yes”, Eclipse abrirá el editor del módulo de transformación ATL, con la configuración de los metamodelos UML y Java.

Reglas de Transformación UML2Java

Las reglas de transformación para UML2Java se pueden resumir en las siguientes:
  • Para cada instancia UML Package, se creara una instancia Java Package. El nombre del paquete UML corresponderá con el de Java, sin embargo el nombre del paquete Java contendrá la ruta completa de la información separada por puntos.
  • Para cada instancia UML Class, se creara una instancia Java Class, donde los nombres de las clases se corresponderán uno a otro, la referencia Package se corresponderá uno a otro, los Modificadores de Clase se corresponderán uno a otro.
  • Para cada instancia UML Data Type, se creara una instancia Java Primitive Type, donde el nombre y el paquete de referencia se corresponderán uno a otro.
  • Para cada instancia UML Attribute, se creara una instancia Java Field, donde los nombres, los tipos, modificadores de clase y clases se corresponderán uno a otro.
  • Para cada instancia UML Operation, se creara una instancia Java Method, donde los nombres, los tipos, modificadores de clase y clases se corresponderán uno a otro.
  • Para cada instancia UML Enumeration se creara una instancia Java Enumeration, donde los nombres y los literales del Enumerador se corresponderán uno a otro.
Cabe mencionar que se añadieron al programa ATL métodos Helpers de ayuda, así como también una lista de datos primitivos Java que será utilizada para corresponder un tipo de dato primitivo de UML (como ser: String, Integer y Boolean) al tipo de dato primitivo de Java correspondiente.

Código ATL

El código ATL para esta transformación es el siguiente, copie y pegue el mismo al editor ATL o puede descargarlo de aquí.

-- @nsURI UML=http://www.eclipse.org/uml2/5.0.0/UML
-- @path Java=/JavaMetamodel/model/Java.ecore

module UML2Java;
create OUT : Java from IN : UML;

helper def : primitiveTypesMap : Map(String,Java!PrimitiveType) = Map{};

helper context UML!Namespace def: getExtendedName() : String =
	if self.namespace.oclIsUndefined() then
		''
	else if self.namespace.oclIsKindOf(UML!Model) then
		''
	else
		self.namespace.getExtendedName() + '.'
	endif endif + self.name;

helper context UML!Type def: getType() : Java!Type =
    if self.oclIsUndefined() 
	then OclUndefined
	else 
		if self.oclIsKindOf(UML!PrimitiveType)
		then thisModule.primitiveTypesMap->get(self.name) 
		else self
		endif
	endif;

rule P2P {
	from e : UML!Package (e.oclIsTypeOf(UML!Package))
	to out : Java!Package (
		name <- e.getExtendedName()
	)
}

rule C2C {
	from e : UML!Class
	to out : Java!JavaClass (
		name <- e.name,
		isAbstract <- e.isAbstract,
		isPublic <- if e.visibility='public' then true else false endif,
		package <- e.namespace,
		superClasses <- e.generalization->collect( g | g.general )
	)
}

rule D2P {
	from e : UML!DataType
	to out : Java!PrimitiveType (
		name <- e.name,
		package <- e.namespace
	)
}

rule T2T {
	from e : UML!Type(e.oclIsKindOf(UML!PrimitiveType))
	to out : Java!PrimitiveType (
		name <- e.name,
		package <- e.namespace
	)
}

rule A2F {
	from e : UML!Property
	to out : Java!Field (
		name <- e.name,
		isStatic <- e.isStatic,
		isPublic <- if e.visibility=#public then true else false endif,
		--isFinal <- e.isFinal(),
		owner <- e.owner,
		type <- if e.type.oclIsUndefined() then OclUndefined else e.type.getType() endif 
	)
}

rule O2M {
	from e : UML!Operation
	to out : Java!Method (
		name <- e.name,
		isStatic <- e.isStatic,
		isPublic <- if e.visibility=#public then true else false endif,
		owner <- e.owner,
		type <- if e.ownedParameter->select(x|x.direction=#return)->first().oclIsUndefined() 
		        then OclUndefined 
				else e.ownedParameter->select(x|x.direction=#return)->first().type.getType()
				endif,
		parameters <- e.ownedParameter->select(x|x.direction<>#return)->collect( par | thisModule.PM2JP( par ) )
	)
}

rule PM2JP(p : UML!Parameter)
{
    to out : Java!MethodParameter
    (
         name <- p.name,
		 owner <- p.owner,
		 type <- if p.type.oclIsUndefined() then OclUndefined else p.type.getType() endif 
    ) do {
		out;
	}
}

rule E2E
{
    from e : UML!Enumeration ( UML!Property.allInstances()->exists( p |
        p.class.oclIsTypeOf( UML!Class ) and p.type = e ) )
    to je : Java!Enumeration
    (
        name <- e.name,
        package <- e.package,
        enumerationLiterals <- e.ownedLiteral->collect( el | thisModule.EL2EL(el))
    )
}

rule EL2EL(el : UML!EnumerationLiteral)
{
    to out : Java!EnumerationLiteral
    (
        name <- el.name
    ) do {
		out;
	}
}

rule makeType(type : UML!Type) {
	to t : Java!Type (
		name <- type.name
	) do {
		t;
	}
}

entrypoint rule Init() {
	to s : Java!PrimitiveType (
		name <- 'String'
	),
	i : Java!PrimitiveType(
		name <- 'Integer'
	),
	b : Java!PrimitiveType(
		name <- 'Boolean'
	)
	do {
		thisModule.primitiveTypesMap <- thisModule.primitiveTypesMap->including(s.name,s)->including(i.name,i)->including(b.name,b);
		s;
		i;
		b;
	}
}

Modelo UML Origen

A continuación se deberá diseñar un o más modelos de Clases UML “Origen”, los mismos pueden ser diseñados con una herramienta de modelado UML, algunas herramientas sugeridas son las siguientes:
  • Papyrus: esta es una herramienta de software libre basada en UML 2 y Eclipse, que provee soporte para DSL, UML2 y SysML, posee un plug-in para su instalación en el IDE Eclipse.
  • Magic Draw, es una herramienta comercial con muy buenas prestaciones para el modelado de sistemas con UML, SysML, BPMN y UPDM.
Una vez diseñado el modelo Origen, que generalmente se crea en un archivo  con extensión UML y en formato XMI, cópielo al folder “models” del proyecto ATL. Para nuestro ejemplo se creó un modelo denominado "Personas.uml" con las siguientes clases:

Puede descargar el archivo “Personas.uml” de aquí.

Ejecución del Módulo de Transformación ATL

Para ejecutar el módulo de transformación ATL por primera vez, deberemos configurar su entorno de ejecución, para realizar esta tarea presione click-derecho sobre el archivo UML2Java.atl, seleccione: "Run As → Run Configutarions…"



En la pantalla “Run Configurations” y en la sección “Source Models” ingrese el valor “/UML2Java/models/Personas.uml”, puede utilizar el botón “Workspace…” para recuperar el modelo origen del folder models. Luego en la sección “Target Models” ingrese el valor “/UML2Java/run/Personas.xmi”  que corresponde al archivo del modelo Destino, en nuestro caso ingresamos el nombre de “Personas.xmi”, luego presione el botón “Apply” y posteriormente ejecute la transformación presionando el botón “Run”.


Si todo salió bien la ejecución deberá generar el archivo “Personas.xmi” en el folder “run”, de lo contrario la pestaña “Console”, mostrara los errores de ejecución del módulo ATL.


Para abrir por primera vez el archivo XMI generado con el editor EMF por defecto, previamente deberemos registrar el metamodelo Java, para realizar esto seleccione el archivo “Java.ecore”, presione click-derecho y seleccione la opción “Register Metamodel”. Finalmente presione doble-click en el archivo “Personas.xmi” y el mismo debería lucir de la siguiente manera:


Puede descargar el archivo "Personas.xmi" de aqui y el proyecto UML2Java completo de aquí.

Resumen

En este artículo se explicó al lector las características principales del Atlas Transformation Language (ATL), se mostró cómo se lleva a cabo el proceso de transformación entre modelos y de manera práctica se hizo un tutorial paso a paso para transformar diagramas de Clase UML a Clases Java. Sin embargo puede que la transformación no cubra todas las expectativas de los lectores, cualquier mejora es bienvenida.

Algunos recursos recomendados son:
En el próximo artículo veremos ATL Query y la serialización de los modelos a archivos, hasta la próxima….

No hay comentarios:

Publicar un comentario