Download Máster MNTI Desarrollo Dirigido por Modelos Seminario 4
Document related concepts
no text concepts found
Transcript
Máster MNTI Desarrollo Dirigido por Modelos Seminario 4 - Transformaciones modelo a código Grupo Modelum Universidad de Murcia 19 de enero de 2011 Resumen En esta sesión se abordará la fase de generación de código utilizando transformaciones modelo a código definidas en MOFScript. MOFScript es un lenguaje de plantillas basado en reglas que permite generar código a partir de modelos conformes a metamodelos. Su simplificidad y facilidad para definir plantillas lo convierte en un lenguaje comprensible y muy potente. Los ejercicios de este seminario tendrán como objetivo generar código a partir de los modelos que han resultado de la transformación modelo a modelo de los ejercicios del seminario anterior. 1 1. Introducción En la sesión anterior se aplicaron una serie de transformaciones modelo a modelo para obtener un modelo intermedio que representa una pequeña arquitectura para gestionar menús a partir del modelo de sintaxis abstracta del DSL. El objetivo de esta sesión es generar el código necesario para la incorporación de un menú en una aplicación Swing a partir del modelo intermedio de arquitectura generado. Para llevar a cabo la generación de código, se utilizará el lenguaje de transformación MOFScript, el cual es un lenguaje basado en reglas integrado en la plataforma Eclipse. Una transformación MOFScript está compuesta por un conjunto de reglas que definen las secciones de código a generar a partir de los elementos del modelo origen. Estas secciones a generar actúan como plantillas donde se inserta determinada información para completarlas, por este motivo, MOFScript también recibe el nombre de lenguaje de plantillas. Durante el desarrollo de este guión se presentará el entorno de programación en MOFScript y se desarrollará la transformación necesaria. En el Anexo A puede encontrarse un resumen de las principales sentencias de MOFScript. 2. Organización del proyecto para el DSL Dado que anteriormente hemos utilizado transformaciones modelo a modelo, se dispone de un proyecto en Eclipse con la estructura de un proyecto tı́pico de la plataforma AGE. De esta forma se creará una carpeta llamada m2c dentro de la carpeta transformations (si no existe ya). Esta será nuestra carpeta de trabajo, todas las transformaciones MOFScript que se realicen se guardarán en su interior. Durante el desarrollo de este guión se generará una aplicación Java Swing que será la que incorporará el menú correspondiente a cada perfil. Por ello es necesario crear un proyecto Java donde se almacenarán los ficheros generados como resultado de la ejecución de la transformación MOFScript. Al integrar la generación del código en un proyecto Java de Eclipse se puede utilizar el sistema de compilación automática de la plataforma ası́ como su entorno de edición y depuración. Figura 1: Estructura de las carpetas del proyecto 3. Configuración del entorno MOFScript está incorporado a la plataforma Eclipse como un plugin que ofrece un editor para transformaciones MOFScript, un motor para la ejecución de transformaciones y una hoja de configuración en el menú de preferencias de Eclipse. Antes de comenzar a definir y ejecutar transformaciones MOFScript, es conveniente comprobar que la información contenida en la hoja de configuración es correcta. La hoja de configuración que ofrece MOFScript puede observarse en la Figura 2. El campo principal a comprobar es el primero, el cual indica la localización de los metamodelos que se 2 utilizarán en la definición de las transformaciones. De todas formas, es conveniente comprobar que los demás campos contienen información correcta para evitar problemas colaterales. Para ello, se debe hacer click en cada campo y comprobar si existe mensaje de error. Figura 2: Hoja de configuración de MOFScript Una cuestión importante a destacar es el hecho de que si trabajamos con metamodelos almacenados en diferentes partes del disco deberı́amos cambiar este campo de configuración continuamente. La solución a este problema es utilizar el registro de metamodelos ecore en la plataforma Eclipse. Con este método, podemos registrar un metamodelo y las transformaciones que utilicen dicho metamodelo funcionarán correctamente sin la necesidad de configurar el plugin en la hoja de configuración. La configuración del directorio donde se almacenarán los ficheros generados como resultado de una transformación MOFScript se debe realizar individualmente para cada fichero m2t. La Figura 3 muestra la hoja de propiedades asociada a cada fichero m2t. Figura 3: Propiedades MOFScript para un fichero m2t 3.0.1. Resumen de comandos Windows → Preferences → MOFScript Preferences Abrir la hoja de configuración del plugin MOFScript Click der. (fichero Ecore) → Register metamodel Registrar metamodelo 3 Click der. (fichero m2t) → Properties → MOFScript properties Configurar propiedades MOFScript para un fichero m2t 3.1. Conocimiento del dominio de la arquitectura destino La transformación MOFScript que se desarrollará en este seminario generará el código necesario para incluir un menú en una aplicación Swing a partir de una instancia del metamodelo de la arquitectura. En primer lugar, se debe conocer el dominio donde se generará el código. Swing es una librerı́a gráfica en Java que forma parte de Java Foundation Classes (JFC). Incluye un conjunto de elementos gráficos (o widgets) tales como cajas de texto, botones, menús o tablas para llevar a cabo el diseño y creación de aplicaciones de interfaz gráfica en Java. Una aplicación Swing puede tener incluido un menú que contiene un conjunto de opciones para interacturar con la aplicación. La clase principal encargada de representar el menú en una aplicación es JMenuBar, la cual puede contener un conjunto de elementos de tipo JMenuItem. En la Figura 4 se muestra la jerarquı́a de estos elementos de menú. Figura 4: Jerarquı́a de clases para los menús en Swing El código Java que incluye un menú File con las opciones New y Close en una aplicación App serı́a el siguiente: // Creacion de menus JMenu fileMenu = new JMenu("File"); fileMenu.add(new JMenuItem("New")); fileMenu.add(new JMenuItem("Close")); // Creacion de barra de menu JMenuBar menuBar = new JMenuBar(); menuBar.add(fileMenu); // Creacion de ventana e inclusin de barra de menu JFrame app = new JFrame("App"); app.setJMenuBar(menuBar); En este seminario se construirá una aplicación Java Swing que incorpore una barra de menú. La organización de los elementos contenidos en dicha barra de menú será dependiente del perfil que esté activo. Por lo tanto, debe existe una forma para realizar el cambio de perfiles. Para llevar a cabo la creación de la aplicación se ha optado por crear dos clases: Clase Application. Esta clase es una aplicación Swing que incluye la barra de menú y permite cambiar entre los diferentes perfiles. El cambio de perfiles se realiza por medio de la pulsación del botón correspondiente incluido en dicha aplicación. 4 Clase MenuManager. Es la clase encargada de construir los menús correspondientes a los perfiles. Es utilizada por Application para recuperar el menú asociado a un perfiles al pulsar el botón correspondiente. El código de ejemplo para esta clase se muestra en el Anexo D. Además, dado que el DSL de menús soporta la internacionalización de los menús, se utilizará la clase Internationalization (mostrada en el Anexo E. Esta clase será utilizada por la clase MenuManager para la construcción de los menús de la aplicación conforme a un idioma determinado. Los ficheros de internacionalización utilizados por esta clase deberán ser generados automáticamente para cada idioma. MenuManager obtendrá la tranducción de cada uno de los menús llamando al método getString de la clase Internationalization. La clase Internationalization utilizada en este caso práctico está configurada para utilizar siempre el lenguaje español, pero un diseño más realista deberı́a contemplar la posibilidad de cambiar el idioma arbitrariamente. A continuación, se abordará la generación de los ficheros necesarios utilizando el lenguaje MOFScript. 3.2. Generar los ficheros de internacionalización La generación de los ficheros de internacionalización toma como entrada instancias de modelos conformes al metamodelo i18n. Los ficheros generados tienen el formato tı́pico de un fichero de propiedades, donde cada lı́na define una propiedad con el formato key = value. Este podrı́a ser un ejemplo: fileMenu.name="Fichero" fileMenu.tooltip="Menu Fichero" fileMenu.mnemonic=f fileMenu.shortcut=ctrl+f Cada fichero corresponde a un lenguaje y contiene las propiedades necesarias para la traducción de cada menú. De esta forma, por cada Bundle del metamodelo i18n se generará un fichero de internacionalización que contendrá tantas propiedades como elementos Entry contenga dicha metaclase Bundle. El fichero generado seguirá el formato menu [language] [variant].properties, donde language y variant corresponden a los valores de dichos atributos en la instancia de la metaclase Bundle. Cada propiedad del fichero seguirá el formado [key]=[value] donde key y value serán precisamente los valores de dichos atributos de la instancia de la metaclases Entry. En el Anexo C se muestra un ejemplo de fichero de internacionalización. 3.3. Generar el esqueleto de Application Al igual que se harı́a para crear la aplicación de manera tradicional, el primer paso consistirı́a en generar una aplicación Java Swing de prueba que incorpore el menú o menús correspondientes a cada perfil. De esta forma, el proceso a seguir para definir la transformación MOFScript consistirá primero en generar la clase Java encargada de mostrar la ventana (llamada Application) y a continuación generar la clase encargada de gestionar los menús (llamada MenuManager). El primer paso para definir una transformación MOFScript es especificar su nombre y el metamodelo que utilizará para definir las reglas de transformación. Se parte del modelo de arquitectura generado por la transformación modelo-a-modelo definida en la sesión anterior. La finalidad principal del metamodelo al que conforma este modelo es acercarse al dominio de la arquitectura destino y facilitar con ello la generación de código. El metamodelo se muestra en el Anexo B. 5 A continuación se debe definir la regla main de la transformación. Como en la mayorı́a de los casos, la regla main de la transformación tendrá como contexto el elemento raı́z del metamodelo, que es MenuManager, el cual representa a la entidad encargada de crear y gestionar los menús. A partir de dicho elemento se puede acceder a los menus y los profiles definidos. La regla main se encargará de crear el fichero que contendrá a la clase Application, la cual se encargará de visualizar una ventana en pantalla. El código esqueleto a generar para mostrar una ventana en Java Swing serı́a el siguiente: package centic; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import import import import javax.swing.BoxLayout; javax.swing.JButton; javax.swing.JFrame; javax.swing.JPanel; public class Application { public static void main(String[] args) throws IOException { javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { Application theInstance = new Application(); ((Application) theInstance).createAndShowGUI(); } }); } public void createAndShowGUI() { // Create and set up the window. final JFrame frame = new JFrame("Application"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); JPanel mainPane = new JPanel(); BoxLayout layout = new BoxLayout(mainPane, BoxLayout.Y_AXIS); mainPane.setLayout(layout); frame.setContentPane(mainPane); // Show the window. frame.pack(); frame.setVisible(true); } } 3.4. Generar los botones de cambio de menú en Application El siguiente paso consistirı́a en enriquecer el código Java de la clase Application para que muestre un botón por cada perfil de menú que esté definido en el metamodelo. La pulsación de cada uno de ellos provocarı́a que la aplicación Java Swing cambie la disposición de los menús. El código especı́fico para la inclusión de menús en la aplicación está asociado a las acciones asociadas a los botones que incluirá la aplicación Swing. En concreto, el código Java que realizarı́a estas acciones serı́a el siguiente JButton ProfileButton = new JButton("Profile"); ProfileButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { frame.setJMenuBar(MenuManager.INSTANCE.getProfile()); frame.pack(); } }); mainPane.add(ProfileButton); Como se puede observar, el código anterior debe repetirse para cada tipo de perfil que se haya definido. Además, el punto de variabilidad del código se encuentra en el nombre que se 6 da a la variable JButton, el texto que incluye el botón, ası́ como la llamada al método get de la clase MenuManager. Con estas modificaciones, la definición de transformación MOFScript genera el código necesario para la clase Application. 3.5. Generar el código para MenuManager Al igual que para la clase Application, la regla main se encargará también de generar la clase MenuManager, la cual se encargará de crear los menús. El código que debe generar la regla main debe ser estudiado para conocer cuales son los puntos que dependen de la información del modelo de arquitectura. La clase MenuManager (un ejemplo de dicha clase puede encontrarse en D) se descompone en las siguientes secciones: 1. Declaración del paquete en el que está incluida la clase. 2. Sección de imports. 3. Declaración de la clase. a) Variables de instancia. b) Metódo de inicialización de los widgets y establecimiento de listeners c) Constructor de los elementos del menú. d ) Establecimiento de las acciones para los elementos del menú. e) Constructores de las barras de menú para los perfiles. De las secciones anteriores, las únicas que dependen de la información contenida en los modelos de la arquitectura las siguientes: Sección 1. El nombre del paquete debe poder ser cambiado. Sección 3a. Las variables de instancia dependen de los elementos de menú que contenga el modelo. Sección 3c. El constructor para los elementos del menú depende del número y tipo de elementos que contenga el modelo. Sección 3d. La acción asociada a cada elemento del menú depente de la información del modelo. Sección 3e. Se debe generar un constructor de barra de menú para cada perfil. Una vez identificadas las partes del código dependientes de la información del modelo, se puede pasar a la definición de la regla main encargada de la generación del código para la clase MenuManager. A continuación se estudiará el código a incluir en cada una de las secciones dependiente de la información del modelo. Sección 1. Generación del paquete. Esta sección es simple y simplemente debe hacerse uso de una propiedad de la definición de transformación para generar el codigo. Sección 3a. Generación de las variables de instancia. Esta sección de la clase MenuManager contiene tantas variables de instancia como elementos del menú contenga la instancia de la metaclase MenuManager, en concreto, la referencia menus. Esta referencia es de tipo MenuItem, que realmente es una jerarquı́a de metaclases que representan a diferentes tipos de menú, por lo que el código a generar para cada una de ellas difiere. Las correspondencias entre cada metaclase y el objeto Java Swing que lo representa como menú son las siguientes: 7 Metaclase arquitectura SingleMenuItem TreeMenu MenuBar CheckboxMenuItem Objeto Java Swing JMenuItem JMenu JMenuBar JCheckBoxMenuItem Para generar el código correspondiente a cada variable de instancia se deberá recorrer los elementos de la referencia menus de la metaclase MenuManager (que es el contexto de la regla main). Para cada elemento se llamará a la regla mapInstanceVariable, que será una regla definida para cada una de las metaclases de la jerarquı́a comentada anteriormente. El código de cada una de las reglas se debe encargar de definir la variable de instancia correspondiente al elemento. Sección 3c. Generación del código para construir los elementos del menú. Esta sección de código, al igual que en el caso anterior, depende de los elementos de la referencia menus de la instancia de la metaclase MenuManager. Para cada elemento que se ha añadido como variable de instancia se debe llamar al constructor. Los constructores de los anteriores objetos Java son: Metaclase arquitectura SingleMenuItem TreeMenu MenuBar CheckboxMenuItem Objeto Java Swing new JMenuItem(String menuText) new JMenu(String menuText) new JMenuBar() new JCheckBoxMenuItem(String menuText) Al igual que en el caso anterior, en la regla main se deberá recorrer los elementos de la referencia menus y para cada elemento llamar la regla llamada mapInitializeInstanceVa riable, que será la encargada de generar el código correspondiente. Sección 3d. Generar las acciones para los elementos del menú. Esta sección de código genera un método encargado de asignar las acciones a cada elemento del menú. Dado que solamente los elementos del menú de tipo SingleMenuItem tendrán establecida una acción (metaclase Action en el metamodelo de arquitectura), se debe recorrer la referencia menus para trabajar solamente con elementos de este tipo. Para cada uno de ellos se añadirá un nuevo listener que apuntará a la clase encargada de realizar la acción. Dicha clase está especificada en el atributo name de la clase Action a la que SingleMenuItem hace referencia. Un ejemplo de generación de código para añadir un listener a un botón serı́a el siguiente: this.fileMenu.addActionListener(new mnti2010.listeners.fileMenuAction()); Sección 3e. Generar los constructores de las barras de menú. Esta sección de la regla main se encarga de generar el código necesario para incluir un método cuya finalidad es construir la barra de menú asociada a un perfil. Se generarán tantos métodos como perfiles tenga la instancia de la metaclase MenuManager. Para ello, se deberá recorrer la referencia profiles y generar el código necesario para cada entrada. Los perfiles están compuestos por un conjunto de sentencias que establecen qué menús debe contener dicho perfil ası́ como sus propiedades básicas. De esta forma, se deberá recorrer la referencia statements para configurar la barra de menú del perfil. Esta referencia contiene un conjunto de elementos de la jerarquı́a Statement del metamodelo de la arquitectura. Para cada metaclase de la jerarquı́a se creará una regla llamada mapStatement que se encargará de generar el código asociado al comportamiento de 8 la sentencia. La siguiente tabla muestra las correspondencias para cada metaclase y la forma de llevar a cabo el comportamiento en Swing. Metaclase arquitectura AddMenu SetToolTip DisableMenu SetShortcut SetMnemonic Java Swing método add(JMenu menu) método setToolTipText(String text) método setEnabled(Boolean isEnabled) método setAccelerator(KeyStroke keyStroke) método setMnemonic(KeyEvent keyEvent) Con estas modificaciones, la definición de transformación MOFScript genera el código necesario para la clase MenuManager. 4. Ejercicios Para realizar estos ejercicios, descarga el proyecto esqueleto que en la web de la asignatura. El fichero seminario4-esqueleto.zip de la web contiene el proyecto esqueleto. Se facilita una primera versión para disponer de un ejemplo de lo que deberı́a generarse. Ejercicio 1. Completa la transformación MOFScript i18n2Text.m2t para que genere los ficheros de internacionalización. Estos ficheros deberán generarse en el directorio src del proyecto seminario4 ejercicios.gen. Los ficheros deben seguir el formato descrito en 3.2. Ejercicio 2. Completa la transformación MOFScript menu2swign.m2t para que genere las clases Application y MenuManager. La generación de estas clases deberá realizarse en el directorio src/imaci del proyecto seminario4 ejercicios.gen. Se deberá establecer el nombre de los menús utilizando la clase Internationalization y al menos su tooltip. Para su elaboración sigue las indicaciones dadas a lo largo de este guión. En el directorio models del proyecto seminario4 ejercicios se facilita un ejemplo de modelo conforme al metamodelo i18n (example-i18n.xmi) y otro ejemplo de modelo conforme al metamodelo de arquitectura (example.xmi) para probar las transformaciones definidas. El resultado de las transformaciones deberá estar contenido en un proyecto Java llamado seminario4 ejercicios.gen. Se enviarán los proyectos seminario4 ejercicios y seminario4 ejercicios.gen en un fichero zip con los ejercicios resueltos a jlcanovas@um.es. 9 A. Resumen sentencias MOFScript Tipos primitivos. Los tipos primitivos ofrecidos por MOFScript son los siguientes: String, Integer, Real, Boolean, Hastable, List y Object. El tipo Object permite representar cualquier tipo. Trabajo con ficheros. La sentencia file permite establecer el fichero a crear donde se generará el código indicado en la transformación. El nombre del fichero se indica como parámetro y puede incluir una ruta relativa o absoluta. Algunos ejemplos de uso son: file (" myfile . java ") file f1 (" myfile2 . java ") Impresión. Estas sentencias permiten imprimir cadenas de texto en un fichero o en la pantalla. Las sentencias principales son print y println. Por defecto, se imprime en el último file indicado pero puede indicarse la salida estándar para imprimir por pantalla. Algunos ejemplos de uso son: stdout . println ( ‘ ‘ mensaje ’ ’) file f1 (" myfile2 . java ") f1 . print ( ‘ ‘ codigo ’ ’ Iteradores. Los iteradores permiten recorrer colecciones de elementos. La sentencia forEach define un iterador sobre una colección que puede ser una colección de elementos del modelo, una lista/hash o una colección de String/Integer. Algunos ejemplos de uso son: collection - > forEach ( element ) collection - > forEach ( c : someType ) collection - > forEach ( a : String | a . size () = 2) Sentencia select. La sentencia select permite realizar una consulta sobre una colección de elementos y devuelve una lista con los elementos encontrados. Actualmente solamente puede ser utilizada en asignaciones y su sintaxis es muy similar a la utilizada en la sentencia forEach. Un ejemplo de uso serı́a: var result : List = self . states - > select ( st : stateMM . State | st . name . startswith (" ,→ ST ") ) Sentencias condicionales. MOFScript ofrece estructuras de tipo if y while. A continuación se muestran ejemplos de cada una de ellas: if ( c . states > 10) { // s t a t e m e n t s } else if ( c . states > 5) { // s t a t e m e n t s } else { // s t a t e m e n t s } while ( c . states > 1) { // s t a t e m e n t s } Expresiones lógicas. MOFScript ofrece soporte a expresiones lógicas estándar al igual que cualquier otro lenguaje de programación. Para más información acerca del http://www.eclipse.org/gmt/mofscript 10 lenguaje, se puede visitar la web B. Metamodelo de la arquitectura Figura 5: Metamodelo de la arquitectura generada. 11 C. Ejemplo de fichero de internacionalización fileMenu.name=Fichero fileMenu.tooltip=Menu Fichero fileMenu.mnemonic=f fileMenu.shortcut=ctrl+f newMenu.name=Nuevo newMenu.tooltip=Crea un fichero newMenu.mnemonic=n newMenu.shortcut=ctrl+n saveAsMenu.name=Salvar como saveAsMenu.tooltip=Salvar con otro nombre saveAsMenu.mnemonic=s saveAsMenu.shortcut=ctrl+s autosaveMenu.name=Autosalvar autosaveMenu.tooltip=Autosalvar autosaveMenu.mnemonic=a autosaveMenu.shortcut=ctrl+a closeMenu.name=Cerrar closeMenu.tooltip=Cierra la aplicacion closeMenu.mnemonic=c closeMenu.shortcut=ctrl+c editMenu.name=Edicion editMenu.tooltip=Menu Edicion editMenu.mnemonic=e editMenu.shortcut=ctrl+e undoMenu.name=Deshacer undoMenu.tooltip=Deshace el ultimo cambio undoMenu.mnemonic=u undoMenu.shortcut=ctrl+u redoMenu.name=Rehacer redoMenu.tooltip=Rehace el ultimo cambio deshecho redoMenu.mnemonic=r redoMenu.shortcut=ctrl+r ... 12 D. Código fuente para la clase MenuManager package mnti; import import import import javax.swing.JCheckBoxMenuItem; javax.swing.JMenu; javax.swing.JMenuBar; javax.swing.JMenuItem; class MenuManager { public final static MenuManager INSTANCE = new MenuManager(); // Instance variables private JMenu fileMenu; ... private void initWidgets() { createSingleMenus(); attachListeners(); } public void createSingleMenus() { this.fileMenu = new JMenu(Internationalization.getInstance().getString("fileMenu.name")); ... } public void attachListeners() { //this.newMenu.addActionListener(new mnti.listeners.newMenuAction()); ... } public JMenuBar getProfileAdvancedUser() { initWidgets(); this.AdvancedUser.add(this.fileMenu); ... return AdvancedUser; } ... } 13 E. Código fuente para Internationalization package mnti; import java.util.Locale; import java.util.ResourceBundle; public class Internationalization { private static ResourceBundle messages; private static Internationalization instance = null; private Internationalization() { Locale currentLocale = new Locale( "es" ,"ES" ) ; messages = ResourceBundle.getBundle("menu", currentLocale ) ; } public static Internationalization getInstance() { if(instance == null) instance = new Internationalization(); return instance; } public static String getString(String key) { String sol = null; try { sol = messages.getString(key); } catch(Exception e) { sol = key; } return sol; } } 14