Download Generación de código basado en modelos
Document related concepts
no text concepts found
Transcript
Generación de Código basado en Modelos Tema Desarrollo de un Framework de generación de código. Implementación de un lenguaje de script orientado a la definición de plantillas, que ante una determinada entrada (definición del modelo), generará el código final. Definición de un proceso de desarrollo para dar soporte a proyectos de mediana y gran escala. Extensión de un entorno de desarrollo (IDE) para facilitar la utilización del framework de generación de código implementado. Autores Bascal, Martín Pablo Hernández, Fernando Javier LU: 64951 LU: 64916 Director Mg. María Laura Cobo Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Indice Introducción............................................................................................................................6 Testing unitario automático....................................................................................................7 Mocking..............................................................................................................................8 Integración continua.............................................................................................................10 Herramientas utilizadas.......................................................................................................12 Java..................................................................................................................................12 NetBeans.........................................................................................................................13 JavaCC............................................................................................................................13 MySQL , Hibernate, JavaDoc-Tools.................................................................................16 Junit..................................................................................................................................19 JMock...............................................................................................................................20 CVS..................................................................................................................................22 Maven..............................................................................................................................23 Maven Plugins.................................................................................................................28 Clean...........................................................................................................................29 Properties....................................................................................................................29 Surefire........................................................................................................................31 Assembly.....................................................................................................................32 Dependency................................................................................................................33 Check-Style.................................................................................................................38 FindBugs......................................................................................................................42 Cobertura.....................................................................................................................44 AntRun.........................................................................................................................47 Tomcat.............................................................................................................................49 Archiva.............................................................................................................................50 Jenkins (antes hudson)....................................................................................................53 El lenguaje...........................................................................................................................55 Tipos nativos....................................................................................................................56 Variables..........................................................................................................................68 Arreglos............................................................................................................................69 Operadores......................................................................................................................71 Precedencia de operadores........................................................................................71 Operadores aritméticos...............................................................................................71 Operadores bit a bit.....................................................................................................72 Operadores de comparación.......................................................................................72 Operadores de incremento/decremento.....................................................................72 Operadores lógicos.....................................................................................................73 Operadores para strings..............................................................................................73 Estructuras de control......................................................................................................73 If...................................................................................................................................73 While............................................................................................................................74 do-while.......................................................................................................................75 for.................................................................................................................................75 foreach.........................................................................................................................76 break y continue..........................................................................................................76 2 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier switch...........................................................................................................................78 return...........................................................................................................................79 Require, Include, Require.once, Include.once............................................................79 Orientación a objetos.......................................................................................................79 Definición de clases.....................................................................................................79 Herencia......................................................................................................................81 Palabras reservadas super y this................................................................................82 Extensiones.....................................................................................................................84 Extensión e inicialización de objetos en la construcción............................................84 Expresiones lambda....................................................................................................86 El tipo ArrayQuery.......................................................................................................87 Funciones de extensión sobre arreglos......................................................................90 Manipulación de objetos Java.........................................................................................96 Logging y manejo de errores.........................................................................................101 Autoloader......................................................................................................................102 Writer..............................................................................................................................104 La función especial @...............................................................................................105 Configuración.................................................................................................................105 Proyectos desarrollados....................................................................................................107 CodeGeneratorPoms.....................................................................................................107 CodeGeneratorModules................................................................................................109 CodeGeneratorCommon...........................................................................................109 Paquete freelancesoft.codegenerator.common....................................................109 Paquete freelancesoft.codegenerator.common.array...........................................109 Paquete freelancesoft.codegenerator.common.arrayquery..................................109 Paquete freelancesoft.codegenerator.common.configuration..............................110 Paquete freelancesoft.codegenerator.common.context........................................110 Paquete freelancesoft.codegenerator.common.errorhandle.................................110 Paquete freelancesoft.codegenerator.common.parser.........................................111 Paquete freelancesoft.codegenerator.common.writer...........................................111 CodeGeneratorCommonTests...................................................................................112 CodeGenerator..........................................................................................................113 Paquete freelancesoft.codegenerator.array..........................................................114 Paquete freelancesoft.codegenerator.arrayquery.................................................114 Paquete freelancesoft.codegenerator.arrayquery.steps.......................................114 Paquete freelancesoft.codegenerator.configuration.............................................115 Paquete freelancesoft.codegenerator.context......................................................115 Paquete freelancesoft.codegenerator.errorhandle................................................115 Paquete freelancesoft.codegenerator.parser........................................................115 Paquete freelancesoft.codegenerator.utils............................................................115 Paquete freelancesoft.codegenerator.writer.........................................................115 CodeGeneratorLibrary...................................................................................................115 CodeGeneratorLibraryGenerator...............................................................................116 Essentials...................................................................................................................118 File.........................................................................................................................118 JavaLang...................................................................................................................119 Boolean..................................................................................................................119 3 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Float.......................................................................................................................120 Integer...................................................................................................................120 String.....................................................................................................................121 JavaNet......................................................................................................................121 wrap_java_net_URI...............................................................................................121 wrap_java_net_URL..............................................................................................122 JavaIo........................................................................................................................122 JavaxXml...................................................................................................................123 wrap_javax_xml_parsers_DocumentBuilder........................................................123 wrap_javax_xml_parsers_DocumentBuilderFactory............................................123 wrap_javax_xml_XMLConstants...........................................................................124 wrap_org_w3c_dom_Attr......................................................................................124 wrap_org_w3c_dom_CDATASection....................................................................124 wrap_org_w3c_dom_CharacterData....................................................................124 wrap_org_w3c_dom_Comment............................................................................125 wrap_org_w3c_dom_Document...........................................................................125 wrap_org_w3c_dom_DocumentFragment............................................................126 wrap_org_w3c_dom_DocumentType...................................................................126 wrap_org_w3c_dom_DOMConfiguration..............................................................126 wrap_org_w3c_dom_Element..............................................................................126 wrap_org_w3c_dom_Node...................................................................................127 wrap_org_w3c_dom_Text.....................................................................................128 wrap_org_w3c_dom_TypeInfo..............................................................................129 FsTemplating.............................................................................................................129 fs_templating_Configurable..................................................................................129 fs_templating_Template........................................................................................130 CodeGeneratorTemplates..............................................................................................132 TemplateManager......................................................................................................132 tpl_fs_TemplateManager.......................................................................................132 PhpEntities................................................................................................................134 FSSchemas...................................................................................................................136 entities.xsd.................................................................................................................137 php-entities.xsd.........................................................................................................138 FscodeGeneratorProjectType (Plugin para Netbeans IDE)..........................................141 Uso del parser....................................................................................................................142 Uso del parser por línea de comandos .........................................................................142 Uso del parser como librería ........................................................................................144 Plan de configuración........................................................................................................145 Configuración del servidor.............................................................................................145 Host...........................................................................................................................145 Tomcat.......................................................................................................................145 Java de Sun...............................................................................................................146 CVS...........................................................................................................................146 Archiva.......................................................................................................................148 Maven........................................................................................................................152 Jenkins (o Hudson)....................................................................................................153 Configuración del cliente...............................................................................................155 4 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Java de Sun...............................................................................................................155 JavaCC......................................................................................................................155 Maven........................................................................................................................155 NetBeans...................................................................................................................156 Configuración de un cliente windows............................................................................157 CVS...........................................................................................................................158 Java de Sun...............................................................................................................158 Maven........................................................................................................................159 PHP...........................................................................................................................159 Compilar en windows desde linux.................................................................................159 Utilizando el Framework de Generación de Código..........................................................162 Contenido del entregable...................................................................................................163 Map road............................................................................................................................164 Conclusiones......................................................................................................................165 Referencias........................................................................................................................165 5 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Introducción Como profesionales ya insertos en el mercado laboral sabemos por experiencia propia que durante el desarrollo de software se presentan ciertas tareas que son repetitivas. La intención de este proyecto es el desarrollo de herramientas para facilitar la ejecución de tareas rutinarias que se dan en el transcurso de un proyecto de software, principalmente en el área de negocios. El ciclo de vida de un producto de software está conformado por tareas de diversa índole, algunas de las mismas no requieren del intelecto o capacidades específicas de miembros del equipo. Si contáramos con un framework para generar código asociado a ese tipo de tareas, los tiempos en los cuales se puede llevar a cabo un proyecto se podrían reducir drásticamente. Nuestra tesis consiste en el desarrollo de un framework apuntado a la reducción de tiempos durante el ciclo de vida de un producto de software. A su vez, también estamos interesados en enfrentarnos a los problemas que se presentan en todo el proceso de desarrollo a gran escala, y principalmente en un entorno distribuido, en el cual los integrantes de un equipo no necesariamente están en el mismo lugar físico. Por esto, investigaremos, documentaremos e implementaremos el uso de herramientas que actualmente existen para producir software de calidad a mediana y gran escala. Durante el desarrollo utilizaremos dos entornos, cliente y servidor. Se describirá como instalar y configurar las herramientas necesarias en un servidor para poder compartir el código e implementar integración continua. También se documentarán los pasos para configurar una máquina cliente permitiendo sumar a un desarrollador al equipo. El producto final resultará en un framework que podrá ser utilizado como herramienta durante el desarrollo de aplicaciones. Dicho framework: • Recibirá como entrada un conjunto de archivos xml que representarán el modelo de negocios. • Recibirá un conjunto de templates con el fin de indicar como procesar los archivos xml para generar una salida. • Luego de su ejecución, el resultado será parte del código necesario para la aplicación final. Los templates deberán ser escritos en un lenguaje que definiremos e implementaremos, lo haremos definiendo una gramática y utilizando la herramienta JavaCC para generar un intérprete. Por otro lado, también sabemos que una herramienta con estas características no tendría mayores beneficios si no contáramos con algún método simple y rápido para definir el modelo. Por lo que desarrollaremos un plugin que extienda al IDE NetBeans para brindar soporte a nuestro lenguaje y la capacidad de definir el modelo de forma gráfica. 6 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Hoy en día, para lograr aplicaciones de calidad, es necesario contar con equipos de varias personas. Cuando manejamos grupos de desarrollo nos enfrentamos con problemáticas que no podríamos resolver sin el uso de herramientas que dan soporte durante todo el proceso. Pretendemos definir y utilizar un proceso que garantiza la calidad del software. Nos enfocaremos en el área de desarrollo y en el testing automático, dejando de lado los procesos que atañan al área funcional y al testing no automático. La principal herramienta que nos dará soporte durante todo el desarrollo será Maven de la organización Apache. Maven significa acumulación de conocimiento y es de utilidad en la gestión de proyectos de software basados en el lenguaje Java. Se basa en el concepto de Modelo de Objetos (POM), puede administrar proyectos, reportes y documentación. Es de fácil extensión mediante el uso de plugins. En particular, nos será de utilidad para: • Manejo de artefactos y dependencias mediante el uso de repositorios. • Ejecución de tests automáticos. • Garantizar la cobertura de los tests. • Garantizar la uniformidad y documentación del código fuente mediante la definición de políticas. • Búsqueda de defectos comunes. • Generar informes sobre todos los puntos anteriores. Definiremos dos entornos. Nos referiremos al entorno cliente como la configuración de software necesaria para que un desarrollador pueda trabajar en el proyecto. Nos referiremos como entorno servidor a la configuración necesaria en una máquina que dará soporte a todo el equipo, en la cual se implementará control de versiones, se alojarán los repositorios maven y servirá para la integración continua de todas las soluciones. Testing unitario automático Como ya mencionamos en la introducción, hemos hecho desarrollos empresariales en lo laboral. Una característica que se usa ampliamente en desarrollos a mediana y gran escala es la automatización de los tests. Una prueba unitaria es una forma de probar el correcto funcionamiento de un módulo de código. Esto sirve para asegurar que cada uno de los módulos funcione correctamente por separado. Luego, con las Pruebas de Integración, se podrá asegurar el correcto funcionamiento del sistema o subsistema en cuestión. Para que una prueba unitaria sea buena se deben cumplir los siguientes requisitos: • Automatizable: no debería requerirse una intervención manual. Esto es especialmente útil para integración continua. • Completas: deben cubrir la mayor cantidad de código. 7 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier • Repetibles o Reutilizables: no se deben crear pruebas que sólo puedan ser ejecutadas una sola vez. También es útil para integración continua. • Independientes: la ejecución de una prueba no debe afectar a la ejecución de otra. • Profesionales: las pruebas deben ser consideradas igual que el código, con la misma profesionalidad, documentación, etc. Aunque estos requisitos no tienen que ser cumplidos al pie de la letra, se recomienda seguirlos o de lo contrario las pruebas pierden parte de su función. En lo que respecta a completitud hay diferentes métodos de medición. Más adelante explicaremos la utilización del plugin de maven “Cobertura”, con este plugin se pude asegurar la cobertura de los tests tomando distintas medidas. Hemos optado por medir cantidad de líneas y no ramas, ya que nos parece excesivo intentar cubrir todos los posibles caminos en el código. El objetivo de las pruebas unitarias es aislar cada parte del programa y mostrar que las partes individuales son correctas. Proporcionan un contrato escrito que el trozo de código debe satisfacer. Estas pruebas aisladas proporcionan cinco ventajas básicas: • Fomentan el cambio: Refactorizar código es mucho más simple si se cuenta con un conjunto de pruebas unitarias automáticas porque ejecutándolas luego de implementar un cambio se puede asegurar, dentro de la medida de lo posible, que no se han introducido nuevos errores. • Simplifica la integración: La integración del código desarrollado por diferentes personas es más simple, porque cada unidad se ha probado individualmente. Es menos costoso testear luego de integrar. • Documenta el código: En muchos casos es relativamente simple aprender a utilizar cierto trozo de código leyendo en los tests que están asociados a él, por ejemplo, si se indaga en los códigos de los templates desarrollados, se puede aprender a escribir los xml que son requeridos como entrada. • Separación de la interfaz y la implementación: Para facilitar la escritura de tests automáticos, es necesario separar claramente la interfaz de la implementación. En la mayoría de los casos se puede utilizar un objeto mock para simular el comportamiento de una interfaz y poder testear independientemente a los objetos que la usan. • Los errores están más acotados y son más fáciles de localizar: Existen pruebas unitarias que pueden desenmascararlos. Mocking Mocking es una técnica con la cual se simula el comportamiento de objetos completos. Es utilizada en los test unitarios para lograr hacer pruebas de ojetos que usan otros objetos de una manera aislada, es decir, sin interacción real con esos otros objetos. La única restricción es que los objetos mock deben respetar una interfaz. Imitan el 8 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier comportamiento de otro objeto real que también implementa la interfaz. La mayoría de las librerías de mocking permiten hacer verificaciones sobre los métodos que el objeto que se está testeando debe invocar sobre esa interfaz, el orden, los parámetros, e incluso retornar algún resultado deseado. Un objeto fake es un objeto que respeta una interfaz y retorna valores por default para cada método. Un objeto mock va un poco más allá, posee la habilidad de: • Verificar si un método de la interfaz es invocado o no, y cuantas veces. • Verificar el orden en el que se invocan los método de la interfaz. • Verificar los parámetros con los cuales se ha invocado cada método. • Retornar un valor preestablecido. Para lograr hacer estos chequeos, las librerías de mocking brindan la posibilidad de setear expectaciones, es decir una forma de decirle al objeto mock lo que se espera que hagan con él. El ejemplo más claro en el cual se utilizan objetos mock es en el acceso a datos. En una aplicación típica tenemos una capa con varios objetos que se encargan de ir contra la base de datos y otra capa con objetos de negocios que utilizan a los de acceso a datos. Si los objetos de la capa de acceso a datos implementan interfaces, entonces pueden ser mockeados y de este modo se puede testear la lógica de negocios sin necesidad de acceder a datos reales. Recordemos que uno de los principios de los test unitarios es que estos deben ser independientes y no depender del entorno, por lo que no se considera correcto que un test unitario dependa de los datos subyacentes en una base de datos. Veamos un ejemplo para terminar de comprender como funciona el mocking de un objeto. Suponiendo que contamos con la siguiente interfaz. interface Subscriber { void receive(String message); } Y queremos testear a la clase Publisher que, internamente usa a un objeto que respeta la interfaz Suscriber. Entonces podemos escribir un test similar al siguiente: import org.jmock.Mockery; import org.jmock.Expectations; class PublisherTest extends TestCase { Mockery context = new Mockery(); public void testOneSubscriberReceivesAMessage() { // set up final Subscriber subscriber = context.mock(Subscriber. class ); Publisher publisher = new Publisher(); publisher.add(subscriber); final String message = "message" ; // expectations 9 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier context.checking( new Expectations() {{ oneOf (subscriber).receive(message); }}); // execute publisher.publish(message); // verify context.assertIsSatisfied(); } } Este test verifica que, cuando se invoca al método publish sobre un objeto de tipo Publisher, éste hará un llamado a receive sobre suscriber con el mismo parámetro. Todo esto sin necesidad de implementar la interfaz Suscriber. Integración continua La integración continua es una metodología, propuesta inicialmente por Martin Fowler, en la cual se intenta integrar partes de código lo más a menudo posible y de una manera automática. Con integrar queremos decir, resolver dependencias, compilar y ejecutar tests. Esta técnica es ampliamente utilizada o sugerida en metodologías ágiles, como Scrum o XP. La idea es la siguiente: Cuando un desarrollador introduce un cambio, este se hace responsable por los efectos en el proyecto en el que lo está introduciendo. Pero muchas veces pierde de vista las dependencias, en este caso, los proyectos que utilizan al que él está modificando. Con integración continua, el desarrollador trabaja de la misma manera. Pero en algún momento del día, posiblemente durante la noche, se ejecutarán compilaciones y corridas de tests sobre las últimas versiones de todos los proyecto. Con lo cual si se introdujo un error se descubre, a lo sumo, al día siguiente. Aplicar integración continua no es posible sin el uso de un conjunto de herramientas. Se debe contar con un repositorio (Control de versiones sobre el código) y con una máquina con una serie de herramientas que sea capaz de: 1. Descargar del repositorio todos los proyectos. 2. Compilarlos en orden, según unos dependan de otros. 3. Correr tests que tengan la mayor cobertura posible. 4. Generar informes, sobre todo en el caso de fallos en cualquiera de los pasos. Para lograr sus objetivos, la integración continua cuenta con una serie de principios: • Mantener un repositorio de código. Es necesario mantener un repositorio donde se aloje todo el código. Si el repositorio acepta creación de ramas, estas deben ser minimizadas ya que en general, la integración se aplica sobre la rama principal. • Automatizar el build. Se debe poder hacer el build con tan sólo un comando. Para esto existen diferentes herramientas, en nuestro caso maven. El proceso incluye la 10 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier ejecución de tests automáticos y en muchos casos otros pasos adicionales, como chequeos extras, generación de informes, entregables, etc. El proceso de build es mucho más amplio que sólo compilar. • Hacer el build auto testeable. Deben existir tests automáticos que se puedan ejecutar para asegurar la correctitud del código. • Todos modifican sobre la línea base. Para evitar conflictos todos los desarrolladores deben hacer “commit” de sus cambios tan regularmente como sea posible. Esto tiene varias ventajas, se minimiza el riesgo de conflictos y además lleva a los desarrolladores a mantener una comunicación fluida sobre los cambios que introducen. • Se debe hacer build de cada cambio. Por cada cambio es esencial hacer build sobre el proyecto que fue modificado. Esto puede ser de manera manual o con herramientas que chequean cambios sobre el control de versiones y disparan el build. • Mantener el build rápido. La ejecución de los builds debe ser lo más rápida posible para identificar problemas tan pronto como se pueda. • Lograr que la obtención de los últimos entregables sea sencilla. Debe ser sencillo para desarrolladores y testers obtener las últimas versiones. Una vez identificado y corregido un error, se debe poder generar una nueva versión del entregable de manera rápida para que pueda ser testeado nuevamente. • Deploys automáticos. También se debe poder pasar a servidores de testing, e incluso en algunos casos de producción, de manera automática. Hoy en día, la integración continua es ampliamente utilizada a nivel empresarial para el desarrollo de proyectos a mediana y gran escala. Las ventajas son realmente innumerables, algunas de las mismas podrían ser: • Los errores de integración se detectan y corrigen continuamente. Sabemos que mientras antes se detecte y corrija un error es mucho más “barato”. • Rápida detección de cambios incompatibles. • Rápida detección de código en conflicto. • Testing automático de todo el código de una manera rápida. • Mantención de métricas e informes sobre diversos aspectos como cobertura y complejidad del código. • Mismo estilo en todo el código. Se pueden definir políticas y obligar a que los desarrolladores las sigan, fallando el build si alguna no se cumple. • Etc. Como todo en el desarrollo de software tiene algunas desventajas: 11 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier • El tiempo para lograr el ambiente para integración continua es significativo. Si el proyecto es pequeño tal vez no valga la pena. • Es necesario que los tests sean automáticos, correctos y que cubran la mayor cantidad de código posible. • Se debe invertir en hardware para un servidor de integración continua. Como conclusión, en general, las ventajas pesan mucho más que las desventajas, siempre que el proyecto que estemos encarando no sea demasiado pequeño. Herramientas utilizadas A continuación, explicaremos someramente el uso de las herramientas que seleccionamos. En algunos casos evaluaremos alternativas e intentaremos justificar nuestra elección. Java Como lenguaje de programación, hemos elegido al lenguaje Java. No hay demasiado para explicar sobre el mismo, es ampliamente utilizado por toda la comunidad. Lo seleccionamos porque queríamos un lenguaje que cumpla con los siguientes requisitos: • Que el uso del lenguaje sea gratuito y preferentemente, con librerías de código abierto. • Lograr que nuestro software sea multiplataforma, que pudiese correr tanto en windows como en linux. Esto se logra gracias a la máquina virtual de java sin mayores problemas. • Contar con un IDE gratuito para utilizar durante el desarrollo. Hay varios IDE para programar en java. Los más utilizados son NetBeans y Eclipse, ambos son gratuitos y cuentan con numerosos plugins que podremos utilizar. • Generar nuestro propio lenguaje basado en su gramática. Contamos con varias herramientas gratuitas para generar analizadores sintácticos y léxicos basados en gramáticas con las cuales podremos generar nuestro propio lenguaje. • Contar con herramientas gratuitas para soportar testing unitario y mocking. Hay numerosas librerías para java de índole gratuito. • Contar con herramientas, también gratuitas, para dar soporte a la integración contínua. Para esto también existen muchas herramientas con plugins gratuitos que servirán a distintos fines. El lenguaje Java cumple con todos los requisitos enumerados, por lo que pensamos que es el ideal para ser utilizado en nuestro proyecto. No se puede hablar de otra alternativa, porque ninguna conocida cumple con todos los puntos anteriores. 12 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier NetBeans Si bien hay varias alternativas para ser utilizadas como IDE durante el desarrollo de proyectos basados en el lenguaje Java, el número se reduce si buscamos software gratuito y preferentemente de código abierto. Es indiscutible que existen dos IDEs “líderes” hoy por hoy, uno es NetBeans y el otro es Eclipse. Ambos proveen todo lo que necesitamos durante el desarrollo de nuestros proyectos y están en constante evolución, también cuentan con numerosos plugins. Para nosotros es indistinto cuál de éstos será utilizado. En particular nos hemos inclinado por NetBeans, dado que nos sentimos más cómodos con el mismo. A demás cuanta con una API relativamente sencilla para desarrollar nuestros propios plugins. Los defensores de Eclipse alegan que si bien NetBeans es un poco más flexible, consume más recursos. Por el momento no hemos tenido este tipo de problemas. De todas formas, dejamos abierta la posibilidad de usar cualquiera de las dos alternativas durante el desarrollo, de hecho hemos compilado todos nuestros proyectos con Eclipse (No debe haber diferencias, ya que ambos se conectan con maven) sin mayores problemas. La única diferencia es que el IDE de Eclipse marca algunos warnings que no vemos en NetBeans y viceversa, por lo que si queremos implementar la política “cero warnings” utilizando Eclipse deberíamos hacer algunos ajustes. Tampoco descartamos la posibilidad del desarrollo de un plugin para Eclipse en el futuro, que brinde soporte a nuestro lenguaje, como lo hemos hecho para NetBeans. JavaCC JavaCC es la herramienta que seleccionamos para generar el parser de nuestro propio lenguaje. Significa Java Compiler Compiler y es una de las herramientas más populares que se utiliza para generar parsers para aplicaciones Java. Lee una especificación de una gramática y la convierte en un programa Java que puede reconocer la misma. A demás de la generación del parser en sí mismo, provee otras capacidades como la construcción de un árbol a partir de la gramática vía la herramienta JJTree que ya viene integrada con JavaCC. Esto es particularmente útil para implementar intérpretes, se puede hacer un parser que construya un árbol a partir de la gramática y luego procesar ese árbol para obtener un resultado. Cuenta con su propio generador léxico, con lo cual se puede indicar la forma de obtener los tokens en el mismo archivo en el cual se especifica la gramática y no se requieren librerías ni herramientas adicionales. También cuenta con ciertos mecanismos mediante los cuales podemos crear una gramática más legible. Por ejemplo el uso de lookahead, con el cual le podemos indicar que ante una arregla ambigüa “vea” algunos tokens hacia adelante para seleccionar la próxima regla correctamente. Con esto, no necesitamos agregar reglas para desambig üar que terminan ensuciando la gramática. Cada caso debe ser evaluado cuidadosamente, no es bueno abuzarse del uso de lookahead. Es ampliamente utilizada y está probado que funciona en ambas plataformas (linux y 13 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier windows) sin problemas. De hecho al probar nuestro desarrollo en diferentes plataformas sólo encontramos un problema con el separador de línea que se solucionó de una manera muy sencilla. Características resaltadas por el equipo de JavaCC: • Top-Down: JavaCC genera parsers top-down (recursivos descendentes), por el contrario, herramientas como YACC generan parsers bottom-up. Esto permite el uso de gramáticas más generales (aunque no permite recursión a izquirda). Estos parsers tienen muchas otras ventajas (además de la gramática más general) como ser más fáciles de debuguear, tienen la habilidad de parsear a cualquier no terminal en la gramática y también la habilidad de pasar atributos hacia arriba y abajo en el árbol de parseo. • Amplia comunidad de usuarios: JavaCC es lejos, el generador más usado en aplicaciones Java. Tienen cientos de miles de descargas y estiman varios miles de usuarios serios. Cuentan con miles de usuarios en la lista de mails . • Especificación léxica y gramática en un único archivo: Las especificaciones léxicas como expresiones regulares, strings, etc. y las especificaciones de la gramática (el BNF) son escritas juntas en el mismo archivo. Esto hace que las gramáticas sean más fáciles de leer (desde que es posible definir expresiones regulares inline en la especificación de la gramática), también es más fácil de mantener. • Preprocesador para construir el árbol gramatical: JavaCC viene con JJTree, un constructor para el árbol extremadamente poderoso. • Muy customizable: JavaCC ofrece muchas diferentes opciones para customizar su comportamiento y el de los parsers generados. Ejemplos de estas opciones son los tipos de unicode para los streams de entrada, el número de tokens para chequear ambigüedad, etc. • 100% Java puro certificado: JavaCC corre en todas las plataformas Java desde la versión 1.1 o superior. Fue usado en incontables diferentes máquinas sin esfuerzo especial para portarlo. Es un testimonio del aspecto del lenguaje Java: “Escribir una vez, correr donde sea”. • Generación de documentación: JavaCC incluye una herramienta llamada JJDoc para convertir archivos de gramática en archivos de documentación (opcionalmente en html). • Muchos ejemplos: El entregable de JavaCC incluye un amplio rango de ejemplos con gramáticas Java y Html. Los ejemplos, junto con su documentación son una excelente manera de familiarizarse con JavaCC. • Multilenguaje: El analizado léxico de JavaCC puede manejar entradas unicode, y las especificaciones léxicas tal vez puedan contener caracteres unicode. Estas facilidades permiten descripciones de elementos de lenguajes como identificadores Java que permitan ciertos caracteres no unicode (que no son ASCII). 14 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier • Especificaciones de lookahead sintácticos y semánticos: Por default JavaCC genera un parser LL(1). Sin embargo puede haber porciones de la gramática que no sean LL(1). JavaCC ofrece la capacidad de lookahead sintácticos y semánticos para resolver ambigüedades localmente en esos puntos. Por ejemplo, el parser es LL(K) solo en esos puntos, pero permanece LL(1) en cualquier otro lugar para mejor performance. • Permite especificaciones BNF extendidas: JavaCC permite BNF extendidos – como (A)*, (A)+, etc. - dentro de las especificaciones léxicas y gramaticales. Los BNF extendidos son más fáciles de leer, como por ejemplo en A ::= y(x)* contra A ::= Ax|y. • Estados léxicos y acciones léxicas: JavaCC ofrece estados y acciones léxicos similares a lex. Aspectos específicos de JavaCC que lo hacen superiores a otras herramientas es el manejo de estados como TOKEN, MORE, SKIP, etc. Esto permite especificaciones más limpias y mejor informe de mensajes de errores por parte de JavaCC. • Análisis léxico no sensible a mayúsculas: Las especificaciones léxicas pueden definir tokens que no sean sensibles a mayúsculas, tanto en el nivel global para la especificación completa, o en un sector más acotado. • Capacidades extensivas para depurar: Usando las opciones DEBUG_PARSER, DEBUG_LOOKAHEAD y DEBUG_TOKEN_MANAGER, se puede analizar en profundidad el parseo y el procesamiento de tokens. • Tokens especiales: Los tokens que son definidos como especiales son ignorados durante el parseo, pero esos tokens están disponibles para ser procesados mediante herramientas. Un uso específico de esto es para el procesamiento de comentarios. • Muy buen reporte de errores: El reporte de errores de JavaCC está entre los mejores. Los parsers generados por JavaCC tienen la capacidad de identificar claramente el punto en el que el error se produjo y brindar información completa de diagnóstico. Por las razones antes expuestas, creemos que de momento, JavaCC es la elección más acertada para la generación de nuestro parser. Ejemplo de una regla léxica en la cual indicamos la forma del token “=”: <CODE> TOKEN : { < ASSIGN: "=" > } Ejemplo de una regla gramatical, en la cual indicamos que hacer ante el token “continue”. En este caso se marcan los límites por si llega a ser necesario reportar un error. Además se genera un nodo en el árbol léxico que luego será interpretado. 15 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier private void continueStatement() #ContinueNode(0) : {Token t;} { t = <CONTINUE> { jjtThis.setTokenLimits(t.beginLine, t.endLine, t.beginColumn, t.endColumn); jjtThis.setOwner(getActualClassName()); } } Para más detalle sobre la gramática, el archivo en donde se define se puede encontrar en el proyecto CodeGenerator. Más precisamente en: CodeGeneratorModules/CodeGenerator/src/main/java/freelancesoft/codegenerator/parser/CodeGenerator. jjt Hemos analizado otras herramientas, entre ellas: • ANTLR • CUP • Jlex • Jflex También hemos leído varias comparativas entre las diferentes alternativas. MySQL , Hibernate, JavaDoc-Tools Estas tres herramientas fueron utilizadas para desarrollar CodeGeneratorLibraryGenerator. Básicamente es una aplicación de escritorio que toma el nombre de una clase Java, utiliza JavaDoc-Tools para extraer de la clase toda la información posible: • Comentario de la clase. • Nombre de la clase. • Campos públicos estáticos, con tipo y comentario. • Métodos públicos estáticos, con tipo de retorno, parámetros y comentarios. • Campos públicos, con tipo y comentarios. • Métodos públicos, con tipo de retorno, parámetros y comentarios. • Constructores, con parámetros y comentarios. Una vez extraída toda esta información, permite hacer customizaciones sobre las mismas. Todo esto se puede guardar en una base de datos MySQL, para poder hacer posteriores modificaciones. 16 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Luego, se pueden generar los siguientes archivos: • Un archivo de tipo fgl que es la implementación completa de un wrapper equivalente a la clase java, pero escrito en nuestro lenguaje. • Un archivo te test para el archivo de tipo fgl. Este contiene tests para todos los campos y métodos implementados parcialmente. Luego deben ser modificados para llegar a ser tests útiles. • Un archivo fgl, también parcialmente implementado (debe ser modificado), para cada método de test. Con esta aplicación logramos generar wrappers de clases java escritas en nuestro propio lenguaje con un esfuerzo relativamente pequeño. Hemos seleccionado la combinación: • MySQL. Como motor de base de datos. • Hibernate. Como ORM (Object-Relational Mapping). • JavaDoc-Tools. Para extraer la información de las clases Java. Dado que esta aplicación se considera de uso interno, no hemos evaluado demasiadas alternativas. Simplemente buscamos que sea algo rápido para desarrollar y usar, de fácil integración. La clase ReflectionHelper utiliza JavaDoc-Tools para procesar la clase Java. El método start es invocado con un objeto de tipo RootDoc, este objeto contiene toda la información que ha recolectado la herramienta acerca de la clase Java, incluyendo los comentarios. Esto es de suma utilidad porque nuestro archivo fgl que representa la misma clase contiene comentario similares. Luego se usa Hibernate, combinado con MySQL para persistir los datos. Un archivo típico de configuración para Hibernate es un xml similar al siguiente: < hibernate-configuration > < session-factory > < property name = "hibernate.dialect" > org.hibernate.dialect.MySQLDialect </ property > < property name = "hibernate.connection.driver_class" > com.mysql.jdbc.Driver </ property > < property name = "hibernate.connection.url" > jdbc:mysql://localhost:3306/CodeGeneratorLibraryGenerator </ property > < property name = "hibernate.connection.username" > **** </ property > < property name = "hibernate.connection.password" > **** </ property > < mapping resource = "freelancesoft/codegeneratorlibrarygenerator/entities/ClassDto.hbm.xml" /> < mapping resource = "freelancesoft/codegeneratorlibrarygenerator/entities/ConstructorDto.hbm.xml" /> < mapping resource = "freelancesoft/codegeneratorlibrarygenerator/entities/ConverterDto.hbm.xml" /> < mapping resource = "freelancesoft/codegeneratorlibrarygenerator/entities/FieldDto.hbm.xml" /> < mapping resource = "freelancesoft/codegeneratorlibrarygenerator/entities/GeneratedClassDto.hbm.xml" /> < mapping resource = "freelancesoft/codegeneratorlibrarygenerator/entities/KnowedTypeDto.hbm.xml" /> < mapping resource = "freelancesoft/codegeneratorlibrarygenerator/entities/MethodDto.hbm.xml" /> 17 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier < mapping resource = "freelancesoft/codegeneratorlibrarygenerator/entities/ParameterDto.hbm.xml" /> < mapping resource = "freelancesoft/codegeneratorlibrarygenerator/entities/ReflectionInfoDto.hbm.xml" /> </ session-factory > </ hibernate-configuration > También hay que definir los mapeos, es decir hay que indicarle a Hibernate como mapear las propiedades de una entidad en tablas de la base de datos: < hibernate-mapping > < class name = "freelancesoft.codegeneratorlibrarygenerator.entities.ClassDTO" table = "ClassDTO" catalog = "CodeGeneratorLibraryGenerator" lazy = "false" > < id name = "id" type = "java.lang.Long" > < column name = "id" /> < generator class = "identity" /> </ id > < property name = "privateConstructor" type = "boolean" > < column name = "privateConstructor" not-null = "true" /> </ property > < property name = "name" type = "string" > < column name = "name" length = "65535" /> </ property > < property name = "group" type = "string" > < column name = "libraryGroup" length = "65535" /> </ property > < property name = "generatedName" type = "string" > < column name = "generatedName" length = "65535" /> </ property > < property name = "comment" type = "string" > < column name = "comment" length = "65535" /> </ property > < property name = "generatedComment" type = "string" > < column name = "generatedComment" length = "65535" /> </ property > < property name = "extendsOf" type = "string" > < column name = "extends" length = "65535" /> </ property > < property name = "useCustomFromJavaObject" type = "boolean" > < column name = "useCustomFromJavaObject" not-null = "true" /> </ property > < property name = "customFromJavaObject" type = "string" > < column name = "customFromJavaObject" length = "65535" /> </ property > </ class > </ hibernate-mapping > Para utilizar Hibernate podemos definir métodos como el siguiente: public static ReflectionInfoDTO getByClassName(String className) { Session sess = getCurrentSession(); return (ReflectionInfoDTO) sess.createQuery( "select Ref from ReflectionInfoDTO as Ref inner join Ref.classInfo as Ci where Ci.name = ?" ). setString( 0 , className).uniqueResult(); } De este modo Hibernate nos abstrae del motor de base de datos subyacente, permitiendo hacer consultas directamente sobre los objetos java, inspeccionando sus relaciones y sin necesidad de definir queries SQL. Para más información sobre el uso de estas herramientas ver la sección CodeGeneratorLibraryGenerator. 18 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Junit Para escribir nuestros tests hemos seleccionado al framework Junit en su versión 4. Junit es un conjunto de librerías que son utilizadas para hacer pruebas unitarias de aplicaciones Java. Básicamente es un framework que permite hacer pruebas sobre el comportamiento de una clase. Además brinda la posibilidad de hacer chequeos sobre las salidas esperadas, en caso de que la salida sea la esperada el test termina exitosamente, caso contrario falla. La principal razón por la que seleccionamos Junit es porque ya ha sido adoptado por la comunidad, a tal punto que los IDEs (tanto NetBeans como Eclipse) lo traen incorporado e incluso traen plantillas para crear clases de tests. Junit se basa en el uso de atributos, un método de test se marca con el atributo @Test, al final del test, se usan asserts para verificar la correctitud de los resultados. Por ejemplo un test sumamente simple para verificar la suma de dos valores podría ser como sigue: @Test public void simpleAdd() { Money m12CHF= new Money(12, "CHF"); Money m14CHF= new Money(14, "CHF"); Money expected= new Money(26, "CHF"); Money result= m12CHF.add(m14CHF); assertTrue(expected.equals(result)); } Si se requiere escribir varios tests sobre el mismo conjunto de objetos, se pueden usar los fixtures. Se puede marcar un método con @Before para inicializar, este método será ejecutado antes de cada test. Se puede marcar otro método con @After que será ejecutado inmediatamente después de cada test. Por ejemplo, un fixture típico podría ser el siguiente: public class MoneyTest { private Money f12CHF; private Money f14CHF; private Money f28USD; @Before public void setUp() { f12CHF= new Money(12, "CHF"); f14CHF= new Money(14, "CHF"); f28USD= new Money(28, "USD"); } } Además de la integración con diferentes IDEs a través de plugins, Junit también cuenta con una variedad de runners. Por ejemplo, para correr tests y ver los resultados mediante una consola se puede ejecutar el siguiente comando: org.junit.runner.JUnitCore.runClasses(TestClass1.class, ...); También tiene atributos para definir expectativas. Por ejemplo, para especificar que se espera una excepción de tipo IndexOutOfBoundsException: 19 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier @Test(expected= IndexOutOfBoundsException.class) public void empty() { new ArrayList<Object>().get(0); } También hemos evaluado la librería TestNG, muchos de las características que ofrece Junit, también las ofrece TestNG. Son muy similares en muchos aspectos. Una de las diferencias es que TestNG está un poco más orientado a realizar tests de integración, no sólo puramente unitarios. De hecho muchos dicen que son complementarias, por lo que no descartamos su uso en el futuro cuando debamos testear integración entre diferentes librerías, templates, etc. JMock En la sección sobre Mocking hemos explicado la importancia del uso de objetos Mock para realizar tests unitarios. Esta tarea es sumamente ardua si no contamos con alguna librería que nos de soporte. Esta elección fue más difícil, ya que existen muchas librerías para esto, la mayoría gratuitas, de código abierto y con características similares. Para comprender las similitudes, presentaremos una tabla comparativa extraída del artículo Java Mock Frameworks Comparision: Actual version Jmock EasyMock Mockito SevenMock Jmockit rMock Unitils 2.6-RC1 (7 Dec 2008) 2.4 (30 Aug 2008) 1.7 (24 Jan 2009) 2.1.0 (17 Jun 2008) 0.94 (30 Jan 2008) 2.0.0 (18 Mar 2007) 2.2 (23 Dec 2008) Website http://jmock.org http://easymoc http://code.goo http://sevenhttps://jmockit. k.org gle.com/p/moc mock.sourcefor dev.java.net kito ge.net Features Specification- «Record/Repla «Verify after like definition y/Verify» run» model. of mocks’ model. Basic Basic methods expectations expectations behavior (looks are defined via are checked like DSL). calling after calling appropriate (mocks mocks’ remember their methods with methods actual data. invocations). Other types of Other types of expectations expectations are defined are defined as with rules before framework’s running. methods. Method stubbing and expectations are defined using inner classes. http://rmock.so http://www.uniti urceforge.net ls.org Methods «Record/Repla stubbing are y/Verify» made using model. inner classes. Expectations Annotations for are defined those methods using helper define basic methods. expectations. Other expectations are defined using DSL-like style. Mockito-like «Verify after run» model. Value returning for stubbed method + + + +* + + + Exception throwing for stubbed method + + + +* + + + Invocations count check + + + -* + + +* Method arguments check + + + -* + + + 20 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Invocations order for one mock check + + + + - + + Iterator-style stubbing + + + -* +* - + Real objects spy - - + -* +* - + Callback invocation from stubbed method + + + - - - + Mocking of final classes, methods, static and private fields - - - - + - - Mocking of equals() and hashcode() methods - - - - - - - Thread safe - + + - - - - Invocation order between mocks check + + + + - + - Total score 6,7 7,5 8,3 3,5 4,8 5,8 5,6 * — La característica es soportada, pero no es implementada directamente por el framework. Si bien la tabla está desactualizada porque fue hecha hace tiempo, refleja claramente que las características son similares. Además las librerías también han evolucionado de manera similar. Por nuestra parte, realizamos pruebas de concepto sobre las siguientes: • jMock • EasyMock • Mockito Son todas similares, ofrecen casi las mismas características y produjeron resultados satisfactorios. Terminamos por seleccionar Jmock, principalmente porque la habíamos utilizado en ocasiones anteriores y nos sentimos más cómodos con su uso. Muchos dicen que una de las desventajas de Jmock es que su forma de uso es algo compleja y la curva de aprendizaje es un poco más grande que la de otras librerías. Justamente, como ya habíamos utilizado antes Jmock, estas no son grandes desventajas para nosotros. Además, la mayoría de los métodos de test no utilizan directamente Jmock si no que lo hacen a través de nuestro proyecto común que contiene una clase base de la cual heredan todos los test fixtures. Para más información sobre la utilización de Jmock, ver la sección CodeGeneratorCommonTests de este documento. Un típico test con objetos mock es como sigue: ... create mock objects ... 21 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier public void testSomeAction() { ... set up ... context.checking(new Expectations() {{ ... expectations go here ... }}); ... call code being tested ... context.assertIsSatisfied(); ... other assertions ... } CVS En la actualidad, es casi imposible el desarrollo de proyectos en el que participa más de un desarrollador sin el uso de algún sistema de control de versiones. Podemos encontrar varios, con diferentes características. Aunque el objetivo principal de cualquiera de ellos es el mismo, mantener un repositorio de código fuente, permitiendo compartir el código. Los cambios que hace un desarrollador son subidos a este repositorio y pasan a ser visible para el resto del equipo. Casi todos tienen también la capacidad de resolver conflictos, es decir, diferentes cambios sobre la misma línea base de un archivo. La decisión de que sistema de control de versiones utilizar no es una decisión sencilla porque hay varios con diferentes características, pero el resultado final será casi el mismo porque los principales requisitos son implementados por casi todas las alternativas. Hemos decidido utilizar CVS (Concurrent Versions System) por las siguientes razones: • Es muy sencillo de instalar y configurar en un servidor linux. • Se integra con los usuarios del sistema y permite dar permisos a diferentes usuarios simplemente otorgando permisos sobre los directorios del disco. • Permite la creación de ramas y también el etiquetado de las versiones. • Permite hacer bloqueos no exclusivos de los archivos y merge relativamente sencillo. • Hay numerosas herramientas cliente para conectarse al repositorio, tanto en modo texto como gráfico, y para diferentes sistemas operativos. • Lleva varios años evolucionando y es sumamente estable. • Cuenta con plugins para los IDEs NetBeans y Eclipse. • El repositorio es central y no distribuido, es decir que el código se encuentra siempre centralizado en el servidor. • Maneja archivos de tipo binario y texto. • También es sencillo conectar a casi cualquier herramienta para integración 22 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier contínua con un repositorio CVS. • Etc. También hemos evaluado algunas otras alternativas: • Subversion. Es una evolución de CVS, hoy por hoy son muy parecidos pero Subversion continúa agregando nuevas características mientras que CVS se mantiene estable. • Mercurial. Es un sistema de control de versiones gratuito y distribuido. • Git. Es otro sistema de control de versiones de código abierto, gratuito y distribuido. Cada clon del repositorio mantiene el historial de cambios completo, se jacta de ser sumamente rápido en sus operaciones de creación de ramas y mezcla de archivos. Maven Para lograr integración continua en proyectos basados en el lenguaje Java se usa un conjunto de herramientas. Maven es una de las más importantes, si no la más importante, utilizada a tal fin. Es una herramienta sumamente poderosa que ayuda a diferentes aspectos durante todo el ciclo de vida de un proyecto. Es muy flexible y cuenta con muchísimos plugins, en los cuales delega ciertas tareas. No es sólo una herramienta para compilar, sino que también se ocupa de resolver dependencias, correr tests, hacer diferentes tipos de chequeos (como estilos de programación y busca de bugs comunes), chequear cobertura sobre los tests unitarios automáticos, generar reportes de diferente índole, hacer deploy de entregables contra un servidor, generar sitios y casi todo lo que sea automatizable durante el transcurso de un proyecto de software. Maven, es una palabra en dialecto Yrddish, que significa acumulador de conocimiento. Comenzó originalmente como un intento de simplificar el proceso de build en el proyecto Jakarta Turbine. Había varios proyectos, cada uno con sus archivos de configuración Ant. Como repositorio se usó CVS y los archivos jar generados eran subidos al repositorio. La gente de apache quería una manera estándar de construir los proyectos, una definición clara de los alcances de cada proyecto, una forma fácil de publicar información a cerca de cada proyecto y también la posibilidad de compartir archivos de tipo Jar entre diferentes proyectos. El resultado es una herramienta que puede ser usada para administrar cualquier proyecto basado en Java. El objetivo principal de maven es que un desarrollador pueda comprender el estado general de un esfuerzo de desarrollo en el período de tiempo más corto posible. Para alcanzar esta meta, hay diferentes áreas que ataca: • Hacer el proceso de construcción simple. Si bien el uso de maven no elimina la necesidad de conocer los mecanismos subyacentes, provee una gran capa de abstracción sobre los detalles. 23 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier • Proveer un sistema de construcción uniforme. Maven permite hacer build de un proyecto utilizando un archivo de configuración de extensión pom (Project Object Model) y un conjunto de plugins que son compartidos por todos los proyectos, brindando un sistema de construcción uniforme. Cuando alguien está familiarizado en como funciona maven sobre un proyecto, entonces ya está familiarizado en como funciona para cualquiera, sin necesidad de intentar comprender detalles por separado, con lo cual se logra un ahorro de tiempo considerable. • Proveer información sobre el proyecto de calidad. Provee gran cantidad de información útil que es tomada en parte del archivo pom asociada al proyecto, y en parte del propio código. Por ejemplo puede brindar: ◦ Documento de cambios (change log) creado directamente utilizando el código fuente. ◦ Resolver dependencias entre proyectos. ◦ Listas de correo. ◦ Reporte sobre tests unitarios, incluyendo cobertura. ◦ Mediante el uso de plugins, también se puede lograr cualquier información basándose en el archivo pom y el código fuente. • Provee guías para las mejores prácticas durante el desarrollo. Tiene como principio fomentar buenas prácticas y guiar a un proyecto en esa dirección. Por ejemplo especificación, ejecución y reporte sobre tests unitarios es parte del ciclo de vida estándar. Se han utilizado las mejores prácticas actuales como guía: ◦ Mantener el código asociado a los tests por separado del código fuente, pero en el mismo árbol de código. ◦ Usar convenciones de nombre para encontrar y ejecutar tests. ◦ Los casos de prueba pueden configurar su propio entorno y no se requieren modificaciones al proceso de build para preparar los tests. • Maven también tiene como objetivo ayudar en el flujo de trabajo de un proyecto, como en la gestión y liberación de entregables y en el seguimiento de problemas. • También sugiere pautas sobre la estructura de directorios de un proyecto (a través de los archType), de modo que al conocer la estructura de un proyecto basado en maven se conoce la de cualquiera con la misma base. • Permite la migración transparente hacia nuevas características. Provee una manera sencilla de actualizar una instalación de maven, con lo cual incorporar nuevas características resulta trivial. Del mismo modo, las actualizaciones sobre plugins se pueden incorporar de forma muy fácil. No resulta sencillo definir lo que es maven, para clarificar la situación, debemos tener en claro que maven no es: 24 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier • Una herramienta para generar sitios y documentación. • Una extensión de Ant para descargar dependencias. • Un conjunto de scriptles Ant reusables. Si bien maven sirve para realizar cualquiera de esas tareas, éstas no son sus únicas características y sus objetivos son algo diferentes. Muy a grandes rasgos, las siguientes son las principales características de maven: • Creación de proyectos simple y siguiendo las mejores prácticas. En segundos se puede crear un nuevo proyecto o módulo. • Uso consistente entre diferentes proyectos, lo cual implica que la curva de adaptación de un nuevo desarrollador a un proyecto es relativamente pequeña. • Administración de dependencias superior, que incluye actualización automática y resolución de dependencias transitivas. • Permite trabajar con diferentes proyectos al mismo tiempo. • Repositorios oficiales en crecimiento con una gran cantidad de librerías listas para ser utilizadas. • Es extensible, permite escribir plugins en el lenguaje Java y otros lenguajes de scripting. • Acceso inmediato a nuevas características, en general sin configuración extra. • Integración con Ant, para manejo de dependencias y deploy fuera de maven. • Builds basados en modelos. Permite compilar cualquier número de proyectos en diferentes tipos de salida, como jar, war, distribuciones basadas en metadata sobre el proyecto. • Sitio con información sobre el proyecto. Permite crear un sitio o documento pdf que incluya cualquier información que se desee. • Administración y distribución de entregables. Sin configurar demasiado, puede conectarse el sistema de control de versiones y generar un paquete basado en una determinada etiqueta. También puede publicar artefactos utilizados por otros proyectos. • Manejo de dependencias. Recomienda el uso de un repositorio central de archivos jar y otro tipo de dependencias. Viene con un mecanismo integrado para descargar todas las dependencias que un proyecto requiera desde ese repositorio. Esto permite a los usuarios maven reutilizar librerías en diferentes proyectos. Maven se base en el concepto de ciclo de vida, el proceso para construir y distribuir un artefacto (proyecto) está claramente definido. 25 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Hay tres ciclos de vida incorporados: default, clean y site. El ciclo de vida default maneja hasta el deploy de un proyecto. El ciclo de vida clean maneja la “limpieza” (eliminación de artefactos generados de la carpeta target) de un proyecto. El ciclo de vida site maneja la creación de un sitio de documentación para el proyecto. Un ciclo de vida está compuesto por fases. Cada ciclo de vida está compuesto por una lista de diferentes fases, cada fase representa una etapa en el ciclo de vida. Por ejemplo, el ciclo de vida default está compuesto por las siguientes fases: • Validate. Validar que el proyecto es correcto y que se encuentra toda la información necesaria para proseguir. • Compile. Compilar el código fuente del proyecto. • Test. Testear el código compilado usando un framework de testing unitario adecuado. • Package. Tomar el código compilado y empaquetarlo en su forma para distribuir, por ejemplo en un archivo jar. • Integration-test. Procesar y si es necesario publicar en un entorno en el cual los tests de integración puedan correr. • Verify. Hacer chequeos para verificar que el paquete es válido y cumple con los criterios de calidad. • Install. Instalar el paquete en el repositorio local, para poder ser utilizado por otros proyectos localmente. • Deploy. Copiar el paquete final en un repositorio remoto para poder ser compartido con otros desarrolladores y usado en otros proyectos. Estas fases, a demás de otras que no están especificadas, corren en orden. Desde la primera hasta la que se requiera. Es decir, que para que absolutamente todo esto ocurra es suficiente con invocar a maven con la última fase a ser ejecutada, en este caso: mvn deploy Esto es porque cuando se invoca la ejecución con una fase, no sólo se ejecutará esa fase, si no también todas las anteriores en el ciclo de vida. Cabe destacar que los mismos comandos también se pueden usar en proyectos multi módulos (con uno o más subproyectos), por ejemplo : mvn clean install Correrá clean y luego install (incluyendo todas las fases anteriores) para cada uno de los subproyectos en orden. Una fase está compuesta por metas. Asociadas a cada fase hay un conjunto de metas. 26 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Una meta representa una tarea (más pequeña que una fase), que contribuye a la construcción y administración de un proyecto. Una meta puede estar ligada a cero o más fases. Una meta que no está ligada a ninguna fase puede ser ejecutada por fuera del ciclo de vida mediante una invocación directa. Por ejemplo, si consideramos el siguiente comando, los argumentos clean y package son fases, mientras que dependency:copydependencies es una meta. mvn clean dependency:copy-dependencies package Primero, se ejecutará la fase clean (se van a ejecutar todas las fases anteriores, más la fase en sí misma). Luego se ejecutará la meta depandency:copy-dependencies, para finalmente ejecutar la fase package (y todas las fases que le preceden en el ciclo de vida default). Además, si una meta se liga a más de una fase, entonces se va a ejecutar por cada una de ellas. Por otro lado, una fase puede tener cero o más metas asociadas. Si no tiene metas, entonces esa fase no ejecutará. Pero si tiene ligadas una o más metas, cuando se ejecute la fase, van a ejecutar todas esas metas. La manera más común de comenzar un proyecto es indicando el tipo de paquete que se desea en el archivo pom, algunos tipos válidos son jar, war, ear y pom. Si no se indica ninguno maven asume jar por default. Cada tipo de paquete tiene metas asociadas a diferentes fases del ciclo de vida, por ejemplo el tipo jar asocia metas de la siguiente manera. process-resources compile process-test-resources test-compile test package install deploy resources:resources compiler:compile resources:testResources compiler:testCompile surefire:test jar:jar install:install deploy:deploy Otros tipos de paquetes ligan metas de manera muy diferente, por ejemplo en un proyecto donde todo el contenido es meta data (el valor del nodo <packaging> es pom) sólo hay metas ligadas a las fases install y deploy. La segunda forma de agregar metas a las fases es configurando plugins a la sección <build> del archivo pom. Los plugins son artefactos que proveen de metas a maven. Cada plugin puede tener varias metas, cada una de ellas representa una capacidad diferente del plugin. Por ejemplo, el plugin Compiler tiene dos metas: compile y testCompile. La primera compila el código fuente de la aplicación, mientras que la segunda compila el código fuente asociado a los tests de la aplicación. Para clarificar el uso de los plugins, veamos una configuración típica. En el siguiente ejemplo, el plugin modello liga la meta modello:java a la fase generate-sources. 27 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier ... <plugin> <groupId>org.codehaus.modello</groupId> <artifactId>modello-maven-plugin</artifactId> <version>1.4</version> <executions> <execution> <configuration> <models> <model>src/main/mdo/maven.mdo</model> </models> <version>4.0.0</version> </configuration> <goals> <goal>java</goal> </goals> </execution> </executions> </plugin> ... El elemento <execution> brinda la posibilidad de que una meta pueda ser ejecutada múltiples veces con diferentes configuraciones. También se puede dotar a la ejecución de un identificador, de este modo, cuando hay herencia de archivos pom se puede controlar como se mezclan estas ejecuciones. Cuando múltiples ejecuciones están ligadas a una fase particular, son ejecutadas en el orden en el que fueron especificadas en el archivo pom, con ejecuciones heredadas primero. Algunas metas pueden ser ejecutadas en distintas fases, incluso pueden no estar ligadas por default a una fase, en ese caso queda como responsabilidad del desarrollador asociar las metas a la fase que lo desee. Por ejemplo, si tuviéramos una meta display:time que imprime por la salida estándar la hora actual, y quisiéramos ejecutarla en la fase testresources para indicar cuando comienzan los tests, podríamos tener una configuración como sigue. ... <plugin> <groupId>com.mycompany.example</groupId> <artifactId>display-maven-plugin</artifactId> <version>1.0</version> <executions> <execution> <phase>process-test-resources</phase> <goals> <goal>time</goal> </goals> </execution> </executions> </plugin> ... Para mayor detalle sobre el ciclo de vida maven consultar la especificación en el sitio de apache: http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html Maven Plugins Hemos adaptado y configurado Maven en todos los sentidos que creímos convenientes 28 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier para construir nuestros proyectos. A continuación intentaremos explicar y ejemplificar de que manera utilizamos ciertos plugins adicionales, así como también configuramos algunos de los que vienen por default. Clean El plugin clean se encarga de eliminar archivos o directorios generados en compilaciones anteriores. Cuenta sólo con una meta: clean:clean • Intenta eliminar el directorio de trabajo del proyecto clean:clean. Intenta eliminar el directorio de trabajo del proyecto. Algunos de nuestros proyectos ejecutan tests que generan un escenario, es decir un directorio con librerías, templates, archivos de entrada y un directorio para la salida y corren el generador con este escenario. Hemos configurado el plugin clean para limpiar este escenario de la siguiente manera: <plugin> <artifactId>maven-clean-plugin</artifactId> <version>2.4.1</version> <executions> <execution> <id>scenario-clean</id> <phase>process-test-resources</phase> <goals> <goal>clean</goal> </goals> <configuration> <excludeDefaultDirectories>true</excludeDefaultDirectories> <filesets> <fileset> <directory>${testing.scenario.path}</directory> </fileset> </filesets> </configuration> </execution> </executions> </plugin> Esta configuración se puede ver en uno de los archivos pom del proyecto CodeGeneratorPoms. Este archivo es el padre de casi todos los archivos de configuración de nuestros proyectos, por lo que el clean se ejecutará en cualquiera de ellos sin necesidad de ninguna otra configuración. El directorio que intenta limpiar está definido como una propiedad del proyecto, se puede ver en la sección <properties> del mismo archivo, esta propiedad puede ser redefinida por archivos pom hijos. La meta clean está asociada a la fase process-test-resources. Con lo cual es ejecutada siempre antes de correr los tests y elimina escenarios creados anteriormente. Properties El plugin properties sirve para leer y escribir propiedades, desde y hacia archivos. El uso más común es leer las propiedades definidas en el archivo pom y escribirlas en otro 29 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier archivo (por ejemplo de extensión .properties) para que puedan ser fácilmente utilizadas desde nuestro código java. Cuenta con las siguiente metas: properties:help properties:read-project-properties properties:set-system-properties properties:write-active-profileproperties properties:write-project-properties Muestra información de ayuda sobre como invocar Lee archivos de propiedades y las escribe como propiedades de proyecto Setea propiedades de sistema Escribe propiedades de un perfil activo a un archivo Escribe propiedades de proyecto a un archivo Si miramos el archivo pom padre definido en el proyecto CodeGeneratorPoms, hay varias propiedades definidas a nivel proyecto: <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <freelancesoft.codegenerator.version>1.0-SNAPSHOT</freelancesoft.codegenerator.version> <freelancesoft.codegenerator.lib.version>1.0SNAPSHOT</freelancesoft.codegenerator.lib.version> <freelancesoft.codegenerator.lib.path>$ {user.home}/.codegenerator/lib</freelancesoft.codegenerator.lib.path> <freelancesoft.codegenerator.tpl.path>$ {user.home}/.codegenerator/tpl</freelancesoft.codegenerator.tpl.path> <check.style.config.location>http://devel.freelancesoft.com:8080/fs_checks.xml</check.style.config.location> <testing.scenario.path>${build.directory}/scenario</testing.scenario.path> <testing.scratch.path>scratch</testing.scratch.path> <testing.lib.path>lib</testing.lib.path> <testing.tpl.path>tpl</testing.tpl.path> <testing.target.path>target</testing.target.path> <testing.lib.res.path>${basedir}/src/main/resources/lib</testing.lib.res.path> <testing.tpl.res.path>${basedir}/src/main/resources/tpl</testing.tpl.res.path> </properties> Estas propiedades son utilizadas para diversos fines, entre otros, las utilizan muchos casos de test para crear el escenario sobre el cual correrá el generador. Para que estas propiedades sean de fácil acceso para nuestros casos de test, las debemos copiar en el target. Esto se puede hacer con la siguiente configuración de plugin: <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>properties-maven-plugin</artifactId> <executions> <execution> <phase>process-test-resources</phase> <goals> <goal>write-project-properties</goal> </goals> <configuration> <outputFile>${project.build.testOutputDirectory}/project.properties</outputFile> </configuration> </execution> </executions> </plugin> 30 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Definiendo el siguiente método en la clase base para los tests: @BeforeClass public static void setUpClass() throws Exception { poperties = new java.util.Properties(); URL url = ParserTestBase.class.getClassLoader().getResource("project.properties"); if (url == null) { throw new Exception("Project properties are missed"); } else { InputStream stream = url.openStream(); poperties.load(stream); stream.close(); } } Ya contamos con las propiedades de proyecto accesibles desde cualquier caso de prueba y las podemos usar, por ejemplo, para la creación de escenarios. Surefire Surefire es el plugin usado durante la fase test del ciclo de vida para ejecutar los casos de prueba de una aplicación. Cuenta con dos metas: surefire:help surefire:test Muestra información del plugin Corre las pruebas unitarias Surefire es sumamente flexible, podemos encontrar una configuración en el proyecto CodeGenerator: <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <configuration> <includes> <include>**/ParserTestSuite.java</include> </includes> </configuration> </plugin> En la cual indicamos al plugin que casos de prueba debe ejecutar. En este caso se configura para que no ejecute absolutamente todos los métodos de test que encuentre, ya que en el proyecto también existen algunos casos de prueba de stress (Para tomar algunas medidas, como nivel máximo de clases heredadas que soporta nuestro lenguaje), no deseamos que esos tests sean ejecutados con cada compilación. Además de fallar el build si algún caso de prueba no es exitoso, surefire también sirve para generar reportes sobre estos casos de prueba. Genera reportes en dos formatos, texto plano y xml. Para que se generen estos reportes se debe agregar el siguiente nodo a la sección <reporting> del archivo pom: <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> </plugin> 31 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier El reporte en texto plano tiene información como la que sigue: ------------------------------------------------------------------------------Test set: freelancesoft.codegenerator.parser.ParserTestSuite ------------------------------------------------------------------------------Tests run: 442, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 14.074 sec Mientras que el reporte en formato xml contiene información de cada uno de los casos de prueba que se ejecutó. Si quisiéramos un reporte en formato html, más legible para seres humanos, lo podríamos generar con otro plugin: http://maven.apache.org/plugins/mavensurefire-report-plugin/ Assembly El objetivo principal del plugin assembly es crear un archivo simple que contenga todas las dependencias, módulos, documentación y otros archivos de un proyecto. De este modo se puede distribuir ese archivo como un único artefacto. Un proyecto puede construir un “assembly” fácilmente utilizando uno de los descriptores predefinidos. Existen descriptores para ejecutar operaciones comunes como la creación de un archivo de tipo zip que contenga todos los artefactos generados junto con la documentación. Pero también existe la posibilidad de crear descriptores propios, lo cual brinda un control mucho mayor sobre cómo las dependencias, módulos, conjuntos de archivos y archivos individuales, son empaquetados en un assembly. El plugin assembly cuenta con las siguientes metas: assembly:help assembly:single Muestra información a cerca del plugin Crea un artefacto a partir de un descriptor También tiene otras metas a las que se les dejó de dar soporte, en su mayoría porque la meta single cumple con los objetivos. En otros casos porque esa funcionalidad se agregó al plugin dependency. En nuestro caso particular es utilizado para los siguiente. El generador de código utiliza librerías y templates. Las librerías pueden depender de otras librerías, a su vez los templates pueden depender de otras librerías y templates. Hemos configurado todo nuestro proceso para que las librerías y templates se manejen de un modo similar a un artefacto de tipo jar. Es decir: • Una librería o template es empaquetada, mediante el plugin assembly, en un archivo de tipo zip. • A su vez, declara en el archivo pom las librerías o templates de las que depende. • Cuando ejecutamos un build sobre un proyecto (de librería o template), maven resuelve las dependencias y mediante el plugin depency, las desempaqueta en un 32 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier directorio especificado. Si somos cuidadosos a la hora de marcar las dependencias, este proceso de resolución es automático. Con lo cual, es muy simple descargar y utilizar estos artefactos. En los archivos pom de nuestros proyectos de librerías y templates, contamos con una sección como la siguiente: <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptors> <descriptor>dep.xml</descriptor> </descriptors> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> Y su respectivo descriptor: <assembly> <formats> <format>zip</format> </formats> <includeBaseDirectory>false</includeBaseDirectory> <fileSets> <fileSet> <directory>${basedir}/src/main/resources/tpl</directory> <outputDirectory></outputDirectory> </fileSet> </fileSets> </assembly> Con esta simple configuración, en la fase package, se genera el archivo zip con nuestros archivos escritos en nuestro propio lenguaje. Además ese archivo es subido a los repositorios durante la fase deploy. Dependency El plugin dependency brinda la posibilidad de manipular artefactos. Puede copiar y/o descomprimir artefactos desde repositorios locales o remotos a directorios específicos. Cuenta con las siguientes metas: dependency:analyze dependency:analyze-dep-mgt Analiza las dependencias del proyecto y determina cuáles son usadas y declaradas, usadas y no declaradas, no usadas y declaradas Analiza las dependencias y lista las que no coinciden con las declaradas en la 33 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier sección dependencyManagement dependency:analyze-only Igual que analyze, pero no compila dependency:analyze-report Analiza las dependencias del proyecto y genera un reporte que incluye cuáles son usadas y declaradas, usadas y no declaradas, no usadas y declaradas Analiza las dependencias y las declaradas en el nodo dependencyManagement y determina dependencias duplicadas dependency:analyze-duplicate dependency:build-classpath Indica a maven que debe generar paths a los repositorios locales para ser usados en el comando “java -cp” dependency:copy Toma la lista de artefactos definidos en la sección plugin y los copia a un directorio específico, puede renombrar o agregar número de versión Toma la lista de dependencias, directas o transitivas y las copia en un directorio específico, puede renombrar o agregar número de versión Descarga un artefacto, con dependencias transitivas desde un repositorio remoto dependency:copy-dependencies dependency:get dependency:go-offline dependency:list Indica a maven que resuelva todas las dependencias del proyecto (dependencias, plugins y reportes) preparándose para pasar a modo offline Alias para resolve, que además lista las dependencias dependency:properties Setea una propiedad para cada dependencia con la ubicación en el sistema de archivos dependency:purge-local-repository Indica a maven que debe eliminar del repositorio local todos los artefactos correspondientes a dependencias y opcionalmente resolverlas nuevamente Indica a maven resolver dependencias y mostrar el número de versión dependency:resolve dependency:resolve-plugins Indica a maven resolver plugins, con todas sus dependencias 34 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier dependency:sources Indica a maven resolver dependencias, con el artefacto de código fuente y mostrar el número de versión dependency:tree Muestra el árbol de dependencias del proyecto dependency:unpack Como copy, pero también descomprime dependency:unpack-dependencies Como copy-dependencies, pero también descomprime Además del uso típico, hemos configurado este plugin para que pueda lidiar con artefactos que corresponden a paquetes de librerías o templates, escritos en nuestro propio lenguaje. Podemos ver este tipo de configuración en archivos pom como el que es utilizado como padre para los archivos de configuración de proyectos de template: <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <executions> <execution> <id>unpack-lib</id> <phase>process-test-resources</phase> <goals> <goal>unpack-dependencies</goal> </goals> <configuration> <includeTypes>zip</includeTypes> <includeGroupIds>freelancesoft.codegenerator.lib</includeGroupIds> <outputDirectory>${freelancesoft.codegenerator.lib.path}</outputDirectory> <overWriteReleases>true</overWriteReleases> <overWriteSnapshots>true</overWriteSnapshots> </configuration> </execution> <execution> <id>unpack-tpl</id> <phase>process-test-resources</phase> <goals> <goal>unpack-dependencies</goal> </goals> <configuration> <includeTypes>zip</includeTypes> <includeGroupIds>freelancesoft.codegenerator.tpl</includeGroupIds> <outputDirectory>${freelancesoft.codegenerator.tpl.path}</outputDirectory> <overWriteReleases>true</overWriteReleases> <overWriteSnapshots>true</overWriteSnapshots> </configuration> </execution> </executions> </plugin> Con esta configuración, todos los artefactos declarados como dependencias, incluyendo 35 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier las dependencias transitivas, son descargados desde los repositorios y descomprimidos en los directorios que corresponde antes de la ejecución de los casos de prueba. Otro aspecto interesante de este plugin es la generación de un reporte sobre las dependencias del proyecto. Si ejecutamos: mvn site En el sitio generado del proyecto veremos una sección “Dependencies” que contiene información muy útil: 36 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier 37 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Check-Style El plugin check-style genera un reporte acerca del estilo de programación de los desarrolladores. No sólo es capaz de generar un reporte, si no que también se lo puede configurar para que falle la compilación si no se siguen ciertas convenciones preestablecidas en cuanto al estilo de programación. Para hacer los chequeos utiliza una herramienta externa. Gracias a este plugin podemos asegurar un estilo uniforme, aún cuando varias personas trabajen en el proyecto. El plugin cuenta con las siguientes metas: checkstyle:checkstyle Realiza un análisis del estilo sobre el código y genera un informe con las violaciones checkstyle:check Realiza un chequeo de violaciones sobre la última corrida, lee el archivo de salida de CheckStyle cuenta el número de violaciones y las muestra por consola En nuestro caso, no hemos utilizado este plugin para todos los proyectos, pero sí para los que consideramos importante que se sigan ciertos lineamientos en cuanto al estilo de programación, como el proyecto CodeGenerator y el proyecto CodeGeneratorCommon. Un ejemplo de como configuramos el plugin es el siguiente: <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> <configuration> <logViolationsToConsole>false</logViolationsToConsole> <consoleOutput>true</consoleOutput> <propertyExpansion>basedir=${basedir}</propertyExpansion> <configLocation>${check.style.config.location}</configLocation> <excludes> **/CodeGeneratorParserBase.java, **/CodeGeneratorParserBaseTreeConstants.java, **/JJTCodeGeneratorParserBaseState.java, **/TokenMgrError.java, **/CodeGeneratorParserTreeConstants.java, **/Token.java, **/JavaCharStream.java, **/CodeGeneratorParserBaseTokenManager.java, **/CodeGeneratorParserBaseConstants.java, **/ParseException.java </excludes> </configuration> <executions> <execution> <id>check</id> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> De este modo, check-style queda configurado para fallar si no se siguen nuestras convenciones. A demás también se puede ver que excluimos ciertas clases del chequeo 38 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier de estilo, esto es porque son clases generadas por JavaCC y no es posible lograr total uniformidad sobre las mismas. En cuanto a las reglas que hemos definido, se puede apreciar una propiedad de proyecto, el valor default de esa propiedad está dado en el archivo pom padre. Nuestras reglas se pueden ver en http://devel.freelance-soft.com/xml/fs_checks.xml. Básicamente tomamos el archivo sun_checks.xml que viene en el descargable de CheckStyle y relajamos o modificamos diferentes reglas. Algunas de las modificaciones que aplicamos son las siguientes: • Quitamos el módulo NewlineAtEndOfFile. Con este módulo se exige que cada archivo de código fuente termine en un carácter de nueva línea. Directamente quitamos el módulo porque no funcionaba bien al modificar y compilar código fuente en distintas plataformas (windows y linux). • Los módulos JavadocMethod, JavadocType y JavadocVariable se encargan de chequear que cada método, tipo o variable respectivamente tenga el correspondiente comentario JavaDoc. Para estos módulos modificamos una propiedad para que se permita violar la regla en métodos, tipos y variables privadas. • El módulo ConstantName exige que los nombres de constantes estén escritos en mayúsculas. Modificamos propiedades, para que no se produzca una violación si no se respeta la regla para variables privadas o protegidas. • El módulo AvoidStarImport evita imports que finalicen con un carácter asterisco. Agregamos una excepción para “org.junit.Assert.*”, ya que este paquete se usa únicamente para los tests y resulta conveniente importarlo de este modo. • El módulo LineLength se encarga de chequear que ninguna línea supere una determinada cantidad de caracteres. Aumentamos esa cantidad de caracteres a 185. • Eliminamos el módulo AvoidInlineConditionals, con el cual no es posible definir condicionales en una única línea, con lo cual resultaría imposible utilizar el operador ternario “?”. • El módulo HiddenField evita que se oculten campos, un caso típico en el que se ocultan campos es cuando una variable de un método tiene el mismo nombre que un atributo interno. Agregamos propiedades al módulo para que no realice chequeos sobre constructores o métodos setter. • Quitamos el módulo FinalParameters, que chequea que métodos, constructores, bloques catch y foreach tengan sus parámetros declarados como final. • Quitamos el módulo TodoComment, con el cual no es posible dejar comentario “TODO” en el código. Este módulo es de utilidad si lo único que queremos es generar el reporte sobre estilos, pero no es posible utilizarlo si queremos que el build falle cuando no se respeta alguna regla. 39 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier • Agregamos el módulo AvoidStaticImport, que evita importaciones de paquetes estáticos, con una propiedad para que se permita “org.junit.Assert.*” ya que es la manera más simple para trabajar sobre los casos de test. • El módulo ClassDataAbstractionCoupling mide el número de instancias de otras clases, dentro de una clase dada. Este módulo fue agregado con un valor máximo de 100. • El módulo ClassFanOutComplexity mide el nivel de herencia para una determinada clase. Este módulo fue agregado con un máximo de 20. • El módulo CyclomaticComplexity chequea complejidad ciclomática. Fue agregado con un máximo de 15. • Se agregó el módulo DefaultComesLast, que chequea que el default esté después de todos los casos en un switch. • Se agregó el módulo MethodCount que chequea el número de métodos dentro de una clase. • Se agregó el módulo ModifiedControlVariable que asegura que la variable de control de un ciclo for no sea modificada en el cuerpo del mismo. • Se agregó el módulo NpathComplexity que chequea otra medida de complejidad, similar a la ciclomática. • Además también se hicieron otras modificaciones de menor importancia. Como dijimos anteriormente, el objetivo principal de este plugin es la generación de un reporte sobre el estilo de programación de los desarrolladores, para que este reporte sea generado se debe agregar lo siguiente a la sección <reporting> del archivo pom: <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-checkstyle-plugin</artifactId> <configuration> <logViolationsToConsole>false</logViolationsToConsole> <consoleOutput>true</consoleOutput> <propertyExpansion>basedir=${basedir}</propertyExpansion> <configLocation>${check.style.config.location}</configLocation> <excludes> **/CodeGeneratorParserBase.java, **/CodeGeneratorParserBaseTreeConstants.java, **/JJTCodeGeneratorParserBaseState.java, **/TokenMgrError.java, **/CodeGeneratorParserTreeConstants.java, **/Token.java, **/JavaCharStream.java, **/CodeGeneratorParserBaseTokenManager.java, **/CodeGeneratorParserBaseConstants.java, **/ParseException.java </excludes> </configuration> </plugin> Con esta configuración, si ejecutamos: 40 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier mvn site Encontraremos un reporte en el sitio generado. Este reporte cuenta con varias secciones, una de ellas muestra las reglas aplicadas. Las demás son referentes a las violaciones encontradas: A demás de esta herramienta también hemos analizado el uso, como alternativa o complemento, del plugin pmd. Hicimos algunas pruebas de concepto, llegando a las siguientes conclusiones: • Pmd intenta chequear, no solo estilo de programación, si no también posibles errores. • CheckStyle es más certero y configurable, en lo que a estilo se refiere. • En lo que respecta a la búsqueda de posibles errores en el código, existen otras herramientas, como FindBug, que son más adecuadas. Es decir producen resultados que a nuestro criterio son más útiles. • Preferimos utilizar una herramienta que se ocupe de tareas más pequeñas y de una manera más adecuada, en lugar de una que intente abarcar más de un 41 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier aspecto de un problema. • Es posible utilizar CheckStyle y Pmd en conjunto, pero hay muchísimas reglas que se solapan y no consideramos adecuado hacer un doble chequeo sobre las mismas. Con lo cuál el mantenimiento de las reglas que se desean aplicar sería mucho más costoso. Por estas razones hemos decidido, de momento, descartar el uso de Pmd y optar por la combinación CheckStyle-FindBugs. FindBugs FindBugs busca errores en programas java, está basado en el concepto de patrones de errores. Un patrón de error es una porción de código, que generalmente se corresponde a un error. Los patrones de errores surgen por varias razones: • Características complejas del lenguaje. • Métodos que suelen entenderse mal. • Modificaciones durante el mantenimiento que introducen errores comunes. • Otros errores comunes, como tipográficos, mal uso de operadores booleanos, etc. FindBugs usa análisis estático para inspeccionar bytecode java en busca de patrones de errores. Está comprobado que en la mayoría de los casos encuentra errores reales, por el tipo de análisis que hace, muchas veces reporta falsos errores (pero el porcentaje suele ser menor al %50). El plugin de maven que da soporte a FindBugs brinda las siguientes metas: findbugs:check findbugs:findbugs Falla el build si existe alguna violación a FindBugs, por default crea un reporte en el directorio de compilación Genera un reporte, por default corre en la fase site findbugs:gui Ejecuta una interfaz de usuario que sirve para depurar los posibles errores findbugs:help Muestra ayuda sobre el plugin Al igual que CheckStyle, no utilizamos FindBugs sobre todos nuestros proyectos, de momento está configurado para correr sobre CodeGenerator y CodeGeneratorCommon. Una configuración típica, en el archivo pom de un proyecto, se ve como sigue: <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>findbugs-maven-plugin</artifactId> <configuration> 42 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier <fork>false</fork> <excludeFilterFile>findbugs-exclude.xml</excludeFilterFile> </configuration> <executions> <execution> <id>check</id> <goals> <goal>check</goal> </goals> </execution> </executions> </plugin> Con esto, el plugin falla el build si encuentra violaciones. A demás, si se encuentran violaciones se puede ejecutar: mvn findbugs:gui Con lo cual veremos una interfaz de usuario sumamente útil para depurar los errores: Por otra parte, también se puede utilizar findbugs para generar un reporte en el sitio de proyecto a cerca de los posibles errores que se hayan encontrado. Para esto se debe agregar la siguiente configuración al archivo pom en la sección <reporting>: <plugin> 43 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> </plugin> Con lo cual encontraremos las siguientes secciones en el reporte generado en el sitio de proyecto: En conclusión, el uso de la combinación FindBugs-CheckStyle posibilita el mantenimiento de código uniforme y libre de errores comunes. Cobertura Este plugin provee a maven de la capacidad de utilizar la Cobertura. Cobertura es una herramienta gratuita, que calcula el porcentaje de código que ha sido probado por los tests unitarios automáticos. Es de utilidad para detectar porciones de código que carecen de pruebas suficientes. Cobertura posee las siguientes características: • Puede ser ejecutado desde ant, maven o línea de comandos. • Instrumenta bytecodes java, luego de que el código fue compilado. • Puede generar reportes en formatos html o xml. • Muestra los porcentajes, en cuanto a líneas y ramas que fueron cubiertos en cada clase, paquete y sobre el proyecto completo. • Muestra la complejidad ciclomática de McCabe de cada clase, paquete, y sobre el proyecto completo. 44 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier • El resultado html puede ser ordenado de forma ascendente o descendente en cuanto a nombre de clase, porcentaje de líneas cubiertas, porcentaje de ramas cubiertas, etc. Para configurar este plugin, debemos agregar las siguientes líneas al archivo pom: <configuration> <check> <haltOnFailure>true</haltOnFailure> <branchRate>0</branchRate> <lineRate>85</lineRate> <totalBranchRate>0</totalBranchRate> <totalLineRate>78</totalLineRate> <packageLineRate>80</packageLineRate> <packageBranchRate>0</packageBranchRate> <regexes> ... <regex> <pattern>.*UnnamedVariable</pattern> <lineRate>80</lineRate> <branchRate>0</branchRate> </regex> </regexes> </check> </configuration> Como puede apreciarse en la configuración, hemos decidido implementar cobertura de líneas, pero no de ramas ya que nos parece una exigencia extrema. Configurado de este modo, el plugin falla el build en el caso de que no se cumplan los porcentajes de cobertura deseados. También cuenta con la posibilidad de agregar un reporte al sitio del proyecto, para que este reporte sea generado debemos agregar en la sección <reporting> del archivo pom las siguientes líneas: <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>cobertura-maven-plugin</artifactId> </plugin> Luego, si ejecutamos: mvn site En el sitio generado encontraremos un nuevo reporte con datos sumamente útiles: 45 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Lo interesante de este reporte es que, no sólo muestra porcentaje de cobertura, si no que los nombres de clase están directamente asociados al código fuente y además muestra con diferentes colores líneas cubiertas y no cubiertas: Existe una alternativa a Cobertura que hemos evaluado, es la herramienta Emma. Emma hace prácticamente lo mismo que Cobertura, trabajan de similar manera (instrumentando a nivel bytecode), también cuenta con plugin para maven y posee casi todas las mismas características. Luego de algunas pruebas de concepto, hemos optado por Cobertura, por 46 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier las siguientes razones: • Aparentemente el proyecto principal de Cobertura está siendo mantenido de una manera mucho más activa que el de Emma. • Cobertura cuenta con integración directa con NetBeans, se pueden ver directamente en el IDE los reportes generados, incluso marca con diferentes colores el nivel de cobertura directamente sobre el código fuente. • El reporte generado por Cobertura es mucho más rico que el que genera Emma. • Cobertura posee una capacidad mayor para ser configurable. • Cobertura toma más medidas que Emma. • La única ventaja que encontramos de Emma sobre Cobertura, es que es más eficiente a la hora de instrumentar. Con esto se logra ejecutar un build de manera más rápida, de momento eso no es problema para nosotros. Con el uso de esta herramienta, podemos asegurar que en la mayoría de nuestros proyectos hemos logrado una cobertura mayor al %85. Esto teniendo en cuenta que medimos cobertura de líneas y que a demás, contamos con excepciones para ciertas clases, para las cuales no es posible llegar a ese número. Esto es debido a que tenemos clases generadas por JavaCC, con líneas prácticamente imposibles de cubrir. También tenemos porciones de código que surgen de programar a la defensiva, es decir, chequear errores que jamás se deberían dar. AntRun Apache ant es una librería y línea de comando que sirve para manejar el proceso descripto en un archivo de build. El uso principal de esta herramienta es construir aplicaciones Java. Provee tareas predefinidas para compilar, ensamblar, testear y correr aplicaciones Java. También puede ser utilizado para aplicaciones no Java, por ejemplo C o C++. Más precisamente, ant puede ser utilizado para cualquier proceso que pueda ser descripto en términos de objetivos y tareas. El plugin AntRun provee la habilidad de correr tareas ant desde maven. Incluso se pueden embeber scripts ant dentro del archivo pom. La intención del plugin no es ensuciar el archivo pom, por lo que se recomienda (para grandes definiciones), escribir las tareas en un archivo build.xml y utilizarlo dentro del archivo pom. Uno de los objetivos principales del plugin, es facilitar la migración de proyectos desde ant a maven. Algunos proyectos pueden estar utilizando funcionalidad a medida escrita para ant y que no soporta maven por default. El plugin provee de las siguientes metas: antrun:help antrun:run Muestra información de ayuda acerca del plugin Permite correr tareas ant Uno de nuestro proyectos, fs-schemas, consiste de un conjunto de distintos archivos xml. 47 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Uno de ellos (fs-checks.xml) es el que define nuestro estilo de programación y es el que utiliza el plugin CheckStyle. Los demás son los esquemas que describen archivos xml válidos para utilizar como entrada del generador de código. Todos estos archivos deben estar disponibles a través de la red para poder ser utilizados. Para actualizar estos archivos en el servidor utilizamos el protocolo FTP. Para poder subir los archivo vía FTP consideramos un plugin para maven llamado Maven Wagon FTP, este requiere de librerías adicionales dentro del repositorio maven, lo cual nos pareció un despropósito. Por eso decidimos utilizar tareas ant para subir los archivos. Con la siguiente configuración, maven utiliza ant para realizar esta tarea: <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <id>ftp-xml-install</id> <phase>install</phase> <configuration> <tasks> <ftp action="del" server="${ftp.xml.server}" remotedir="/var/www/html/xml" userid="${ftp.xml.user}" password="${ftp.xml.pass}" depends="yes" verbose="yes"> <fileset> <include name="**" /> </fileset> </ftp> <ftp action="rmdir" server="${ftp.xml.server}" remotedir="/var/www/html/xml" userid="${ftp.xml.user}" password="${ftp.xml.pass}" depends="yes" verbose="yes"> <fileset> <include name="**" /> </fileset> </ftp> <ftp action="send" server="${ftp.xml.server}" remotedir="/var/www/html/xml" userid="${ftp.xml.user}" password="${ftp.xml.pass}" depends="yes" verbose="yes"> <fileset dir="${project.build.outputDirectory}"> <include name="**" /> </fileset> </ftp> </tasks> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> <dependencies> <dependency> <groupId>commons-net</groupId> <artifactId>commons-net</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>ant</groupId> <artifactId>ant-commons-net</artifactId> <version>1.6.5</version> </dependency> <dependency> <groupId>ant</groupId> <artifactId>ant-nodeps</artifactId> <version>1.6.5</version> </dependency> </dependencies> </plugin> 48 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier De este modo ejecutando la fase install, mantenemos actualizados todos nuestros esquemas y el archivo con políticas para CheckStyle, el cual puede ser reutilizado entre diferentes proyectos ya que se encuentra disponible en línea. Tomcat Casi todas las herramientas que se utilizan hoy en día requieren de cierto soporte del lado servidor. Casi todos los manejadores de repositorios maven y administradores para la integración continua están implementados sobre la base de Java Servlet y/o JavaServer pages. Una buena pregunta es: Quién dará soporte a estas últimas dos tecnologías para que podamos instalar el resto de las aplicaciones que deseamos utilizar? En el mercado existen varias implementaciones exitosas, algunas de las más conocidas son Tomcat, Glassfish, Jboss y Jetty. Sobre las tres hemos hecho algunas pruebas de concepto instalando diferentes aplicaciones. Si buscamos en internet, podremos encontrar varios cuadros comparativos y diversas opiniones sobre todos estos servidores de aplicaciones. No nos detendremos en un listado exhaustivo de sus características. Simplemente intentaremos listar las razones que nos llevaron a seleccionar tomcat como nuestro servidor de aplicaciones. • En líneas generales, JBoss y Jetty son las que menos características ofrecen. Pero son ideales para lograr una aplicación standalone, es decir para correr una aplicación simplemente ejecutando el distribuible que se descarga desde la página del autor. De hecho, una gran cantidad de estas aplicaciones ofrecen la descarga de un archivo jar, con Jetty integrado, simplemente con ejecutar el jar tenemos nuestra aplicación funcionando y podemos accederla desde un navegador bajo un puerto específico. Dado que tendremos varias aplicaciones corriendo, nos pareció conveniente tener sólo una instancia de servidor, que nos brinde la posibilidad de un administrador mediante el cual podamos ejecutar y parar las diversas aplicaciones. Por todo esto, descartamos Jboss o Jetty, creemos que su aspecto más interesante es para embeberlo y lograr aplicaciones standalone. • Glassfish es un producto mucho más nuevo que Tomcat. Viene en sus dos versiones, edición open source y oracle. Mientras que Tomcat es un producto de apache y es totalmente código abierto. Aquí surgen varios puntos a tener en cuenta. Glassfish parece ser sencillo y sumamente flexible. Por ser más nuevo podemos pensar que Tomcat es más estable, pero Glassfish se está desarrollando más activamente. Yendo al grano, no hemos encontrado grandes diferencias para el uso que nosotros le daremos que nos lleven a seleccionar uno u otro. El punto principal por el cual seleccionamos Tomcat es que luego de hacer pruebas en un servidor real, parece que Tomcat consume mucho menos recursos, esto teniendo en cuenta que no tendremos una gran cantidad de aplicaciones corriendo. En nuestro caso, estamos limitados de recursos, sobre todo de memoria en el servidor. 49 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Archiva Maven utiliza el concepto de repositorio para manejar artefactos, ya sean nuestros propios archivos o de terceros, los cuales podemos utilizar como dependencias. Al ejecutar un build podemos resolver dependencias contra repositorios oficiales, pero si deseamos hacer un mantenimiento de nuestros propios paquetes, debemos contar con nuestro propio manejador. Los manejadores de repositorios maven sirven para dos propósitos: actúan como proxies altamente configurables entre los repositorios de una organización y los repositorios maven púbicos y además proveen a una organización de un lugar centralizado donde alojar sus propios artefactos. Utilizar un proxy para repositorios maven tiene varios beneficios. Se incrementa sustancialmente la velocidad a la hora de obtener dependencias ya que utilizan una caché para todos los artefactos que obtienen desde el repositorio maven central. Si un desarrollador requiere una versión específica de un artefacto específico y estamos utilizando un manejador de repositorio maven, las dependencias (incluyendo las dependencias transitivas) se descargarán sólo una vez desde los repositorios remotos. Con una conexión de internet de alta velocidad, esto parece ser un tema menor, pero si cada desarrollador está constantemente descargando cientos de megabytes por dependencias de terceros, la verdadera ganancia se encuentra en tiempo que toma maven para chequear por nuevas versiones y descargar dependencias. Configurando a maven para descargar dependencias desde un repositorio local puede ahorrar cientos de requests http y en proyectos de gran tamaño con varios módulos, puede ahorrar gran tiempo a la hora de compilar. En nuestro caso particular, no contamos con un repositorio totalmente local porque no poseemos la infraestructura. Es decir, trabajamos desde nuestras casas utilizando un servidor que sí cuenta con un repositorio local. De todas formas los beneficios siguen siendo enormes. Cuando compilamos de manera local, las dependencias serán descargadas desde nuestro servidor, pero builds nocturnos se ejecutarán descargando dependencias directamente desde la red local. Una versión snapshot de un artefacto, es una versión que aún se encuentra en desarrollo y recibe modificaciones constantemente. Si un proyecto depende de muchos artefactos snapshot, Maven necesitará chequear por versiones actualizadas de estos artefactos. Dependiendo de la configuración, maven hará chequeos por actualizaciones periódicamente o por cada build. Cuando maven chequea por una actualización de un artefacto snapshot, debe interrogar al repositorio remoto por la última versión. Dependiendo de la conexión a internet, y de la carga del repositorio central de maven, una actualización de un artefacto snapshot puede agregar segundos a un build. Cuando contamos con un manejador de repositorio local con un proxy, el manejador de repositorio puede hacer chequeos periódicos por actualizaciones de snapshots, y las aplicaciones interactuarán directamente con el repositorio local. Si desarrollamos grandes proyectos con muchos módulos y muchas dependencias de tipo snapshots, con el uso de un repositorio local, se puede pasar de tardar varios segundos, a solo algunos milisegundos para resolver y descargar dependencias. Además del ahorro en cuanto a tiempo y ancho de banda, un repositorio local provee a 50 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier una organización el control sobre lo que maven descarga. Se pueden incluir o excluir artefactos específicos de los repositorios públicos. Tener este tipo de control sobre los que es descargado desde el repositorio central es un prerrequisito para organizaciones que requieren un control estricto sobre las dependencias que son utilizadas en toda la organización. Por otra parte, una organización puede desear estandarizar la versión que usa de ciertos artefactos, como por ejemplo Spring o Hibernate, puede hacerlo de una manera muy sencilla dando acceso sólo a las versiones que quiera en el manejador de repositorio. Otras organizaciones también pueden estar interesadas en utilizar sólo artefactos con licencias compatibles a las suyas. Si una corporación está desarrollando una aplicación que desea distribuir, puede asegurarse que no se utilice una librería con licencia copy-left, como GPL. Los manejadores de paquetes proveen a las organizaciones de un nivel de control con el cual pueden asegurar que toda su arquitectura y políticas son respetadas. Más allá de los beneficios de contar con un proxy entre los repositorios remotos y la organización, un administrador de repositorio también provee de algo esencial para la adopción plena de maven. Además de resolver dependencias con artefactos de terceros, también provee de un mecanismo para compartir nuestros propios artefactos en sus dos tipos de versiones, release y snapshot. Un administrador de repositorio maven provee con un destino para hacer deploy. Una vez instalado el administrador, se puede comenzar a utilizar maven para alojar nuestros propios paquetes. Después de un tiempo este podrá ser el punto central de colaboración entre diferentes equipos. Existe una lista de administradores de repositorio que la gente de apache sugiere en la página de maven, entre ellos se encuentran los siguientes: • Nexus absorbió todas las características de su predecesor (Proximity). Proximity es un producto que creó Tamas Cservenak en el año 2005, dos años más tarde la empresa Sonatype crea Nexus junto a Tamas. • Artifactory es un administrador de repositorio maven 2 a nivel empresarial. Ofrece características avanzadas en cuanto a proxy, caching y seguridad. Lo utilizan desde pequeños grupos de desarrollo hasta grandes corporaciones, desarrollando productos en los que participan desde decenas hasta cientos de desarrolladores. Artifactory logra una robusta plataforma de administración de artefactos, tiene una interfaz de usuario basada en Ajax y puede utilizarse simplemente descargando, descomprimiendo y corriendo. • Apache Archiva es un administrador de repositorio extensible. Es perfecto para utilizar junto con herramientas como maven, Continumm y ANT. Tiene varias características como proxy, seguridad, almacenamiento, búsqueda, indexado, reporte de uso y más. En cuanto a nuestra experiencia con el uso de estas herramientas, no encontramos diferencias en su utilización desde maven. Es decir todas sirven de igual manera para conectarlas con maven, resolver dependencias y hacer deploy de nuestros propios artefactos. Las diferencias fundamentales se encuentran en otras características. Pensamos que la interfaz de usuario más rica la provee artifactory, ya que está basada en requerimientos ajax a través de la red. 51 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Su interfaz es más completa y provee de más características de configuración. A nuestro criterio tiene dos problemas. El primero es que ofrece versiones pagas y open source, o sea que no es totalmente libre. Esto no termina de ser un problema ya que con la versión gratuita sería suficiente para nosotros por el momento, la versión paga ofrece un mayor número de características, pero casi todas apuntadas al uso empresarial. El segundo problema es que si bien su interfaz es más rica, consume más recursos. Por ahora estamos limitados en cuanto a cantidad de memoria o números de request simultáneos. Por estas razones estamos utilizando Archiva. 52 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Si bien la interfaz de Archiva es más rudimentaria, provee de todas las características que requerimos a un costo (en cuanto a recursos) bastante menor. Además es desarrollado por la organización Apache y es totalmente libre. Jenkins (antes hudson) Sabemos que es responsabilidad de cada desarrollador medir el impacto de un cambio. Pero en grandes proyectos, donde muchos artefactos dependen de otros esto es muy difícil de lograr. Muchas veces, con cambios en proyectos de este tipo, el mejor acercamiento al que podemos llegar es implementar integración continua centralizada en un servidor. De este modo, un desarrollador puede introducir un cambio con el cual deja de funcionar un proyecto que depende del proyecto en el cual se está introduciendo el cambio y este problema se detectará a más tardar al día siguiente, ya que podemos configurar nuestro servidor de integración continua para ejecutar todos los build de todos 53 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier los proyectos en orden, en un horario donde la carga sea baja. Hasta hace un tiempo atrás, la mejor opción para implementar integración continua centralizada en un servidor para aplicaciones java era la herramienta hudson. Hudson era una herramienta de código abierto soportado por la empresa Sun, cuando Oracle compró Sun ya no se le dió el mismo soporte. Hubo problemas incluso por el nombre “hudson”. Por estas razones surgió una rama totalmente libre y de código abierto denominada Jenkins. La realidad es que si bien en el futuro Jenkins y Hudson tomarán diferentes caminos, hoy en día prácticamente no hay ninguna diferencia. Hemos decidido usar Jenkins simplemente porque no existe ninguna restricción y es mantenido por la comunidad. Utilizando esta herramienta hemos definido una serie de builds en el servidor para soportar integración continua de todos nuestros proyectos: • CodeGeneratorPoms-deploy: descarga por cvs la solución CodeGeneratorPoms y ejecuta maven con las metas “clean deploy”. • CodeGeneratorModules-deploy: descarga por cvs la solución CodeGeneratorModules y ejecuta maven con las metas “clean deploy”. • CodeGenerator-coverage: descarga por cvs la solución CodeGeneratorModules/CodeGenerator y ejecuta maven con las metas “cobertura:clean cobertura:check”. • CodeGeneratorLibrary-deploy: descarga por cvs la solución CodeGeneratorLibrary y ejecuta maven con las metas “clean deploy”. • CodeGeneratorTemplates-deploy: descarga por cvs la solución CodeGeneratorTemplates y ejecuta maven con las metas “clean deploy”. • FscodeGeneratorProjectType-deploy: descarga por cvs la solución FscodeGeneratorProjectType y ejecuta maven con las metas “clean deploy”. • CodeGenerator-nightly-build: ejecuta todas las acciones anteriores en orden, 54 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier deteniéndose ante la primer falla. La idea de utilizar estas definiciones, es que si un desarrollador introduce un cambio en alguno de estos proyectos, luego de hacer el commit a través de cvs debe ejecutar manualmente el build que corresponda asegurándose de que quede en estado “verde”. Por las noche, en un horario en el que no se sobrecargará al servidor, se ejecutará automáticamente el último build. Diariamente deberíamos corroborar que el build nocturno finalizó correctamente, de hecho se lo puede configurar para que envíe un e-mail en caso de algún fallo. El lenguaje En esta sección explicaremos aspectos del lenguaje creado para la generación de código. No lo haremos de forma extensiva ya que excede los límites de esta documentación. El primer aspecto a tener en cuenta es que es un lenguaje de scripting con delimitadores. … <%% … %%> … Los delimitadores funcionan de igual manera que los que utiliza php, “<?php” y “?>”. Todo lo que se encuentre dentro de los límites de “<%%” y “%%>” es considerado código del lenguaje y será interpretado. Todo lo que se encuentre por fuera de los limitadores será considerado parte del template y se intentará escribirlo en el archivo actual de salida, si no hay ningún archivo en donde escribir aún, se procederá a mostrar un error. Para clarificar el uso de los delimitadores veamos un ejemplo: <%% Writer.open(__Config.get("scratch") $ "/dir/testFile"); Writer.echo("String test 1 - "); @("String test 2 - "); %%>String test 3<%% Writer.close(); %%> En el bloque de código anterior se pueden apreciar tres formas de escribir en el archivo testFile. La primera, es invocando directamente a la función del lenguaje a tal fin. En la segunda se usa un alias de la función anterior. Mientras que en la tercera se utilizan los delimitadores. Esto, sumado a la posibilidad de incluir otros archivos es sumamente atractivo para combinar el uso del lenguaje con templates que representen la estructura 55 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier de ciertas partes del código a generar. Si ejecutamos el generador directamente sobre un archivo con el contenido del ejemplo anterior, se generará un archivo de nombre testFile con el contenido “String test 1 - String test 2 - String test 3”. Este ejemplo se sumamente simple, pero demuestra el poder del generador. En cuanto a la sintaxis del lenguaje es un lenguaje basado en C, hereda del mismo la mayor parte de las estructuras de control, la manera de formar expresiones es la misma. Pero a demás cuenta con otras estructuras, muchas para dar soporte a orientación a objetos. Tipos nativos El lenguaje soporta 6 tipos primitivos. Cuatro tipos escalares: • Boolean. Es el tipo más simple, expresa un valor de verdad. Como literal puede ser true o false. • Integer. Es un número del conjunto ℤ = {..., -2, -1, 0, 1, 2, …}. Los integer pueden ser especificados mediante notación decimal (base 10), hexadecimal (base 16), octal (base 8) o binaria (base 2), opcionalmente precedidos por un signo (- o +). Los tamaños de un Integer dependen de la plataforma, es decir de la máquina virtual de java subyacente. No están limitados por el lenguaje de generación, si no por el lenguaje Java. • Float. Los números de punto flotante (también conocidos como "flotantes", "dobles" o "números reales") pueden ser especificados usando cualquiera de las siguientes sintaxis: <%% a = 1.234; b = 1.2e3; c = 7E-10; %%> • String. Un String es una cadena de caracteres. La forma de especificar un literal es la misma que en el lenguaje Java y las limitaciones también están dadas por la máquina virtual subyacente. Y dos tipos compuestos: • Array. Un array (matriz) es en realidad un mapa ordenado. Un mapa es un tipo de datos que asocia valores con claves. Este tipo es optimizado para varios usos diferentes; puede ser usado como una matriz real, una lista (vector), tabla asociativa (caso particular de implementación de un mapa), diccionario, colección, pila, cola y probablemente más. Ya que los valores de un array pueden ser otros array, también es posible crear árboles y array multidimensionales. Por default las claves de los arreglos estarán ordenadas, primero estarán las claves enteras de menor a mayor y luego las claves de tipo string en orden 56 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier alfabético. La sentencia foreach sobre un arreglo recorrerá el arreglo según el orden de sus claves. • Object. Para dar soporte a la orientación a objetos, existe el tipo Object. Todos los objetos que se crean, heredarán del mismo. Sobre los tipos escalares existe conversión explícita. Es decir, 0 se convierte a FALSE en un contexto en el que se espera un valor de tipo Booleano. Cualquier otro valor numérico se convierte a TRUE. En los casos en que el generador detecte una conversión extraña, logueará un warning. Si no puede continuar debido a que no puede convertir entre tipos escalares logueará un error irrecuperable. El lenguaje no requiere (ni soporta) la definición explicita de tipos en la declaración de variables; el tipo de la variable se determina por el contexto en el cual se emplea la variable. Es decir, si se asigna un valor string a una variable var, entonces var se convierte en un string. Si un valor integer es entonces asignado a la misma variable var, ésta se convierte en integer. Detrás de cada tipo escalar hay realmente un objeto, por lo que una variable de tipo escalar puede ser tratada como un objeto para invocar métodos que soporta directamente el lenguaje. Se da soporte nativo para los siguientes métodos del tipo Integer: compareTo public int compareTo(Object o) Compara este objeto entero con otro objeto. Retorna: el valor 0 si el entero es igual al argumento; un valor menor a 0 si el entero es numéricamente menor que el argumento; y un valor mayor a 0 si el entero es numéricamente mayor al argumento. equals public boolean equals(Object obj) Compara este objeto con el especificado. El resultado es verdadero si y solo si el argumento es un entero con el mismo valor. Parámetros: obj – El objeto con el cual comparar. Retorna: Verdadero si el objeto es el mismo; falso en caso contrario. floatValue public float floatValue() Retorna el valor de este entero como un float. Retorna: El valor numérico de este objeto convertido a float. 57 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier toString public String toString() Retorna un objeto de tipo string con el valor de este entero. Retorna: Una representación de tipo string con este entero en base 10. Para el tipo float: compareTo public int compareTo(Object o) Compara numéricamente contra otro objeto de punto flotante. Parámetros: o – El objeto contra el cuál comparar. Retorna: El valor 0 si el argumento es un número de punto flotante con el mismo valor al de este objeto; un valor menor a 0 si el argumento es numéricamente mayor a este objeto; y un valor mayor a cero si el argumento es numéricamente menor a este objeto. equals public boolean equals(Object obj) Compara este objeto contra el objeto especificado. El resultado es verdadero si y sólo si el argumento es un objeto de tipo Float y representa el mismo valor numérico que este objeto. Parámetros: obj – el objeto con el cuál comparar. Retorna: Verdadero si los objetos representan el mismo valor; falso en caso contrario. isInfinite public boolean isInfinite() Retorna verdadero si el valor es infinitamente grande en magnitud, falso en caso contrario. Retorna: Verdadero si el valor representado es positivo infinito o negativo infinito; falso en caso contrario. isNaN public boolean isNaN() Retorna verdadero si el valor no es un número (Not a Number en inglés), falso en caso contrario. Retorna: Verdaderso si el valor representado por este objeto es NaN; falso en caso contrario. toString public String toString() 58 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Retorna una representación de tipo String de este objeto. Retorna: Una representación String de este objeto. El tipo String es uno de los que cuenta con más soporte nativo, posee los siguientes métodos: charAt public char charAt(int index) Retorna un String con el carácter en el índice especificado. El rango de índices puede ir desde 0 a length() - 1. El primer carácter se considera en la posición 0, en segundo en la 1 y así siguiendo. Parámetros: index – El índice del valor que se espera obtener. Retorna: Un String representando el carácter en la posición index. El primer valor se considera en el índice 0. compareTo public int compareTo(String anotherString) Compara dos Strings alfabéticamente. La comparación se basa en el valor unicode de cada carácter de los Strings. La secuencia de caracteres en este objeto es comparada lexicográficamente con la secuencia de caracteres del argumento. El resultado es un entero negativo si este String precede lexicográficamente al del argumento. El resultado es un entero negativo si este String sigue lexicográficamente al del argumento. El resultado es cero si los dos Strings son iguales; compareTo retorna cero cuando equals es verdadero. Esta es la definición de orden lexicográfico. Si dos Strings son diferentes, entonces o bien tienen un carácter diferente en un mismo índice (valido para ambos), o su longitud es diferente, o ambos. Si tienen caracteres diferentes en una o más posiciones, entonces sea k el índice más pequeño en el que difieren; entonces el string con el carácter más pequeño en la posición k precede lexicográficamente al otro. En este caso compareTo retorna la diferencia entre los valores de los caracteres en la posición k, es decir: this.charAt(k)-anotherString.charAt(k) Si no hay una posición en la que los caracteres difieran, entonces el String más corto precede lexicográficamente al más largo. En este caso compareTo retorna la diferencia entre las longitudes de los Strings, esto es: this.length()-anotherString.length() Parámetros: anotherString – El String con el cuál comparar. Retorna: El valor 0 si el argumento es igual a este objeto; un valor menor a 0 si el String es lexicográficamente menor al argumento; y un valor mayor a 0 si el String es lexicográficamente mayor al argumento. compareToIgnoreCase public int compareToIgnoreCase(String str) Compara dos Strings lexicográficamente, ignorando las diferencias entre mayúsculas y minúsculas. Parámetros: 59 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier str – El String con el cual comparar. Retorna: Un entero negativo, cero, o un entero positivo. Ya sea que el argumento sea mayor, igual, o menor a este String. Ignorando consideraciones sobre mayúsculas y minúsculas. concat public String concat(String str) Concatena el String especificado al final de este String. Si el largo del argumento es 0, entonces este objeto es retornado. En cualquier otro caso, un nuevo objeto de tipo String es creado y retornado, internamente contiene un String que representa la concatenación de la secuencia de caracteres de este objeto con la secuencia de caracteres del argumento. Parámetros: str – El String que será concatenado al final de este. Retorna: Un String que representa la concatenación de los caracteres de este objeto con la secuencia de caracteres del argumento. endsWith public boolean endsWith(String suffix) Prueba si este String termina con el sufijo especificado. Parámetros: suffix – El sufijo. Retorna: Verdadero si la secuencia de caracteres de este objeto es sufijo de la secuencia de caracteres representada por el argumento; falso de otra manera. Notar que el resultado será verdadero si el argumento es un String vacío o igual a este objeto. equals public boolean equals(Object anObject) Compara este String con el objeto especificado. El resultado es verdadero si y sólo si el argumento es no nulo y es un objeto de tipo String que representa la misma secuencia de caracteres que este objeto. Parámetros: anObjet – El objeto con el cuál comparar. Retorna: Verdadero si los String son iguales; falso en caso contrario. equalsIgnoreCase public boolean equalsIgnoreCase(String anotherString) Compara este String con otro String, ignorando consideraciones sobre mayúsculas y minúsculas. Serán considerados iguales si tienen la misma longitud y los caracteres correspondientes son iguales. Parámetros: anotherString – El string contra el cuál comparar. Retorna: 60 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Verdadero si el argumento es no nulo y los strings son iguales sin tener en cuenta mayúsculas y minúsculas; falso en caso contrario. indexOf public int indexOf(int ch) Retorna el índice dentro de este String de la primer ocurrencia del carácter especificado. Si un carácter con el valor ch especificado está en la secuencia de caracteres representada por ese objeto, entonces el índice (en unidades unicode) de la primer ocurrencia de ese carácter es retornado. Si no existe ninguna ocurrencia del carácter ch, -1 es retornado. Parámetros: ch – Un carater. Retorna: El índice de la primer ocurrencia del carácter de valor ch dentro de la secuencia de caracteres representada por este Objeto; o -1 si el carácter no aparece dentro de dicha secuencia. indexOfWithFrom public int indexOfWithFrom(int ch, int fromIndex) Retorna la primer ocurrencia del carácter especificado dentro de este String, comenzando la búsqueda en el índice especificado. Si una ocurrencia del carácter ch dentro de la secuencia de caracteres representada por este String ocurre en un índice no menor a fromIndex, entonces el índice de la primer ocurrencia es retornado. No hay restricciones sobre el valor fromIndex. Si es negativo, tiene el mismo efecto que si fuese cero: se buscará en el String completo. Si es mayor a la longitud del String, tiene el mismo efecto que si fuese igual a dicha longitud: se retornará -1. Nota: Estamos pensando en eliminar este método y unirlo con indexOf, dejando el parámetro fromIndex como opcional. La posibilidad de utilizar parámetros con valores default fue agregada luego de este método. Parámetros: ch – Un carácter. FromIndex – El índice en el cual comenzar la búsqueda. Retorna: El índice de la primer ocurrencia del carácter en la secuencia de caracteres representada por este objeto que es mayor que o igual a fromIndex, o -1 si el carácter no se encuentra. lastIndexOf public int lastIndexOf(int ch) Retorna el índice dentro de este String de la última ocurrencia del carácter especificado. Si no existen ocurrencias del carácter, retorna -1. La búsqueda se hace hacia atrás comenzando por el último carácter. Parámetros: ch – Un carácter. Retorna: El índice de la última ocurrencia del carácter dentro de la secuencia de caracteres representada por este objeto, o -1 si no existe tal ocurrencia. lastIndexOfWithFrom public int lastIndexOfWidthFrom(int ch, int fromIndex) 61 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Retorna el índice dentro de este String de la última ocurrencia del carácter especificado, buscando hacia atrás y comenzando en el índice especificado. Si no existe ocurrencia del carácter especificado antes de la posición fromIndex, retrona -1. Nota: Estamos pensando en eliminar este método y unirlo con lastIndexOf, dejando el parámetro fromIndex como opcional. La posibilidad de utilizar parámetros con valores default fue agregada luego de este método. Parámetros: ch – Un carácter unicode. fromIndex – El índice desde donde comenzar la búsqueda. No hay restricción en cuanto a los valores de fromIndex. Si es mayor o igual a la longitud del String, tiene el mismo efecto que si fuese igual a la longitud de este String: se busca en la secuencia completa de caracteres. Si es negativo, tiene el mismo efecto que si fuese -1: -1 es retornado. Retorna: El índice de la última ocurrencia del carácter dentro de la secuencia de caracteres representada por este objeto que es menor que o igual a fromIndex, o -1 si el carácter no se encuentra antes de este punto. length public int length() Retorna la longitud de este String. La longitud es igual al número de caracteres unicode en el objeto. Retorna: La longitud de la secuencia de caracteres representada por este objeto. matches public boolean matches(String regex) Indica si esta String concuerda o no con la expresión regular dada. Parámetros: regex – La expresión regular. Retorna: Verdadero, si y sólo si, este String machea con la expresión regular dada. regionMatches public boolean regionMatches(int toffset, String other, int ooffset, int len) Prueba si dos regiones de Strings son iguales. Un substring de este objeto es comparado con un substring del argumento other. El resultado es verdadero si esas porciones representan la misma secuencia de caracteres. El substring de este objeto a ser comparado comienza en el índice toffset y tiene longitud len. El substring del argumento other a ser comparado comienza en el índice ooffset y tiene longitud len. El resultado es falso, sí y solo si al menos una de las siguientes condiciones es verdadera: • toffset es negativo. • ooffset es negativo. • toffset+len es mayor a la longitud de este objeto. • ooffset+len es mayor a la longitud de other. • Existe algún entero no negativo k menor que len tal que this.charAt(toffset+k) != other.charAt(ooffset+k) Parámetros: toffset – el índice de comienzo de la región en este String. other – un argumento String. ooffset – el índice de comienzo de la región del String other. 62 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier len – el número de caracteres a comparar. Retorna: Verdadero si la región especificada de este String coincide con la región del String other; falso en cualquier otro caso. regionMatchesIgnoreCase public boolean regionMatchesIgnoreCase(int toffset, String other, int ooffset, int len) Prueba si dos regiones de Strings son iguales ignorando cuestiones de mayúsculas y minúsculas. Un substring de este objeto es comparado con un substring del argumento other. El resultado es verdadero si esas porciones representan la misma secuencia de caracteres. El substring de este objeto a ser comparado comienza en el índice toffset y tiene longitud len. El substring del argumento other a ser comparado comienza en el índice ooffset y tiene longitud len. Parámetros: toffset – el índice de comienzo de la región en este String. other – un argumento String. ooffset – el índice de comienzo de la región del String other. len – el número de caracteres a comparar. Retorna: Verdadero si la región especificada de este String coincide con la región del String other sin consideraciones de mayúsculas o minúsculas; falso en cualquier otro caso. replace public String replace(String target, String replacement) Cada substring de este string que machea con target es reemplazado con replacement. El reemplazo procede desde el comienzo hacia el final de la cadena, por ejemplo reemplazar “aa” con “b” en “aaa” resulta en “ba” y no en “ab”. Parámetros: target – la secuencia de caracteres en la cual reemplazar. replacement – la secuencia de caracteres a usar como reemplazo. Retorna: El String resultante de llevar a cabo el reemplazo. replaceAll public String replaceAll(String regex, String replacement) Reemplaza cada substring de este String que machea con la expresión regular de reemplazo. Parámetros: regex – la expresión regular a buscar. replacement – la cadena de reemplazo. Retorna: El String resultante de llevar a cabo el reemplazo. replaceFirst public String replaceFirst(String regex, String replacement) Reemplaza la primer ocurrencia dentro de este String que machea con la expresión regular de reemplazo. 63 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Parámetros: regex – la expresión regular a buscar. replacement – la cadena de reemplazo. Retorna: El String resultante de llevar a cabo el reemplazo. startsWith public boolean startsWith(String prefix) Verdadero si este String comienza con el prefijo especificado. Parámetros: prefix – el prefijo. Retorna: Verdadero si cada carácter en la secuencia especificada por el argumento prefix es prefijo de la secuencia de caracteres representada por este String; falso en caso contrario. El valor retornado será verdadero si el argumento es vacío o igual a este String. startsWithWithOffset public boolean startsWith(String prefix, int toffset) Prueba si este String comienza con prefix, comenzando en el índice especificado. Nota: Estamos pensando en eliminar este método y unirlo con startsWith, dejando el parámetro toffset como opcional. La posibilidad de utilizar parámetros con valores default fue agregada luego de este método. Parámetros: prefix – el prefijo. toffset – donde comenzar a buscar dentro del String. Retorna: Verdadero si la secuencia de caracteres representada por el argumento es un prefijo del substring de este objeto comenzando en la posición toffset; falso en caso contrario. El resultado es falso si toffset es negativo o mayor a la longitud de este String; en cualquier otro caso el resultado es el mismo que el de la expresión: this.substring(toffset).startsWith(prefix) substring public String substring(int beginIndex, int endIndex = null) Retorna un nuevo String que es un substring de este objeto. El substring comienza en la posición especificada como beginIndex y: • si el parámetro endIndex no está presente, se extiende hasta el final del String • si el parámetro está presente, se extiende hasta la posición endIndex – 1 Parámetros: beginIndex – el índice de comienzo. endIndex (opcional) – el índice de finalización. Retorna: El substring específico. toLowerCase public String toLowerCase() 64 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Convierte todos los caracteres de este String a minúsculas. Retorna: El String, convertido a minúsculas. toString public String toString() Este objeto (que actualmente es un String) es retornado. Retorna: El mismo objeto. toUpperCase public String toUpperCase() Convierte todos los caracteres de este String a mayúsculas. Retorna: El String, convertido a mayúsculas. trim public String trim() Retorna una copia de este String, eliminando los espacios en blanco al comienzo y al final. Si este objeto es el String vacío, o el primer y el último carácter en la cadena representada por este String tienen códigos mayores a '\u0020' (carácter de espacio), entonces una referencia a este objeto es retornada. De otra manera, si no hay caracteres con un código mayor a '\u0020' en el String, entonces un nuevo String representando a la cadena vacía es creado y retornado. En cualquier otro caso, sea k el primer índice del String en cuya posición existe un carácter con código mayor a '\u0020', y sea m el índice del último carácter cuyo código es mayor a '\u0020'. Un nuevo String es creado, representando el substring que comienza en el carácter k y termina en el carácter m, esto es el resultado de this.substring(k, m+1). Retorna: Una copia del String con los espacios removidos al principio y final de la cadena, o este String si no tiene espacios al comienzo o final. split public array(String) split(String regex) Parte este String alrededor de las coincidencias con la expresión regular dada. Parámetros: regex – la expresión regular. Retorna: El arreglo de Strings resultante de partir este String según la expresión regular dada en regex. Sobre el objeto Object, brindamos dos métodos nativos: equals public boolean equals(Object obj) 65 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Indica si un objeto es igual a este objeto. El método equals implementa una relación de equivalencia sobre referencias no nulas: • Es reflexiva: para cualquier referencia no nula x, x.equals(x) debe ser verdadero. • Es simétrica: dadas dos referencias x e y no nulas, x.equals(y) retorna verdadero si y sólo si y.equals(x) retorna verdadero. • Es transitiva: dadas tres referencias no nulas x, y, z, si x.equals(y) es verdadero y y.equals(z) es verdadero, entonces x.equals(z) es verdadero. • Es consistente: dadas dos referencias no nulas x e y, múltiples invocaciones de x.equals(y) retornan verdadero o falso consistentemente. • Para toda referencia no nula x, x.equals(null) retorna falso. Parámetros: obj – la referencia a un objeto con el cuál comparar. Retorna: Verdadero si este objeto hace referencia al mismo que el argumento; falso en caso contrario. dump public static String dump(Object obj, Integer depth = null) Retorna un String con información estructurada de un objeto, incluyendo tipo y valor. Los arreglos y los objetos son explorados recursivamente para mostrar información sobre su estructura de manera identada. Parámetros: obj – Objeto sobre el cual se desea obtener información. depth (opcional) – profundidad hasta la cual llegar en la exploración. El lenguaje utiliza una profundidad de 100 por default, es decir que si no se provee de este parámetro se mostrarán los primeros 100 elementos/propiedades. Retorna: Un String con la información estructurada sobre el objeto obj. Dado que de momento no contamos la posibilidad de debuggear el código escrito en nuestro lenguaje, la función dump es de vital importancia para conocer el contenido de las variables en ejecución y así poder depurar el código. Algunos ejemplos del funcionamiento de la función dump son los siguientes: Si ejecutamos: <%% Log(Object.dump(array("el 1", "k1" => "el 2"))); %%> Por consola se imprimirá la siguiente información: array(2) { [0]=> string(4) "el 1" ["k1"]=> string(4) "el 2" } El método también opera sobre arreglos anidados, si ejecutamos: 66 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier <%% Log(Object.dump(array( 1, array(2), 3 ))); %%> El resultado por consola será: array(3) { [0]=> int(1) [1]=> array(1) { [0]=> [0]=> int(2) } [2]=> int(3) } Y con objetos más complejos también es de utilidad. Si ejecutamos: <%% class MyClass { public _att1 = "att1"; public function MyClass() {} } class MyClass2 extends MyClass { public _att2 = "att2"; public _att3 = new MyClass(); } public function MyClass2() : super() {} Log(Object.dump(new MyClass2())); %%> E resultado será: Object::MyClass2(2) { [MyClass2::public:_att2]=> string(4) "att2" [MyClass2::public:_att3]=> Object::MyClass(1) { [MyClass::public:_att1]=> string(4) "att1" } [MyClass::public:_att1]=> string(4) "att1" } 67 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Variables Las variables en nuestro lenguaje son creadas ante el primer uso. La mayoría de las variables tienen un ámbito simple, pero su alcance es propagado por archivos incluidos. Por ejemplo: <%% a = 1; include("b.inc"”); %%> En este caso se creará una variable de nombre a y esta variable será accesible tanto en el archivo que está definido como dentro del código en el archivo b.inc. Para funciones ralas, es decir funciones definidas dentro de un archivo, pero que no pertenecen a ningún objeto las reglas son las siguientes: <%% function test(...) { … a … } %%> Cuando el parser encuentra la variable a, • primero busca si existe en el ámbito local de la función (dentro del cuerpo de la misma) • si no existe en el ámbito local, se buscará como parámetro • si tampoco existe como parámetro, se buscará en el ámbito global • si tampoco existe en el ámbito global, entonces será creada como una variable local al cuerpo de la función Cuando introducimos objetos, el ámbito de las variables es similar, solo con algunas diferencias. <%% class MyClass { public function test(...) { … a … } } %%> En este caso, cuando el parser encuentra la variable a, • primero busca si existe en el ámbito local del método • si no existe en dicho contexto, la buscará como parámetro de la función • si tampoco existe como parámetro, la buscará como atributo interno de la clase 68 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier • si tampoco existe, recién entonces es creada como una variable local al cuerpo del método Si bien aún no hemos hablado de expresiones lambda ya que estas son introducidas en la sección sobre extensiones al lenguaje, cabe destacar que también tienen un ámbito algo diferente para buscar las variables a las que se accede en el cuerpo. Inicialmente funcionan igual que una función, pero si buscan hacia “arriba” entonces depende del contexto en el que son ejecutadas. Una vez introducido el concepto de expresiones lambda, esto quedará un poco más claro mediante algunos ejemplos. Arreglos Ya hemos definido el concepto de un arreglo para nuestro lenguaje, lo que no hemos aclarado aún es la forma de declararlos, inicializarlos y acceder a sus valores. Un valor array puede ser creado por la construcción de lenguaje array(). Ésta toma un cierto número de parejas clave => valor separadas con coma. array( key => value , ... ) // key puede ser un integer o string // value puede ser cualquier valor Si no especifica una key para un valor dado, entonces es usado el máximo de los índices Integer y la nueva key será ese valor máximo más 1. Si se especifica una key que ya tiene un valor asignado, ese valor será sobrescrito. <%% // Este array es lo mismo que... array(5 => 43, 32, 56, "b" => 12); // ...este array array(5 => 43, 6 => 32, 7 => 56, "b" => 12); %%> Es posible modificar un array existente al definir valores explícitamente en él. Esto es posible al asignar valores al array al mismo tiempo que se especifica la key entre corchetes. También es posible omitir la key, lo que resulta en una pareja de corchetes vacíos ([]). arr[key] = value; arr[] = value; // key puede ser un integer o un string // value puede ser cualquier valor Además de la sintaxis típica sobre arreglos, se brinda soporte nativo para los siguientes métodos: clear void clear() 69 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Elimina todos los elementos del arreglo. containsKey boolean containsKey(Object key) Retorna verdadero si el arreglo contiene la clave específica, el tipo del argumento key debería ser Integer o String. Parámetros: key – la clave a buscar en el arreglo Retorna: Verdadero si el arreglo contiene la clave especificada; falso en caso contrario. containsValue boolean containsValue(Object value) Retorna verdadero si el arreglo contiene el valor especificado. Parámetros: value – valor a buscar en el arreglo. Retorna: Verdadero si el arreglo contiene el valor especificado; falso en caso contrario. isEmpty boolean isEmpty() Retorna verdadero si este arreglo está vacío. Retorna: Verdadero si el arreglo está vacío; falso en caso contrario. remove Object remove(Object key) Quita del arreglo el objeto asociado a la clave key si es que está presente. Retorna el valor al que estaba asociada la clave, o NULL si es que la clave no existía en el arreglo. Un retorno nulo también puede indicar que la clave estaba asociada a ese valor. Luego de esta operación no habrá en el arreglo elemento asociado a la clave. Parámetros: key – clave del elemento a eliminar del arreglo. Retorna: Valor previo asociado a la clave, o NULL si el valor no estaba presente. size int size() Retorna el número de elementos en el arreglo, si el arreglo tiene más elementos que el máximo entero soportado, entonces retorna Integer.MAX_VALUE. Retorna: El número de elementos del arreglo. 70 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Por supuesto con estas operaciones no es suficiente, por lo que existen varias extensiones sobre arreglos que explicaremos en seccione subsecuentes de esta documentación. Operadores Un operador es una construcción del lenguaje que toma uno o más valores y produce otro valor, por lo que la construcción en sí misma se convierte en una expresión. En nuestro lenguaje hemos implementado la mayoría de los operadores típicos de todos los lenguajes de programación que heredan del lenguaje C. Precedencia de operadores La siguiente tabla lista en orden la precedencia de los operadores, con los operadores de mayor precedencia en la parte superior. Los operadores en la misma línea tienen la misma precedencia, en cuyo caso su asociatividad decide cuál es el orden de evaluación. Asociatividad Operadores Izquierda [ no asociativo ++ -- no asociativo Instanceof derecha ! Izquierda */% Izquierda +-$ no asociativo < <= > >= no asociativo == != !== Izquierda & Izquierda ^ Izquierda | Izquierda && Izquierda || Izquierda ?: derecha = += -= *= /= $= %= &= |= ^= Operadores aritméticos Ejemplo Nombre Resultado -a Negación Opuesto de a. a+b Adición Suma de a y b. 71 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier a-b Sustracción Diferencia de a y b. a*b Multiplicación Producto de a y b. a/b División Cociente de a y b. a%b Módulo Resto de a dividido por b. Operadores bit a bit Ejemplo Nombre Resultado a&b And (y) Los bits que están activos en ambos a y b son activados. a|b Or (o inclusivo) Los bits que están activos ya sea en a o en b son activados. a^b Xor (o exclusivo) Los bits que están activos en a o en b, pero no en ambos, son activados. Operadores de comparación Ejemplo Nombre Resultado a == b Igual TRUE si a es igual a b después de la manipulación de tipos. a != b Diferente TRUE si a no es igual a b después de la manipulación de tipos. a<b Menor que TRUE si a es estrictamente menor que b. a>b Mayor que TRUE si a es estrictamente mayor que b. a <= b Menor o igual que TRUE si a es menor o igual que b. a >= b Mayor o igual que TRUE si a es mayor o igual que b. Operadores de incremento/decremento Ejemplo Nombre Efecto ++a Incrementa a en uno, y luego retorna a. Pre-incremento 72 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier a++ Post-incremento Retorna a, y luego incrementa a en uno. --a Pre-decremento Decrementa a en uno, luego retorna a. a-- Post-decremento Retorna a, luego decrementa a. Operadores lógicos Ejemplo Nombre Resultado !a Not (no) TRUE si a no es TRUE. a && b And (y) TRUE si tanto a como b son TRUE. a || b Or (o inclusivo) TRUE si cualquiera de a o b es TRUE. Operadores para strings Ejemplo Nombre Resultado a$b Concatenación a concatenado a b. a $= b Concatenación asignada a se concatena con b y luego se asigna a a. Estructuras de control Todo script en nuestro lenguaje está compuesto por un conjunto de sentencias, a continuación explicaremos las sentencias más comunes. En su mayoría son heredadas del lenguaje C y se comportan de manera similar. Algunas, tienen un funcionamiento similar a sentencias del lenguaje PHP, como son los Include, Require, Include.once y Require.once. If El constructor if permite la ejecución condicional de porciones de código. if (expr) sentencia Si la expresión evalúa a TRUE, se ejecuta la sentencia, caso contrario se ignora. Ejemplo: <%% if(true) Writer.echo("GOOD"); 73 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier if(true) Writer.echo("GOOD"); else Writer.echo("BAD"); if(false){ Writer.echo("BAD"); } else{ Writer.echo("GOOD"); } if(1 == 1) if(false) Writer.echo("BAD"); else Writer.echo("GOOD"); else Writer.echo("BAD"); %%> La sentencia if, también acepta la palabra else. if (expr) sentencia else sentencia2 En ese caso si la expresión evalúa a FALSE, se ejecuta la sentencia2. While La sentencia while es la forma más simple de lograr bucles, se comporta igual que en el lenguaje C. La sintaxis básica tiene la siguiente forma. while (expr) sentencia La sentencia se ejecuta tantas veces como la expresión evalúe a TRUE. El valor de la expresión es verificado cada vez al inicio del bucle, por lo que incluso si este valor cambia durante la ejecución de las sentencias anidadas, la ejecución no se detendrá hasta el final de la iteración. A veces, si la expresión while se evalúa como FALSE desde el principio, las sentencias anidadas no se ejecutarán ni siquiera una vez. Ejemplo: <%% i = 0; while(i < 11){ Writer.echo(i); i = i + 1; } %%> 74 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier do-while Es similar a la sentencia while, la diferencia es que en este caso está garantizado que la sentencia dentro del bucle se ejecuta al menos una vez, ya que la expresión de condición es evaluada al final de cada iteración. Es decir si utilizamos while, como la expresión se evalúa al comienzo, puede suceder que nunca se ejecute el cuerpo, mientras que si utilizamos do-while, como la expresión se evalúa al final, siempre se ejecuta al cuerpo al menos una vez. Su forma es la siguiente. do sentencia while (exp) Ejemplo: <%% i = 0; do{ Writer.echo(i); i = i + 1; }while(i < 11); %%> for La sentencia for es la forma más compleja de realizar un bucle, pero también la más flexible. Se comporta igual que la sentencia for para el lenguaje C. La sintaxis es la siguiente. for (expr1; expr2; expr3) sentencia El comportamiento es el siguiente: • La primera expresión es siempre evaluada al comienzo. • En el comienzo de cada iteración se evalúa la segunda expresión, si evalúa a TRUE, se ejecuta el cuerpo de la sentencia. Si evalúa a FALSE ya no se continúa con el bloque. • Al final de cada iteración, la última expresión es evaluada. Cada una de las expresiones puede estar vacía o contener múltiples expresiones separadas por comas. En la expr2, todas las expresiones separadas por una coma son evaluadas pero el resultado se toma de la última parte. Que la expr2 esté vacía significa que el bucle deberá ser corrido indefinidamente. 75 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Ejemplo: <%% for(i = 0, j = 10; i < 10; i = i + 1, j = j - 1){ Writer.echo(i); Writer.echo(j); } %%> foreach El constructor foreach es similar al del lenguaje PHP y respeta su comportamiento. Funciona sólo sobre arreglos. Su sintaxis es la siguiente. foreach (valor in expresión_array) sentencias foreach (clave => valor in expresión_array) sentencias La primera forma recorre el array dado por expresión_array. En cada iteración, el valor del elemento actual se asigna a valor y el puntero interno del array avanza una posición (así en la próxima iteración se estará observando el siguiente elemento). La segunda forma además asigna la clave del elemento actual a la variable clave en cada iteración. Ejemplo: <%% arr = array(1 => "c", "a" => "a", "b", 0 => "d"); foreach(key => value in arr) { Writer.echo(key); Writer.echo(value); } foreach(value in array(3, 2, 1)) { Writer.echo(value); } %%> break y continue Break termina la ejecución de la estructura actual for, foreach, while, do-while o switch. Continue se utiliza dentro de las estructuras de bucle o switch para saltarse el resto de la actual iteración del bucle y continuar la ejecución en la evaluación de la condición (al comienzo de la siguiente iteración). Ejemplo: <%% 76 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier // WHILE BREAK i = 0; while(true){ if(i == 11) break; Writer.echo(i); i++; } // WHILE CONTINUE i = -1; while(i<11){ i++; if(i == 5) continue; Writer.echo(i); } // DO-WHILE BREAK i = 0; do{ if(i == 11) break; Writer.echo(i); i++; }while(true); // DO-WHILE CONTINUE i = -1; do{ i++; if(i == 5) continue; Writer.echo(i); }while(i<11); // FOR BREAK for( i = 0; ; i++){ if(i == 11) break; Writer.echo(i); } // FOR CONTINUE for( i = 0; i < 11 ; i++){ if(i == 5) continue; Writer.echo(i); } // FOREACH BREAK NO KEY arr = array(0,1,2,3,4,5,6,7,8,9,10,11); foreach(value in arr) { if(value == 5) break; Writer.echo(value); } // FOREACH CONTINUE NO KEY foreach(value in arr) { if(value == 5) continue; Writer.echo(value); } // FOREACH BREAK WITH KEY arr = array(0,1,2,3,4,5,6,7,8,9,10,11); foreach(key => value in arr) { if(value == 5) break; Writer.echo(value); } %%> 77 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier switch Switch se utiliza para seleccionar uno (o más) de entre varios caminos de ejecución. Se puede lograr el mismo efecto anidando sentencias if. <%% function monthToString( month ){ switch (month) { case 1: Writer.echo("January"); break; case 2: Writer.echo("February"); break; case 3: Writer.echo("March"); break; case 4: Writer.echo("April"); break; case 5: Writer.echo("May"); break; case 6: Writer.echo("June"); break; case 7: Writer.echo("July"); break; case 8: Writer.echo("August"); break; case 9: Writer.echo("September"); break; case 10: Writer.echo("October"); break; case 11: Writer.echo("November"); break; case 12: Writer.echo("December"); break; default: Writer.echo("Invalid month.");break; } } function numberDays( month, year){ switch (month) { case 1: case 3: case 5: case 7: case 8: case 10: case 12: numDays = 31; break; case 4: case 6: case 9: case 11: numDays = 30; break; case 2: if ( ((year % 4 == 0) && !(year % 100 == 0)) || (year % 400 == 0) ) numDays = 29; else numDays = 28; break; default: Writer.echo("Invalid month."); break; } Writer.echo(numDays); } monthToString(1); monthToString(12); monthToString(15); numberDays( numberDays( numberDays( numberDays( numberDays( numberDays( 2, 2000); 2, 2001); 1, 2008); 7, 2008); 12, 2008); 11, 2008); %%> 78 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier return La sentencia return debe ser utilizada dentro del ámbito de una función o método de un objeto. Caso contrario se producirá un error y se lo informará al usuario. Inmediatamente termina la ejecución de la función o método y retorna su argumento. En el caso de funciones o métodos que no contengan, o que contengan pero no lleguen a ejecutar la sentencia return, se devolverá el valor NULL. Require, Include, Require.once, Include.once Todas estas sentencias toman un argumento (el nombre de un archivo) y lo incluyen. Las diferencias son las siguientes. • Entre Require e Include: Si Include no encuentra el archivo simplemente mostrará un warning, mientras que si Require no encuentra un archivo habrá un error fatal. • El comportamiento sin agregar .once es que cada vez que se ejecuta la sentencia se incluirá el archivo especificado, mientras que al agregar .once se incluirá únicamente la primera vez que se ejecuta la sentencia para el mismo archivo. Orientación a objetos Para dar soporte a la orientación a objetos se han introducido una serie de elementos. Se soporta la mayor parte de las características de los lenguajes orientados a objetos: • Es posible definir clases, con atributos privados, protegidos o públicos. • Los métodos también pueden ser privados, protegidos o públicos. • A su vez, tanto atributos como métodos pueden ser estáticos. • Se permite herencia simple. • Se permite sobrescribir atributos y métodos definidos en una clase padre. • No existe la idea de atributos ni métodos abstractos. Aunque se puede simular. • Tampoco existe la noción de interfaz. Aunque se puede simular. Definición de clases La sintaxis básica para definir una clase es la siguiente. <%% class MyClass { public att1; protected att2; private att3; public static att4; public function fcn1 { } 79 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier protected function fcn2 { } private function fcn3 { } public static function fcn4 { } public function MyClass() { } } %%> Tanto los atributos como los métodos son precedidos del modificador. En cuanto al constructor, si existe una función con el mismo nombre que la clase, es considerada el constructor de la misma. Si no existe dicha función, no se podrá crear una instancia de esa clase directamente. No está permitida la sobrecarga directa de métodos ni constructores, es decir que una clase no puede tener dos métodos con el mismo nombre. Lo que si es posible es utilizar parámetros con valores por defecto con la siguiente sintaxis. <%% function myFunc(att1, …, att2 = Default2, att3 = Default3) { } %%> Los primeros parámetros son requeridos. Los parámetros con valores defecto deben ser los últimos que están en la declaración de la función o método. Es decir, una vez que se encuentra un parámetro con un valor por defecto comenzando en la lista desde la izquierda hacia la derecha, ya no puede haber parámetros que no tengan valores por defecto. Al invocar una función, está permitido omitir uno o varios de los parámetros con valores por defecto, en cuyo caso tomarán el valor que tienen por defecto en la declaración. Esta es una característica que también posee el lenguaje PHP y el comportamiento es prácticamente el mismo. Para crear instancias de objetos se utiliza la palabra reservada new. Por ejemplo: <%% class MyClass{ public pubField = "pub"; public constInit; private privField = "priv"; } public function MyClass(){ constInit = "initialized"; } obj1 = new MyClass(); Writer.echo(obj1.pubField); %%> 80 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Los campos y método que se pueden utilizar sobre la instancia del objeto creado, son únicamente los públicos. Para acceder a un campo o método se utiliza el modificador “.”, como se puede ver en el ejemplo. Con el mismo modificador también es posible acceder a campos y métodos estáticos, anteponiendo el nombre de la clase en donde están definidos. Herencia Para lograr herencia se utiliza la palabra reservada extends. La sintaxis básica es la siguiente: <%% class MyClass extends AnotherClass { … } %%> En este caso la clase MyClass hereda todos los atributos y métodos de la clase AnotherClass, incluyendo los estáticos. Dentro de la clase MyClass podremos acceder a todos los elementos que estén definidos en AnotherClass como públicos y protegidos. Los elementos definidos como privados son solo accesibles dentro del ámbito de la misma clase. También dentro de la clase MyClass podremos sobrescribir cualquier atributo o método definido en la clase AnotherClass como público o protegido. Para clarificar el uso de la herencia veamos un ejemplo: <%% class ParentClass{ public parentField = "parentField"; public function setParentField(newValue){ parentField = newValue; } public function getParentField(){ return parentField; } public function ParentClass(){ } } class SunClass extends ParentClass{ public sunField = "sunField"; public function setSunField(newValue){ sunField = newValue; } public function getSunField(){ return sunField; } 81 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier public function SunClass(){ } } class SunOfSunClass extends SunClass{ public sunField = "overrited"; public function SunOfSunClass(){ } } obj = new SunClass(); anotherObj = new SunOfSunClass(); Writer.echo(obj.getParentField()); Writer.echo(obj.getSunField()); obj.setParentField("newParentField"); obj.setSunField("newSunField"); Writer.echo(obj.getParentField()); Writer.echo(obj.getSunField()); Writer.echo(anotherObj.getParentField()); Writer.echo(anotherObj.getSunField()); anotherObj.setParentField("newParentField"); anotherObj.setSunField("newSunField"); Writer.echo(anotherObj.getParentField()); Writer.echo(anotherObj.getSunField()); %%> Palabras reservadas super y this La palabra reservada super se utiliza para acceder a atributos y métodos definidos en la clase padre. De este modo, se pueden acceder a atributos o métodos definidos en la clase padre, aún cuando la clase hija los redefina. <%% class Parent { public aField = "parentField"; public function aMethod() { return "parentMethod"; } public function Parent(){} } class Sun extends Parent { public aField = "sunField"; public function aMethod() { return "sunField"; } public function getSuperField() { return super.aField; } public function getSuperMethod() { 82 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier return super.aMethod(); } public function Sun(){} } sun = new Sun(); Writer.echo(sun.getSuperField()); Writer.echo(sun.getSuperMethod()); %%> En el ejemplo, las últimas sentencias están accediendo al atributo y método definidos en la clase padre. Otro uso de la palabra reservada super es en los constructores, la sintaxis es la siguiente: <%% class MyClass extends AnotherClass { public function MyClass() : super(...) { } } %%> Si utilizamos la palabra reservada super en un constructor, cuando se cree una nueva instancia del objeto, lo primero que sucederá es que se ejecutará el constructor del padre. La palabra super utilizada en este contexto equivale al llamado a una función y puede ser parametrizada, al pasar parámetros al constructor padre, se puede acceder a los parámetros del constructor propio. En cuanto a la palabra reservada this, sirve para hacer referencia a la propia instancia y de este modo desambigüar el uso de atributos o funciones. Si dentro de un método tenemos un parámetro con nombre x, y x también está definido como atributo del objeto. x por sí solo hará referencia al parámetro, mientras que this.x hará referencia al atributo. Podemos ver un ejemplo de este tipo de usos: <%% class MyClass{ private aField = "aField"; public function doWork(){ Writer.echo(this.aField); } public function MyClass(){ } } obj = new MyClass(); obj.doWork(); %%> 83 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Extensiones Un aspecto muy interesante que quisimos lograr sobre los arreglos es el uso de un pseudo lenguaje, algo similar a lo que logró la empresa microsoft sobre C# con la tecnología LinQ. De este modo, podremos lograr consultas sobre los elementos de un arreglo de una manera relativamente simple y fácil de leer, además de declarativa. Teniendo en cuenta que uno de los objetivos es procesar archivos xml para lograr un resultado y que las librerías que procesan xml retornan en su mayoría elementos en arreglos, esto sería sumamente útil. Para comprender la potencia de contar con estas características, veamos un ejemplo: File.create(__Config.getInputPath()).listFiles(). select(f => this.getFileInfo(f)). where( fInfo => !fInfo.isDirectory && fInfo.isXml && (this._modulesInclude == null || this._modulesInclude.containsValue(fInfo.fileName)) && (this._modulesExclude == null || !this._modulesExclude.containsValue(fInfo.fileName)) ).orderBy(fInfo => fInfo.originalFile, this._fileComparator). forEach(fInfo => { inputFound = true; docBuilder = wrap_javax_xml_parsers_DocumentBuilderFactory.newInstance().newDocumentBuilder(); doc = docBuilder.parseUri(fInfo.absolutePath); doc.normalize(); tpls.forEach(tpl => { Log(String.format("Running template %s (%s)", array(tpl.getId(), fInfo.fileName))); tpl.setModule(fInfo.fileName); tpl.run(doc); Log(""); }); }); La porción de código del ejemplo son líneas extraídas de uno de los templates escritos en nuestro lenguaje. Si bien se están utilizando características del lenguaje que aún no hemos explicado, se puede apreciar como de una manera simple, declarativa y en pocas líneas se pueden recorrer todos los archivos de entrada para ser procesados. Con el fin de lograr una implementación de la tecnología LinQ para el lenguaje C#, la empresa microsoft tuvo que introducir una serie de características al lenguaje, lo hizo a partir de la versión 4. Nuestra implementación tiene otro enfoque y es más limitada por el momento. La primer limitación es que funciona únicamente sobre arreglos, además no hemos implementado todos los métodos que deseamos, pero quedará pendiente para futuras versiones del lenguaje agregar nuevos métodos (se puede hacer de una manera relativamente sencilla). Además de procesar arreglos de una manera declarativa, se puede hacer eficientemente. Para lograr esto nosotros también tuvimos que introducir una serie de características, las cuales intentaremos explicar a continuación. Extensión e inicialización de objetos en la construcción Una de las características que hemos introducido al lenguaje es la posibilidad de extender e inicializar un objeto cuando se construye, para lo cual luego de la sentencia new sigue 84 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier una parte opcional para tal fin. La sintaxis es la siguiente: new Object(...) { … extensiones … { … inicialización ... } } Esto puede aplicarse a cualquier objeto de cualquier tipo. La sección de extensión puede estar vacía, en el caso de que no esté vacía puede contener cualquier definición válida dentro de una definición de clase, es decir que se pueden definir nuevos atributos y métodos que estarán disponibles únicamente para el objeto recién creado. Por otra parte, la sección de inicialización es opcional, las sentencias en esta sección se ejecutan en el ámbito del objeto recién creado. Esta sección es particularmente útil para inicializar objetos en la creación, lo cual combinado con el uso de expresiones lambda permitirá la definición de una especie de lenguaje sobre arreglos. Veamos un ejemplo para comprender la potencialidad de esta nueva característica, si ejecutamos el parser sobre un archivo con el siguiente contenido. <%% class Parent { public parentField; private parentPrivateField; public function parentFunction() { return parentPrivateField; } } public function Parent() {} sunFieldValue = "sunField"; obj = new Parent() { private sunField; public className; public localFuncAssigned; private function setParentPrivateField(value) { this.parentPrivateField = value; } public function getSunField() { return sunField; } { this.sunField = sunFieldValue; localVar = "parentPrivateField"; setParentPrivateField(localVar); className = typeof(this); localFuncAssigned = localFunc(); } }; function localFunc() { return "localFuncAssigned"; } 85 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Log(obj.getSunField()); Log(obj.parentFunction()); Log(obj.className); Log(localVar); Log(obj.localFuncAssigned); %%> El resultado será el siguiente. sunField parentPrivateField Parent parentPrivateField localFuncAssigned Expresiones lambda Otra extensión que fue necesario introducir en el lenguaje para lograr nuestro objetivo es la de expresiones lambda. En nuestro lenguaje una expresión lambda es un nuevo tipo, es el tipo __LamdaExpression. Este tipo no es ni un tipo ni una clase en sí mismo, si no que debe ser creado mediante una sintaxis especial (en realidad existen dos formas de crear una expresión lambda): (<parametros>) => <expression> Esta es la primer sintaxis con la cual se puede crear una expresión lambda, la sección de parámetros es igual que para cualquier otra función, por lo que también se aceptan parámetros opcionales. En el caso de que haya un único parámetro, los paréntesis son opcionales. Se pueden utilizar únicamente los paréntesis para indicar una expresión sin parámetros. Luego del símbolo “=>” debe ir una expresión, y el resultado de evaluar la expresión lambda es el resultado de evaluar la expresión que se encuentra luego del símbolo “=>” con los parámetros actuales. (<parametros>) => { <cuerpo> } Con la segunda forma de creación, luego del símbolo “=>” debe ir el cuerpo, al igual que si fuese el cuerpo de una función. En este caso pueden existir varias sentencias en lugar de una única expresión. El resultado de evaluar la expresión lambda será: • El resultado de la sentencia return dentro del cuerpo si es que esta sentencia existe. • NULL en caso contrario. Con la sintaxis de creación no se evalúa la expresión lambda, si no que se crea un objeto 86 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier de tipo __LambdaExpression. Este es un objeto especial que posee una única función eval. Esta función contendrá los parámetros y el cuerpo de la expresión lambda, y ejecutará esa expresión al ser invocada. Al ser objetos, las expresiones lambda pueden pasarse como parámetros a funciones e incluso, a otras expresiones lambda. Veamos algunos ejemplos clarificadores. <%% expr1 = (expr, par) => expr.eval(par); expr2 = par => par; Writer.echo(expr1.eval(expr2, "Yeah1!")); Writer.echo( ((expr, par) => expr.eval(par)).eval( par => par, "Yeah2!" ) ); %%> <%% factorial = (n) => { if( n == 0 ) return 1; return n * this.eval( n - 1 ); }; for( i = 0; i < 6; i++) Writer.echo(factorial.eval(i)); %%> Combinando estas características, se logra extender el lenguaje de manera que pueden escribirse funciones que tomen como parámetros expresiones lambda y aplicando estas expresiones sobre los elementos de arreglos logramos ciclar sobre arreglos y procesarlos de manera declarativa. El tipo ArrayQuery No es suficiente con procesar arreglos de manera declarativa, también tenemos que lograr que se haga de una manera eficiente. Supongamos que tenemos el siguiente arreglo. arr = array(1 => 1, 2 => 2, 3 => 3, 4 => 4); Y queremos filtrarlo de manera declarativa por valores menores a 3 y mayores a 1. Declarativamente podríamos escribir algo como lo que sigue. newArr = arr. where(v => v < 3). where(v => v > 1); 87 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Supongamos la función de extensión “where” trabaja directamente sobre arreglos. Es decir, al aplicarse sobre un arreglo, recorre todos los elementos del mismo y retorna un nuevo arreglo con los elementos filtrados. Ahora pensemos en la implementación de las sentencias del ejemplo. Sucedería algo como: 1. Se ejecuta el primer where. 2. La función where crea un nuevo arreglo vacío y luego cicla sobre los elementos del arreglo original. Para cada elemento, evalúa la expresión lambda “v => v < 3”, si el resultado es verdadero agrega el elemento al arreglo que retornará como resultado, en caso de que la evaluación de la expresión lambda de falso ese elemento no se agrega. 3. Luego la función where retorna el nuevo arreglo. 4. Se ejecuta el segundo where sobre el resultado del primero. 5. La función where crea un nuevo arreglo vacío y luego cicla sobre los elementos del arreglo original. Para cada elemento, evalúa la expresión lambda “v => v > 1”, si el resultado es verdadero agrega el elemento al arreglo que retornará como resultado, en caso de que la evaluación de la expresión lambda de falso ese elemento no se agrega. 6. El resultado del segundo where es retornado y asignado a la variable newArr. Con este modelo estaríamos ciclando sobre el arreglo 2 veces para aplicar un filtro, uno por cada función where. Esto, con llamados sucesivos de funciones sobre arreglos sería sumamente ineficiente. Por este motivo se introduce el concepto de funciones sobre arreglos de primer orden y funciones sobre arreglos de segundo orden. Las funciones de primer orden retornan variables de tipo __ArrayQuery, mientras que las de segundo órden pueden retornar cualquier tipo de resultado. Una variable de tipo __ArrayQuery no es un arreglo en sí misma. Podría verse como un conjunto de datos necesarios para aplicar funciones sobre arreglos. Puede pensarse como el siguiente conjunto de datos: • Un arreglo sobre el cuál operar. • Una lista de funciones a aplicar sobre los elementos del arreglo. Las funciones de primer orden son funciones que pueden aplicarse directamente sobre arreglos, o sobre variables de tipo __ArrayQuery. Su comportamiento es el siguiente: 1. Si son aplicadas sobre una variable de tipo Array, crean una de tipo __ArrayQuery, y suman a la información en el __ArrayQuery cuál es el arreglo sobre el cuál operar. De aquí en más trabajan sobre la variable de tipo __ArrayQuery. Si, en cambio son aplicadas sobre una variable de tipo __ArrayQuery, trabajan directamente sobre la misma. 2. Agregan una función a la lista de funciones del __ArrayQuery. 3. Retornan la variable de tipo __ArrayQuery. 88 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Las funciones de segundo orden, también son funciones que pueden aplicarse directamente sobre arreglos, o sobre variables de tipo __ArrayQuery. El comportamiento es similar a las de primer orden: 4. Si son aplicadas sobre una variable de tipo Array, crean una de tipo __ArrayQuery, y suman a la información en el __ArrayQuery cuál es el arreglo sobre el cuál operar. De aquí en más trabajan sobre la variable de tipo __ArrayQuery. Si, en cambio son aplicadas sobre una variable de tipo __ArrayQuery, trabajan directamente sobre la misma. 5. Agregan una función a la lista de funciones del __ArrayQuery. 6. Disparan la evaluación del __ArrayQuery. 7. Luego de la evaluación completa del __ArrayQuery, deciden cuál será el resultado final. Para formalizar el funcionamiento del __ArrayQuery, digamos que puede verse como la siguiente estructura de datos: __ArrayQuery { // Arreglo con cualquier tipo de elementos array(mix) arreglo; // Arreglo con una lista de n funciones a aplicar sobre los elementos del arreglo anterior array(fcn) funciones; } Donde: • n>0 • las funciones desde 1.. n-1 son de primer orden • la función número n es de segundo orden y un pseudo algoritmo para evaluar un __ArrayQuery sería como sigue. Para cada elemento elem del arreglo { PasarSiguiente = verdadero; Para cada función fcn del arreglo de funciones { Si PasarSiguiente { evaluar fcn con elem; PasarSiguiente = Preguntar a fcn si se debe evaluar la siguiente función sobre elem; } } } Resultado = Preguntar a la función número n el resultado; Retornar Resultado; Cada vez que se encuentra una función de segundo orden, se dispara automáticamente la evaluación del __ArrayQuery. De este modo, podemos lograr algo mucho más eficiente, ya que podemos acumular tantas funciones de primer orden como sea necesario y luego 89 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier evaluarlas todas ciclando únicamente una vez sobre el arreglo. A grandes rasgos, este es el modo en el que se procesan las funciones de extensión sobre arreglos. Para más información al respecto, referirse al proyecto CodeGenerator. Ahora, veamos como se implementaría el ejemplo al inicio de esta sección aplicando estos nuevos conceptos: newArr = arr. where(v => v < 3). where(v => v > 1). toArray(); En este caso: • where es una función de primer orden. • toArray es una función de segundo orden. • Cuando se encuentra la función toArray se dispara la ejecución del __ArrayQuery, que contiene información sobre las tres funciones aplicadas. • Al aplicarse la evaluación, se ciclará únicamente una vez sobre el arreglo original. • El primer where filtrará los valores >= a 3, por lo que al segundo llegarán únicamente los valores < a 3. • El segundo where filtrará los valores <= a 1, por lo que a la función toArray llegarán únicamente los valores x tal que 1 < x < 3. • toArray recolectará todos los valores que le lleguen y generará un nuevo arreglo. • El resultado asignado a la variable newArr será un arreglo con el valor 2. Funciones de extensión sobre arreglos Dada la explicación de como llegamos a implementar estas funciones de extensión, ahora haremos mención de cuáles son las que implementamos. Muchas de las funciones que deseamos implementar quedaron fuera de esta entrega, pero tenemos intenciones de agregar muchas más en el futuro. aggregate mix aggregate(mix seed, __LambdaExpression func = NULL, __LambdaExpression resultSelector = NULL) Aplica una función de acumulación sobre una secuencia. El valor de semilla es usado como valor inicial del acumulador y la función especificada es usada para seleccionar el valor del resultado. En el caso de que se invoque únicamente con un parámetro, este parámetro es utilizado como función de acumulación y no como semilla. Parámetros: seed – semilla a ser utilizada como valor inicial func (opcional) – función de acumulación resultSelector (opcional) – función de selección de resultado 90 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Retorna: Resultado de la acumulación. all Boolean all(__LambdaExpression predicate) Determina si todos los elementos de un arreglo cumplen una condición. El parámetro predicate debe ser una expresión lambda con uno o dos parámetros. En el caso de que tenga 2 parámetros se le pasará el valor del arreglo, si tiene dos, se le pasará clave y valor. Debe retornar un valor booleano. Ante una evaluación falsa del predicado corta el ciclo retornando falso. Parámetros: predicate – predicado a aplicar sobre los elementos del arreglo Retorna: Verdadero si todos los elementos del arreglo cumplen con el predicado; falso en caso contrario. any Boolean any(__LambdaExpression predicate) Determina si algún elemento de un arreglo cumple una condición. El parámetro predicate debe ser una expresión lambda con uno o dos parámetros. En el caso de que tenga 2 parámetros se le pasará el valor del arreglo, si tiene dos, se le pasará clave y valor. Debe retornar un valor booleano. Ante una evaluación verdadera del predicado, corta el ciclo retornando verdadero. Parámetros: predicate – predicado a aplicar sobre los elementos del arreglo Retorna: Verdadero si algún elemento del arreglo cumple con el predicado; falso en caso contrario average mix average(_LambdaExpression selector = NULL) Computa el promedio de entre los valores de un arreglo. Si el parámetro selector está presente, se utiliza para transformar los valores (o claves y valores) del arreglo. Parámetros: selector – selector para transformar los valores del arreglo. Retorna: El promedio de entre los valores del arreglo concat __ArrayQuery concat(array(mix) second) Concatena dos arreglos. Parámetros: second – arreglo a concatenar al final de la secuencia actual Retorna: Un arreglo que es el resultado de concatenar el arreglo actual, seguido del arreglo second 91 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier count Integer count(__LambdaExpression predicate) Cuenta los elementos de un arreglo que satisfacen una condición. El parámetro predicate debe tomar valor (o clave y valor) y retornar verdadero si el elemento se debe sumar a la cuenta. Parámetros: predicate – predicado a aplicar sobre los elementos del arreglo para determinar si deben ser contados Retorna: Número de elementos del arreglo para el cual el predicado es verdadero distinct __ArrayQuery distinct(__LambdaExpression comparer = NULL) Retorna los elementos diferentes de un arreglo. Si el parámetro comparer está presente se utiliza para comparar los elementos del arreglo dos a dos, debe tomar dos elementos del arreglo y retornar verdadero si y sólo si dichos elementos son iguales según la comparación que se intenta hacer. En el caso de que el parámetro no esté presente, se utiliza el comparador de igualdad default del lenguaje. Parámetros: comparer (opcional) – expresión que será utilizada para comparar los elementos del arreglo dos a dos Retorna: Un arreglo con los elementos diferentes de este arreglo según la comparación elementAtOrNull mix elementAtOrNull(Integer index) Retorna el elemento que se encuentra en la posición index del arreglo, comenzando a contar en la posición 0. En el caso de que no exista elemento en dicha posición, retorna el valor NULL. Parámetros: index – posición a buscar en el arreglo Retorna: El elemento en la posición index; o NULL si no existe tal elemento elementAt mix elementAtOr(Integer index) Retorna el elemento que se encuentra en la posición index del arreglo, comenzando a contar en la posición 0. En el caso de que no exista elemento en dicha posición, se produce un error irrecuperable. Parámetros: index – posición a buscar en el arreglo Retorna: El elemento en la posición index except __ArrayQuery except(array(mix) second, __LambdaExpression comparer = NULL) Produce el conjunto diferencia entre este arreglo y el arreglo second. Si el parámetro comparer está presente se utiliza para comparar los elementos del arreglo dos a dos, debe tomar dos elementos del arreglo y retornar verdadero si y sólo si dichos elementos son iguales según la comparación que se intenta hacer. 92 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier En el caso de que el parámetro no esté presente, se utiliza el comparador de igualdad default del lenguaje. Parámetros: second – arreglo con elementos a quitar del resultado comparer (opcional) – expresión para comparar los elementos de los arreglos dos a dos y determinar si son iguales Retorna: Arreglo resultante de la diferencia entre el actual y second firstOrNull mix firstOrNull(__LambdaExpression predicate = NULL) Retorna el primer elemento del arreglo que satisface la condición predicate. Predicate es una expresión que toma valor (o clave y valor) y debe retornar verdadero si el elemento del arreglo satisface la condición requerida. El bucle sobre el arreglo es cortado al encontrar un elemento que satisfaga la condición. Si el parámetro predicate no está presente simplemente se retorna el primer elemento del arreglo. En este caso, si el arreglo es un arreglo vacío se retorna NULL. Parámetros: predicate (opcional) – expresión que determina si un elemento cumple la condición para ser retornado Retorna: El primer elemento que cumple con la condición first mix first(__LambdaExpression predicate = NULL) Retorna el primer elemento del arreglo que satisface la condición predicate. Predicate es una expresión que toma valor (o clave y valor) y debe retornar verdadero si el elemento del arreglo satisface la condición requerida. El bucle sobre el arreglo es cortado al encontrar un elemento que satisfaga la condición. Si el parámetro predicate no está presente simplemente se retorna el primer elemento del arreglo. En este caso, si arreglo es un arreglo vacío se produce un error irrecuperable. Parámetros: predicate (opcional) – expresión que determina si un elemento cumple la condición para ser retornado Retorna: El primer elemento que cumple con la condición foreach void foreach(__LambdaExpression fcn) Cicla sobre el arreglo aplicando la función fcn. Esta función debe tomar el elemento del arreglo, o la clave y el valor. Si tiene algún valor de retorno será ignorado. Parámetros: fcn – expresión a ser aplicada sobre cada uno de los elementos del arreglo ifEmpty __ArrayQuery ifEmpty(mix elem) Si este arreglo tiene elementos es retornado, en caso contrario se retorna un arreglo con un único elemento: elem. Parámetros: elem – elemento a retornar en un arreglo si este arreglo es vacío 93 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Retorna: El arreglo; o un arreglo con el elemento elem si el arreglo era vacío lastOrNull mix lastOrNull(__LambdaExpression predicate = NULL) Retorna el último elemento del arreglo que satisface la condición predicate. Predicate es una expresión que toma valor (o clave y valor) y debe retornar verdadero si el elemento del arreglo satisface la condición requerida. Si el parámetro predicate no está presente simplemente se retorna el último elemento del arreglo. En este caso, si el arreglo es un arreglo vacío se retorna NULL. Parámetros: predicate (opcional) – expresión que determina si un elemento cumple la condición para ser retornado Retorna: El último elemento que cumple con la condición last mix last(__LambdaExpression predicate = NULL) Retorna el último elemento del arreglo que satisface la condición predicate. Predicate es una expresión que toma valor (o clave y valor) y debe retornar verdadero si el elemento del arreglo satisface la condición requerida. Si el parámetro predicate no está presente simplemente se retorna el último elemento del arreglo. En este caso, si el arreglo es un arreglo vacío se produce un error irrecuperable. Parámetros: predicate (opcional) – expresión que determina si un elemento cumple la condición para ser retornado Retorna: El último elemento que cumple con la condición orderBy __ArrayQuery orderBy(__LambdaExpression keySelector, __LambdaExpression comparer) Ordena los elementos de un arreglo en orden descendente. keySelector es utilizado para seleccionar los elementos a comparar, es decir, recibe un elemento del arreglo y debe retornar el elemento a comparar, dicho elemento puede ser el mismo del arreglo. comparer es utilizado para comparar los elementos dos a dos, es decir, recibe dos elementos retornados por keySelector y debe retornar: • 0 si y sólo si los elementos son iguales. • Un valor mayor a 0 si el primer parámetro es mayor al segundo. • Un valor menor a 0 si el primer parámetro es menor al segundo. Parámetros: keySelector – expresión para seleccionar los elementos a comparar comparer – expresión que compara dos elementos Retorna: Un nuevo arreglo con los elementos de este arreglo ordenados en forma descendente select __ArrayQuery select(__LambdaExpression selector) Proyecta los elementos de un arreglo en una nueva forma. Selector toma cada uno de los valores (o claves y valores) del arreglo y debe retornar dicho elemento en su nueva forma. 94 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Parámetros: selector – expresión para proyectar cada uno de los elementos del arreglo Retorna: Un nuevo arreglo con los valores de este arreglo proyectados por la expresión selector singleOrNull mix singleOrNull(__LambdaExpression predicate = NULL) Busca entre los elementos de este arreglo si existe alguno que cumpla con el predicado. Predicate es una lambda expression que toma cada valor (o clave y valor) del arreglo y debe retornar verdadero si y sólo si el elemento cumple con la condición deseada. Si el predicado no está presente, se considera que cualquier elemento del arreglo cumple con la condición. Si existe un único elemento que cumple con la condición, este elemento es retornado. Si no existen elementos que cumplan con la condición o existe más de uno, se retorna el valor NULL. Parámetros: predicate (opcional) – predicado para evaluar la condición sobre los elementos del arreglo Retorna: El único elemento que cumple con la condición; o NULL single mix single(__LambdaExpression predicate = NULL) Busca entre los elementos de este arreglo, si existe alguno que cumpla con el predicado. Predicate es una lambda expression que toma cada valor (o clave y valor) del arreglo y debe retornar verdadero si y sólo si el elemento cumple con la condición deseada. Si el predicado no está presente, se considera que cualquier elemento del arreglo cumple con la condición. Si existe un único elemento que cumple con la condición, este elemento es retornado. Si no existen elementos que cumplan con la condición, se produce un error irrecuperable. Parámetros: predicate (opcional) – predicado para evaluar la condición sobre los elementos del arreglo Retorna: El único elemento que cumple con la condición toArray array(mix) toArray() Retorna este arreglo. Si ya estamos hablando de un arreglo un arreglo con los mismos elementos es retornado. Si es un __ArrayQuery, se retorna un arreglo que está formado por todos los elementos del __ArrayQuery evaluado. Esta función no es útil por sí solo, si no al combinarla con otras funciones de primer orden, ya que sirve para disparar la evaluación de un __ArrayQuery y convertirlo en un arreglo del lenguaje. Retorna: Un arreglo con los mismos elementos de este arreglo where __ArrayQuery where(__LambdaExpression predicate) Retorna los elementos de este arreglo filtrados por la expresión predicate. Predicate debe ser una expresión lambda que toma cada uno de los valores (o claves y valores) del arreglo original y retorna verdadero si y sólo si el valor actual debe estar incluido en el arreglo que se devolverá como resultado. 95 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Parámetros: predicate – predicado que determina si los elementos del arreglo deben ser incluidos Retorna: Un nuevo arreglo con los elementos únicamente para los que predicate retornó verdadero Aclaración importante: Muchas de las expresiones lambda utilizadas por estas funciones cuentan con la posibilidad de tomar tanto la clave como el valor de los elementos del arreglo. Cabe destacar que la clave es correctamente pasada sólo a la primer función aplicada, es decir cuando el objeto sobre el cual se aplica es un arreglo del lenguaje. Si el objeto es de tipo __ArrayQuery y la expresión lambda espera la clave del elemento además del valor, recibirá el valor NULL como clave. Esto es porque los objeto de tipo __ArrayQuery no son realmente arreglos sino que se consideran un conjunto de elementos y por lo tanto sus elementos no están asociados a una clave. Manipulación de objetos Java Uno de los objetivos de nuestro lenguaje fue que sea fácil de extender, que no resulte difícil escribir una librería con clases de cualquier tipo. Pero como lograr este objetivo? Que mejor que permitir manipular objetos Java desde nuestro lenguaje. Para poder manipular objetos Java desde nuestro lenguaje damos soporte a dos funciones, java.lang.ClassLoader.addJar y java.lang.ClassLoader.loadClass. addJar public static void addJar(String jarPath) Agrega un archivo de tipo jar a los archivos de librerías java en los cuales se buscarán clases cuando se invoque loadClass. Parámetros: jarPath – locación física del archivo de librerías java loadClass public static void Class loadClass(String name) Lee una clase java, para ser más preciso del tipo java.lang.Class y la retorna. Parámetros: name – nombre completo (con el package) de la clase java Retorna: Objeto de tipo java.lang.Class Esta última función retorna un objeto de un tipo que es del lenguaje Java y no pertenece a nuestro propio lenguaje, sin embargo es tratado también como un objeto de nuestro lenguaje. Es decir que hay ciertos métodos que se pueden aplicar directamente sobre un objeto de tipo java.lang.Class como si fuese un objeto propio del lenguaje. Los métodos que están permitidos son los siguientes. 96 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier getConstructor public Constructor getConstructor(array(Class) parameterTypes) Retorna un objeto de tipo Constructor que refleja un constructor público de la clase que representa este objeto. El parámetro parameterTypes es un arreglo de objetos del tipo Class que identifica el tipo de los parámetros del constructor en el orden en el que fueron declarados. Parámetros: parameterTypes – el arreglo de parámetros Retorna: El objeto que representa el constructor público que machea con los parámetros especificados en el arreglo parameterTypes getField public Field getField(String name) Retorna un objeto de tipo Field que refleja un campo público de esta clase. El parámetro name es el nombre simple del campo que se desea obtener. También se busca en campos declarados en clases ancestro. Paráemtros: name – el nombre del campo a buscar Retorna: El objeto de tipo Field de esta clase especificado por name getMethod public Method getMethod(String name, array(Class) parameterTypes) Retorna un objeto de tipo Method que refleja un método público especificado en esta clase. El parámetro name es el nombre simple del método que se desea obtener. ParameterTypes es un arreglo con los tipos de los parámetros formales del método que se desea obtener, si es nulo es tratado como un arreglo vacío. Parametros: name – el nombre del método parameterTypes – lista de los tipos de los parámetros formales del método Retorna: El objeto de tipo método que refleja el método de esta clase que machea con name y parameterTypes Sobre un objeto de tipo Constructor se soporta un único método. NewInstance public Object newInstance(array(Object) initargs) Usa el constructor representado por este objeto para crear e inicializar un nuevo objeto del tipo del que este constructor declara, con los parámetros initargs específicos. El arreglo de parámetros puede ser de longitud 0 o NULL, en cuyo caso se trata como si fuese de longitud 0. Parámetros: initargs – arreglo de objetos para pasar al constructor como argumentos Retorna: Un objeto creado a partir del constructor que este objeto representa 97 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Sobre el tipo Field, se soportan dos métodos. get public Object get(Object obj) Retorna el valor actual del campo representado por este campo, sobre el objeto obj. Si el campo es estático, el parámetro obj es ignorado y podría ser NULL. Parámetros: obj – objeto sobre el cual obtener el valor del campo Retorna: Valor del campo representado por este campo set public void set(Object obj, Object value) Asigna al campo representado por este campo el valor especificado en value, sobre el objeto especificado en obj. Si se trata de un campo estático, el valor de obj es ignorado y podría ser NULL. Parámetros: obj – objeto sobre el cual se desea asignar el valor al campo representado por este objeto value – valor que se desea asignar al campo representado por este objeto Finalmente, sobre objetos de tipo Method se soporta también un único método. invoke public Object invoke(Object obj, array(Object) args) Invoca el método subyacente representado por este objeto, sobre el objeto especificado en obj, con los parámetros especificados en args. Si el método representado por este objeto es estático, el parámetro obj es ignorado y podría ser NULL. Si el método representado por este objeto no tiene argumentos, el parámetro args puede tener longitud 0 o ser NULL. Parámetros: obj – objeto sobre el cuál invocar el método representado por este objeto args – argumentos con los cuáles invocar el método Retorna: El valor retornado por la ejecución del método representado por este objeto, si es que tiene valor de retorno Con todas estas características es suficiente para crear y manipular cualquier objeto de tipo Java, también es suficiente para manipular cualquier clase Java. Es decir, ya podemos crear objetos, obtener y asignar valores de campos, invocar métodos sobre objetos, obtener y asignar valores de campos estáticos e invocar métodos estáticos. Aún así, todavía hay un objeto del lenguaje Java que no podemos manipular por completo, es el arreglo nativo de Java. Para manipular arreglos del lenguaje Java hemos introducido la clase JavaArray, la clase JavaArray tiene soporte para los siguiente métodos. 98 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier get public static Object get(Object arr, Integer index) Obtiene el valor del objeto en la posición index en el arreglo arr, comenzando en la posición 0. Parámetros: arr – el arreglo index – el índice Retorna: El valor en arr del objeto en la posición index getBoolean public static Boolean getBoolean(Object arr, Integer index) Obtiene el valor del objeto en la posición index en el arreglo arr, comenzando en la posición 0, como un Boolean. Parámetros: arr – el arreglo index – el índice Retorna: El valor en arr del objeto en la posición index getFloat public static Boolean getFloat(Object arr, Integer index) Obtiene el valor del objeto en la posición index en el arreglo arr, comenzando en la posición 0, como un Float. Parámetros: arr – el arreglo index – el índice Retorna: El valor en arr del objeto en la posición index getInt public static Boolean getInt(Object arr, Integer index) Obtiene el valor del objeto en la posición index en el arreglo arr, comenzando en la posición 0, como un Integer. Parámetros: arr – el arreglo index – el índice Retorna: El valor en arr del objeto en la posición index getLength public static Integer getLength(Object arr) Obtiene la longitud de el arreglo representado por arr. Parámetros: arr – arreglo sobre el cual se desea obtener la longitud 99 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Retorna: Un entero que representa la longitud del arreglo en arr newInstance public static Object newInstance(Class componentType, Integer length) Crea un nuevo arreglo con el tipo de los componentes componentType y de longitud length. Parámetros: componentType – tipo de los componentes del arreglo que se desea crear length – longitud del arreglo que se desea crear Retorna: Arreglo recién creado, cuyo tipo de los componentes es componentType y de longitud length newInstanceWithDimensions public static Object newInstanceWithDimensions(Class componentType, array(Integer) dimensions) Crea un arreglo, cuyos componentes son del tipo representado por componentType y de dimensions.length dimensiones. La longitud de la dimensión i es dimensions[i]. Parámetros: componentType – tipo de los componentes del arreglo a crear dimensions – arreglo de enteros, con la longitud de cada uno de los subarreglos a crear Retorna: Un arreglo según las especificaciones componentType y dimensions set public static void set(Object arr, Integer index, Object value) Asigna el valor value en la posición index del arreglo arr, comenzando a contar en la posición 0. Parámetros: arr – arreglo al cual se le desea asignar un valor index – índice en el cuál se desea asignar el valor value – valor a asignar setBoolean public static void setBoolean(Object arr, Integer index, Boolean value) Asigna el valor value en la posición index del arreglo arr, comenzando a contar en la posición 0, como un Boolean. Parámetros: arr – arreglo al cual se le desea asignar un valor index – índice en el cuál se desea asignar el valor value – valor a asignar setFloat public static void setFloat(Object arr, Integer index, Float value) Asigna el valor value en la posición index del arreglo arr, comenzando a contar en la posición 0, como un Float. Parámetros: arr – arreglo al cual se le desea asignar un valor 100 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier index – índice en el cuál se desea asignar el valor value – valor a asignar setInt public static void setInt(Object arr, Integer index, Integer value) Asigna el valor value en la posición index del arreglo arr, comenzando a contar en la posición 0, como un Integer. Parámetros: arr – arreglo al cual se le desea asignar un valor index – índice en el cuál se desea asignar el valor value – valor a asignar Ahora sí, con todas estas características introducidas, podemos manipular todo lo necesario sobre el lenguaje Java para escribir librerías en nuestro propio lenguaje que por debajo usen Java. De hecho, muchas de las librerías ya implementadas, utilizan esta técnica y en realidad son wrappers de librerías Java. Logging y manejo de errores Cualquier template o librería, tiene la posibilidad de informar progreso (o cualquier otra cosa) al usuario mediante la salida estándar. Para imprimir una cadena de caracteres, o tipo nativo por la salida estándar, se usa el método especial Log. <%% Log(""); Log("This is a String"); Log(1); Log(1.1); Log(true); %%> A su vez, también contamos con una infraestructura para informar errores y warnings, así como también para terminar la ejecución ante la presencia de un error fatal. Durante su ejecución, el parser va recolectando información sobre los errores que se producen. Al finalizar se pueden explorar estos logs programáticamente e informar al usuario sobre los mismos. Si se produce un error del que no es posible recuperarse, finaliza la ejecución del parser y se informa al usuario. <%% // Informa al parser que debe recolectar este warning para informarlo al final de su ejecución. Log.warning("This is a warning"); // Informa al parser que debe recolectar este error para informarlo al final de su ejecución. Log.error("This is an error"); // Finaliza la ejecución del parser indicándole que debe informar la causa del error fatal. Log.fatal("This is a fatal error"); %%> 101 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier El plugin desarrollado informará de estos errores mediante los mecanismos que provee el IDE a tal fin. Autoloader Cuando se intenta utilizar una clase inexistente se produce un error irrecuperable. Para que no sea necesario incluir un archivo por cada clase que se desea utilizar se introdujo una nueva característica. Existe una clase especial denominada Autoloader. Si no se encuentra una determinada clase según su nombre, antes de fallar, el parser intentará encontrar esta clase con los objetos registrados como Autoloaders. La clase Autoloader cuenta con los siguientes campos y métodos. DEFAULT_PRIORITY public static Float DEFAULT_PRIORITY La prioridad utilizada por default cuando ninguna otra prioridad fue asignada. Es decir, si se registran objetos sin invocar antes a Autoloader.setPriority, todos los objetos serán registrados con esta prioridad. add public static void add(Autoloader autoloader) Agrega un objeto a la lista de objetos a los cuales se interrogará para incluir clases que aún no existen ante su primer uso. La prioridad utilizada será la última que se ha asignado con Autoloader.setPriority, o Autoloader.DEFAULT_PRIORITY si nunca se ha invocado al método para asignar prioridad. SetPriority public static void setPriority(Float newPriority) Asigna una nueva prioridad al Autoloader, la próxima vez que se invoque al método Autoloader.add, el objeto será registrado con prioridad newPriority. Parámetros: newPriority – prioridad a asignar getPriority public static Float getPriority() Retorna la prioridad actual, es decir la prioridad con la que se registrará al próximo objeto mediante la invocación del método Autlodader.add. Retorna: Prioridad actual autload public void autload(String className) Este método no tiene implementación actual y debe ser reimplementado por todos los herederos. La idea de este método es que será invocado con el nombre de una clase que no se pudo encontrar aún, la ejecución del método debería intentar incluir un archivo si es que conoce en qué archivo está la especificación de la clase de nombre className. Luego de la inclusión la clase de nombre className ya debería existir. Parámetros: className – nombre de la clase que aún no existe y se está intentando utilizar 102 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Entonces, el parser sigue el siguiente pseudo algoritmo si no encuentra la definición de una clase. Sea className el nombre de la clase cuya definición no se ha encontrado. Para los autoloader registrados de 1.. n ordenados por prioridad de registración ascendente hacer { invocar autoloader.autoload(className) si existe className finalizar el ciclo y retornar si no existe class name autoloader = próximo autoloader en la lista } Error irrecuperable, className no existe! Una implementación típica de Autoloader es como sigue. <%% /** * This class is responsible for auto load all freelancesoft library * classes. * */ class FSLibAutoloader extends Autoloader { private specialClasses = array( "JavaWrapper" => __Path.getCurrentDirectory() $ "/java/wrap/JavaWrapper.fgl", "JavaConverter" => __Path.getCurrentDirectory() $ "/java/wrap/JavaConverter.fgl", "JavaArrayConverter" => __Path.getCurrentDirectory() $ "/java/wrap/JavaArrayConverter.fgl", "String" => __Path.getCurrentDirectory() $ "/java/wrap/java/lang/String.fgl", "Integer" => __Path.getCurrentDirectory() $ "/java/wrap/java/lang/Integer.fgl", "Float" => __Path.getCurrentDirectory() $ "/java/wrap/java/lang/Float.fgl", "Boolean" => __Path.getCurrentDirectory() $ "/java/wrap/java/lang/Boolean.fgl", "File" => __Path.getCurrentDirectory() $ "/java/wrap/java/io/File.fgl" ); /** * Tries to auto load a class. * * @param String className * The name of the class that must be loaded. */ public function autoload(className) { specialPath = this.specialClasses[className]; if (specialPath == null) { // Find if ther is as a java wrap file = File.create(__Path.getCurrentDirectory() $ "/java/" $ className.replaceAll("_", "/") $ ".fgl"); if(file.exists()) { Require.once(file.getAbsolutePath()); } else { // Find if ther is as a library class file = File.create(__Path.getCurrentDirectory() $ "/" $ className.replaceAll("_", "/") $ ".fgl"); if(file.exists()) { Require.once(file.getAbsolutePath()); } } } else { Require.once(specialPath); } } /** 103 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier * Default constructor. Simply adds this to Autoloader. * */ public function FSLibAutoloader() { } } Autoloader.add(new FSLibAutoloader()); %%> De este modo, no es necesario incluir un archivo cada vez que deseamos utilizar la clase definida en el mismo. Sino que estos objetos pueden encargarse de buscar estos archivos por nosotros. En el ejemplo, el objeto conoce la locación de los archivos que corresponden a ciertas clases especiales como Integer, Float, String, etc. para cualquier otro nombre de clase intentará buscar el archivo utilizando el carácter “_” como si fuese el separador para nombre de directorios. Si el archivo existe (tomando como base el directorio actual), lo incluirá. En cualquier otro caso se pasará a invocar al método autoload del próximo objeto según su prioridad o fallar si no existe tal implementación registrada. Writer Para crear y escribir los archivos generados, existe la clase especial writer con los siguientes métodos. open public static void open(String fileName) Abre un archivo para escribir. Luego de invocar open, las siguientes invocaciones a Writer.echo escribirán sobre el archivo recién abierto. A su vez, las secciones fuera de los delimitadores “<%%” y “%%>” se interpretarán al igual que si se invocara Writer.echo. Parámetros: fileName – nombre del archivo sobre el cual se desea escribir close public static void close() Cierra el último archivo abierto por una invocación a Writer.open. Si no existe un archivo abierto se producirá un error irrecuperable. echo public static void echo(Object toWrite) Intenta escribir un objeto sobre el último archivo abierto. El parámetro toWrite será convertido a String antes de ser escrito en el archivo. Si no existe un archivo abierto se producirá un error irrecuperable. Parámetros: toWrite – objeto a ser escrito sobre el último archivo abierto 104 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier La función especial @ La función @ es un alias de la función Writer.echo, pero funciona en diferentes contextos. También funciona fuera de los límites de “<%%” y “%%>”. Entonces, hay varias maneras de escribir en un archivo, por lo que las siguientes sentencias son equivalentes. <%% Writer.echo("to write"); %%> <%% @("to write"); %%> to Write @("to write") El hecho de que la función @ escape a los caracteres de scripting es particularmente útil en templates, para que sean más legibles, sin necesidad de tantos caracteres de escape. Configuración Para configurar el parser antes de ejecutarlo hay ciertos parámetros que pueden ser asignados y luego serán accesibles dentro del código escrito en nuestro lenguaje. Para esto existe la clase especial __Config con los siguientes métodos. getFsLibPath public static String getFsLibPath() Obtiene el directorio raíz en donde se encuentran las librerías que hemos implementado. Retorna: Directorio donde están nuestras librerías getConfigPath public static String getConfiigPath() Obtiene el directorio en donde los templates deberían buscar archivos de configuración. Retorna: Directorio en donde buscar archivos de configuración getTemplatesPath public static String getTemplatesPath() Obtiene el directorio en donde el parser buscará archivos de template. Retorna: Directorio donde el parser buscará archivos de template getInputPath public static String getInputPath() Obtiene el directorio donde el parser buscará archivos de entrada para que procesen los templates. Retorna: Directorio donde buscar archivos de entrada getTargetPath 105 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier public static String getTargetPath() Obtiene el directorio donde se espera que los templates generen contenido. Retorna: Directorio base en donde generar los archivos getTemplateMainFile public stati String getTemplateMainFile() Obtiene el nombre de los archivos principales de templates, es decir el primero que se ejecutará por cada template. El valor default de esta propiedad de configuración es main.fgl. Con lo cual el parser buscará este archivo en el directorio base en donde se deberían encontrar los templates. Retorna: Nombre del archivo principal de template get public static String get(String key) Obtiene un parámetro genérico de configuración asociado a una clave, que puede ser asignado desde el propio lenguaje. Parámetros: key – clave del parámetro de configuración Retorna: Valor del parámetro de configuración asociado a la clave key; o el valor NULL si no existe ningún valor asociado a la clave set publis static void set(String key, Object value) Asocia un valor a un parámetro de configuración, de modo que la próxima vez que se invoque __Config.get(key) se obtendrá el valor value. Parámetros: key – clave a la cual se le desea asignar el valor value value – nuevo valor para la clave key A su vez, existe otra clase especial con dos métodos, que sirven para que los archivos de template puedan hacer referencias relativas a su propia ubicación. Esta es la clase __Path y cuenta con los siguientes métodos. getCurrent public static String getCurrent() Obtiene la ruta absoluta el archivo que el parser está procesando actualmente. Retorna: Ruta absoluta del archivo que se está procesando getCurrentDirectory public static String getCurrentDirectory() 106 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Obtiene la ruta absoluta del directorio donde se encuentra el archivo que el parser está actualmente procesando. Retorna: Ruta absoluta del directorio en donde se encuentra el archivo actualmente en proceso Proyectos desarrollados A continuación, explicaremos los proyectos que hemos desarrollado para dar soporte a esta idea. No ahondaremos en demasiados detalles ya que el código está completamente comentado y se puede consultar el mismo para mayor detalle. Del mismo modo, se puede ejecutar la meta site sobre los proyectos y así consultar la documentación generada. CodeGeneratorPoms CodeGeneratorPoms es un proyecto multi-módulo, que consta de tres proyectos. Cada uno de ellos contiene un archivo pom que será utilizado como padre para descriptores de otros proyectos. El objetivo es contar con un lugar común donde se puedan definir características comunes que puedan compartir diferentes proyectos. Los subproyectos son los siguientes. • CodeGeneratorParentPom. Contiene un descriptor que directa o indirectamente es usado por casi todos los demás proyectos. Define características como: ◦ Variables que contienen versión actual del generador, directorio default para instalar librerías, directorio default para instalar templates, rutas para crear escenarios durante los tests, rutas para obtener librerías y templates durante los tests, url del archivo de estilos de programación definido. ◦ Versión de la máquina virtual utilizada. ◦ Configuración para limpiar escenarios creados durante los tests. ◦ Descripción de los repositorios internos maven. • CodeGeneratorLibraryParentPom. Contiene un descriptor con características utilizadas por todas las librerías desarrolladas como: ◦ Configuración para empaquetar una librería. ◦ Configuración para desempaquetar las librerías que se utilicen como dependencias. • CodeGeneratorTemplatesParentPom. Contiene un descriptor con características utilizadas por todos los templates desarrolladas como: ◦ Configuración para empaquetar un template. ◦ Configuración para desempaquetar los templates y librerías que se utilicen como dependencias. Además en este proyecto agregamos archivos ampliamente utilizados durante el 107 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier desarrollo. Dado que nuestro servidor no cuenta con recursos suficientes para ejecutar jenkins, creamos herramientas para simular corridas de builds. De este modo podemos asegurar que ante el impacto de un cambio en un proyecto no dejan de funcionar los proyectos que lo utilicen como dependencia. Una de las herramientas es deployall, se utiliza por línea de comandos y cuenta con los siguientes argumentos. Modo de uso: deployall.php [opciones] Sin opciones hace update por cvs, deploy de todos los proyectos y chequea cobertura del proyecto CodeGenerator Opciones: -cvsu -e -h Usuario cvs Nombre de los proyectos a excluir separado por comas Muestra esta ayuda -i -mvn -nc -nm -nu -vbox Nombre de los proyectos a incluir separado por comas Ociones Maven No correr cobertura:clean cobertucha:check sobre el proyecto CodeGenerator No Maven runs No cvs update Corre vboxdeployall.php con parámetros por default antes de finalizar Esta herramienta funciona tanto en entornos windows como linux. Dado que la mayor parte del tiempo desarrollamos sobre entornos linux, también agregamos el archivo vboxdeployall. Este hace casi lo mismo que el anterior, pero en realidad enciende una máquina virtual preconfigurada con un entorno windows. Es decir que podremos ver el resultado de deployall sobre la última versión del código en un entorno windows. Modo de uso: vboxdeployall.php [opciones] Sin opciones abre la máquina virtual, ejecuta deployall.php y apaga la máquina virtual Opciones: -mn Machine Name. Default: TesisXP -u -p SSH user. Default: codegenerator SSH pass. Default: codegenerator -a Arguments. Default: "-nc -cvsu cvsadmin -mvn clean\ install" 108 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier CodeGeneratorModules CodeGeneratorModules es un proyecto multi-módulo que contiene los proyectos principales del generador de código. Es decir, es el corazón del generador. Está compuesto por tres proyectos: CodeGeneratorCommon, CodeGeneratorCommonTests y CodeGenerator. CodeGeneratorCommon Este proyecto tiene principalmente dos objetivos. El primero es agrupar clases comunes que se utilizarán durante el parseo. El segundo es definir todas las interfaces necesarias para implementar un parser de nuestro lenguaje en su totalidad. Paquete freelancesoft.codegenerator.common Contiene la implementación de una pila que es utilizada por el parser como pila del lenguaje durante la interpretación. Además, varias clases Manager con implementaciones actuales. De este modo, la implementación actual de una clase a la cual se le delega un trabajo específico, puede cambiarse durante la ejecución. Todas estas clases tienen dos métodos estáticos, setCurrent para asignar la implementación actual y current para obtener la implementación actual. Estas clases son las siguientes: • ConfigurationManager. Con la implementación actual de Configuration. • ContextFactoryManager. Con la implementación actual de ContextFactory. • LogManager. Con la implementación actual de Recordable. • StackManager. Con la implementación actual de LanguageStack. • StackTraceManager. Con la implementación actual de Stack<TreeNode>. • WriterManager. Con la implementación actual de Writer. Al crear el parser, automáticamente serán asignadas todas las implementaciones actuales para estas interfaces pero pueden ser cambiadas en cualquier momento. Paquete freelancesoft.codegenerator.common.array Define la interfaz LanguageArray, la cual debe respetar cualquier implementación de un arreglo del lenguaje. Además, la excepción UnsupportedKeyException que puede ser arrojada por los métodos de LanguageArray. Paquete freelancesoft.codegenerator.common.arrayquery Define las interfaces necesarias para implementar el pseudo lenguaje sobre arreglos. 109 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier • ArrayQuery. Interfaz que debe respetar la estructura de datos que mantiene las funciones de primer y segundo orden aplicadas sobre un arreglo. Simplemente define métodos para agregar implementaciones de dichas funciones. • LastQueryStep. Interfaz que debe implementar una función de segundo orden sobre un arreglo del lenguaje. Define sólo dos métodos, simplemente creando un objeto que la implemente y registrándolo con una clase de tipo factory es suficiente para tener disponible una nueva función sobre arreglos. • NoLastQueryStep. Interfaz que debe implementar una función de primer orden sobre un arreglo del lenguaje. Define cuatro métodos, para que haya una nueva función de primer orden sobre arreglos disponible en el lenguaje se debe crear un objeto que respete esta interfaz y registrarlo con una clase de tipo factory. • QueryStep. Interfaz que define dos métodos que deben implementar tanto los objetos que representen funciones de primer orden, como los que representen funciones de segundo orden. Con estas interfaces y su correspondiente implementación, resulta relativamente sencillo agregar una nueva función sobre objetos de tipo arreglo del lenguaje. Paquete freelancesoft.codegenerator.common.configuration Define una única interfaz, Configuration. Es la que se debe implementar para dar soporte al objeto del lenguaje __Config. Paquete freelancesoft.codegenerator.common.context En todo lenguaje existen entidades que tienen un contexto en el cual ejecutan, como variables, clases, objetos, funciones, métodos. Este paquete contiene interfaces varias que deben implementarse para dar vida a esas entidades, también contiene las excepciones que manejan esos objetos. No nombraremos todas una por una, sólo diremos que existe una ContextFacotry, que se encarga de crear estos contextos y las demás clases representan esos contextos en sí mismos. Cuando el parser requiere la creación de una entidad, pedirá a la implementación de ContextFactory la creación de un contexto en el cual debe ejecutar esa entidad, pasando las características de la entidad. De este modo, ContextFactory, junto con la implementación de los contextos apropiados, toma ciertas “decisiones” sobre características del lenguaje (como por ejemplo el alcance de una variable según el contexto en el cuál fue definida) que no son inherentes al parser. Paquete freelancesoft.codegenerator.common.errorhandle Define interfaces y algunas clases que están relacionadas con el manejo de errores y warnings. Manejamos errores irrecuperables, para lo que define IrrecuperableError y IrrecuperableCause. La clase Record contiene información variada, 110 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier • Define todos los mensajes de error (en el futuro pude llegar a internacionalizarse de una manera muy sencilla). • Define los tipos de error (warning, error y error irrecuperable). • Además un objeto de este tipo contiene toda la información necesaria para informar adecuadamente sobre un warning o error. Como líneas (inicio y fin), columnas (inicio y fin) en las que se produjo el error, una descripción de la causa, el tipo, el archivo en el que se produjo y la excepción asociada al error (si es que existe). Este paquete también contiene la interfaz Recordable, la cual debe ser implementada por el manejador de errores del lenguaje. Paquete freelancesoft.codegenerator.common.parser Define interfaces varias que tienen que ver con la implementación del core del parser. • LanguageParser. Interfaz que debe respetar la implementación del parser propiamente dicho. • TreeNode. Interfaz para cualquier nodo en el árbol de parseo. En general estos nodos son interpretados en una segunda pasada del parser. • BreakableNode. Interfaz que implementan los nodos que deben ser informados si se ejecuta una sentencia break, en general para detener la ejecución de un ciclo o evitar la ejecución de las sentencias siguientes. • ContinueableNode. Interfaz que implementan los nodos que deben ser informados ante la ejecución de una sentencia continue, en general para evitar que se ejecute un conjunto de sentencias que le siguen al continue. • ReturnableNode. Similar a la anterior, pero la implementan nodos que deben ser informados si se ejecuta una sentencia return. • FieldInitializatorNode. Interfaz que implementan ciertos nodos que inicializan campos de clases o instancias de clases. • ConstructorInitializatorNode. Interfaz para la característica extendida del lenguaje de inicialización dinámica de objetos. Paquete freelancesoft.codegenerator.common.writer Define sólo la interfaz Writer, la cual debe ser implementada para objetos que se encargan de escribir los archivos de salida que son generados por una interpretación. El parser delega todas las escrituras en objetos de este tipo, ya sea que se produzcan por la sentencia Writer.echo, por el alias @ o por cadenas fuera de los delimitadores “<%%” y “%%>”. 111 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier CodeGeneratorCommonTests Este proyecto contiene clases que darán soporte para realizar tests unitarios automáticos en todos los otros tests. Hay una clase principal, ParserTestBase, que servirá como clase base para todas las clases de tests. El resto de las clases que se encuentran en este proyecto dan soporte a ParserTestBase. Entre otras cosas, ParserTestBase brinda: • Inicialización preconfigurada para tests del propio parser, librerías o templates. Con la cual contamos con un parser inicializado apropiadamente para correr sobre un escenario. • Mocking sobre la salida estándar, con lo cual se puede verificar que una ejecución ha mostrado lo que se desee sobre la salida estándar. • Mocking sobre la clase Writer, con lo cual se puede verificar que se ha invocado a Writer.echo, Writer.open y Writer.close con los parámetros adecuados luego de una ejecución. • Creación de escenarios sobre los cuáles podemos agregar archivos de configuración, entrada, librería o templates a gusto. • Posibilidad de verificar que dado un escenario y un conjunto de archivos de salida, los archivos que generó el parser luego de su ejecución coinciden con los esperados. • Posibilidad de obtener archivos específicos localizados en una dirección relativa al test actualmente corriendo. • Posibilidad de agregar archivos con contenido específico. • Posibilidad de verificar contenido de archivos específicos contra archivos dentro de un proyecto o agregados con el mecanismo anterior. • Posibilidad de indicar que se espera un determinado warning, error o error irrecuperable. • Posibilidad de verificar que los errores y warnings que antes se han indicado se produjeron luego de una ejecución. • Verificación automática de que no se han producido errores o warnings inesperados, es decir que no se produjo ninguno que no se ha indicado previamente. Heredando de esta clase resulta relativamente sencillo llevar a cabo un test unitario automático, tanto para el parser en sí mismo como para una librería o template. Por ejemplo, un test de una librería no cuenta con muchas líneas. @Test public void M_createAttribute_Success() throws Throwable { initializeTest(); 112 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier // setup writerExpectations.one("wrap_org_w3c_dom_Attr").checking(); // execute parser.parseUnit(); ContextFactoryManager.current().getRootContext().getField(XML_VARIABLE_NAME_1, null).setValue( getFixedFilePath("xml/testFile_001.xml")); parser.interpret(); // verify writerExpectations.assertIsSatisfied(); } Pero sí está verificando muchas cosas. Si su ejecución es exitosa, sabemos que el método createAttribute crea una instancia de la clase esperada, también se invocó Writer.echo con el argumento esperado y además, no se produjo ningún error o warning durante la ejecución del parser. Verificar todo esto implicaría una cantidad de código considerable, pero puede hacerse en unas cuantas líneas gracias al soporte brindado por la clase ParserTestBase. Del mismo modo, un test para verificar el comportamiento de un template es relativamente sencillo. @Test public void setterDefault() throws Throwable { // Setup scenario.input.addFile("setterDefault.xml", "default.xml"); scenario.expectedTarget.addDirectory("classes"). addDirectory("default"). addFile("setterDefault.php", "DefaultEntity.php"); writerExpectations.logUntilRun("default", "php-entities"). oneOfLog("Generating class DefaultEntity"). oneOfLog(""). checking(); // Run parser.parseUnit(); parser.interpret(); // Verify writerExpectations.assertIsSatisfied(); scenario.verifyTarget(); } No sólo se está verificando el comportamiento correcto (archivos generados) para una determinada entrada, sino que también sabemos que se informó del progreso mediante la salida estándar de manera correcta y que no se produjeron errores o warnings. CodeGenerator El proyecto CodeGenerator es uno de los principales ya que aquí se implementan todas las interfaces definidas en CodeGeneratorCommon. Además cuenta con la implementación del parser. La distribución de los paquetes también es similar a la del proyecto CodeGeneratorCommon. 113 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Paquete freelancesoft.codegenerator.array En este paquete se encuentra la implementación de todas las interfaces definidas en freelancesoft.codegenerator.common.array. Además también cuenta con otras clases para dar soporte a arreglos del lenguaje. La única clase que es visible por fuera del paquete es LanguageArrayFactory, que contiene un método de creación para arreglos que retorna un objeto de tipo LanguageArray. De este modo, la implementación interna de los arreglos queda totalmente encapsulada. Paquete freelancesoft.codegenerator.arrayquery En este paquete encontramos las clases que hacen posible las funciones de primer y segundo orden sobre arreglos del lenguaje. Dentro de lo posible, la implementación también se encuentra confinada al paquete. Una de las principales clases públicas (visibles por fuera del paquete) es la clase ArrayQueryFactory. Esta clase es utilizada directamente por el parser para consultar sobre las funciones disponibles. Cuenta con un bloque estático donde se debe registrar cada implementación de una función para que esté disponible y se pueda utilizar desde el lenguaje. De momento tiene registradas las siguientes implementaciones. static { initializators.put(WhereStep.NAME, WhereStep.getInitializator()); initializators.put(ConcatStep.NAME, ConcatStep.getInitializator()); initializators.put(IfEmptyStep.NAME, IfEmptyStep.getInitializator()); initializators.put(ToArrayStep.NAME, ToArrayStep.getInitializator()); initializators.put(AggregateStep.NAME, AggregateStep.getInitializator()); initializators.put(AllStep.NAME, AllStep.getInitializator()); initializators.put(AnyStep.NAME, AnyStep.getInitializator()); initializators.put(AverageStep.NAME, AverageStep.getInitializator()); initializators.put(CountStep.NAME, CountStep.getInitializator()); initializators.put(DistinctStep.NAME, DistinctStep.getInitializator()); initializators.put(ElementAtStep.NAME, ElementAtStep.getInitializator()); initializators.put(ElementAtOrNullStep.NAME, ElementAtOrNullStep.getInitializator()); initializators.put(ExceptStep.NAME, ExceptStep.getInitializator()); initializators.put(FirstStep.NAME, FirstStep.getInitializator()); initializators.put(FirstOrNullStep.NAME, FirstOrNullStep.getInitializator()); initializators.put(LastStep.NAME, LastStep.getInitializator()); initializators.put(LastOrNullStep.NAME, LastOrNullStep.getInitializator()); initializators.put(SingleStep.NAME, SingleStep.getInitializator()); initializators.put(SingleOrNullStep.NAME, SingleOrNullStep.getInitializator()); initializators.put(SelectStep.NAME, SelectStep.getInitializator()); initializators.put(ForEachStep.NAME, ForEachStep.getInitializator()); initializators.put(OrderByStep.NAME, OrderByStep.getInitializator()); } Paquete freelancesoft.codegenerator.arrayquery.steps Lo único que contiene esta clase es la implementación de cada una de las funciones sobre arreglos registradas en ArrayQueryFactory. Son todas clases que implementan LastQueryStep o NoLastQueryStep. Desde el lenguaje, cada una de estas clases se puede ver como una función disponible sobre objetos de tipo arreglo. Para contar con una nueva función, es suficiente con agregar la implementación aquí y registrar una instancia en ArrayQueryFactory. 114 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Paquete freelancesoft.codegenerator.configuration Este paquete contiene la implementación por default de la interfaz Configuration definida en freelancesoft.codegenerator.common.configuration. Paquete freelancesoft.codegenerator.context En este paquete están todas las implementaciones default de las interfaces definidas en freelancesoft.codegenerator.common.context. Estas clases son las que realmente “deciden” cual es el alcance de las entidades, como variables, objetos, métodos accedidas dentro del lenguaje. Además están definidos aquí la mayor parte de los métodos a los cuáles se les da soporte nativo. Como métodos sobre tipos Float, Integer, String, Object, JavaArray, JavaClass, JavaConstructor, JavaField, JavaMethod y el tipo nativo array. Paquete freelancesoft.codegenerator.errorhandle Contiene sólo la implementación default de la interfaz Recordable definida en freelancesoft.codegenerator.common.errorhandle. Es la encargada del manejo de warnings, errores y errores irrecuperables. El parser la utilizará para llevar cuenta de los errores producidos durante su ejecución, luego se la puede consultar para determinar todas las anomalidades producidas. Paquete freelancesoft.codegenerator.parser En este paquete se encuentra la implementación del parser y todos los nodos que pueden formar parte del árbol generado por el parser. En su mayoría son clases generadas por JavaCC y luego modificadas según las necesidades. Paquete freelancesoft.codegenerator.utils Define utilidades varias utilizadas por el resto de los paquetes en este proyecto como métodos para manejo de Strings, Reflection, implementación de URLClassLoader (para poder incluir librerías java de tipo jar desde el lenguaje), etc. Paquete freelancesoft.codegenerator.writer Contiene la implementación por default de la interfaz Writer definida en freelancesoft.codegenerator.common.writer. Es el encargado de crear los archivos a generar, escribir sobre los mismos y cerrarlos. CodeGeneratorLibrary Este proyecto contiene una herramienta que sirve como ayuda durante la implementación de las librerías del lenguaje que sean wrapper de librerías java y también la implementación de todas nuestras librerías. 115 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Sobre las librerías que son wrappers de clases Java no las documentaremos en su totalidad, sólo mencionaremos lo que implementamos sin explicar su uso. Para más información se puede consultar la api de Java sobre el paquete donde se encuentre la clase para la cuál está implementado el wrapper. CodeGeneratorLibraryGenerator Aquí hemos implementado una aplicación de uso interno que sirve para generar una clase que emula el comportamiento de una del lenguaje Java. En general los objetos del lenguaje implementados como librerías cuentan internamente con un objeto Java, al aplicar un método en el lenguaje este delega la tarea en la instancia del objeto Java. Si ingresamos el nombre de una clase Java en el campo de texto que aparece arriba a la izquierda y presionamos el botón Load, la aplicación utilizará la herramienta Javadoc para obtener toda la información posible sobre la clase cuyo nombre acabamos de ingresar, Incluyendo los comentarios. Una vez cargados los datos, permite modificar casi cualquier aspecto que pueda ser configurado para generar un wrapper escrito en nuestro lenguaje. Una vez hechas las modificaciones deseadas, se puede presionar el botón save y los datos serán persistidos en una base de datos. 116 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Luego, presionando el botón generar se generan una serie de archivos: • Un único archivo del lenguaje que representa la implementación de la clase Java, esta puede contener campos, campos estáticos, métodos, métodos estáticos y constructor. Todos tomarán los comentarios originales, pero es posible modificarlos. • Un archivo de test escrito en el lenguaje Java, con el esqueleto de todos los métodos para realizar tests unitarios sobre la clase que se está intentando generar. Cada uno de los métodos en este archivo es sólo un esqueleto que luego debe ser modificado por el desarrollador. • Un archivo del lenguaje por cada método de test que será utilizado por el mismo. Este también cuenta sólo con un esqueleto y debe ser modificado. Esta herramienta cuenta con diversos aspectos de configuración, no la documentaremos por completo ya que la consideramos una herramienta de uso interno y como ayuda al 117 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier desarrollador y no es parte del generador de código. Por esta misma razón, tampoco cuenta con tests unitarios sobre la propia herramienta como sí lo hacen el resto de los proyectos. Essentials En la librería Essentials hemos incluido. • JavaConverter. Clase base para conversores entre elementos del lenguaje Java y de nuestro propio lenguaje y viceversa. • JavaWrapper. Clase base para wrappers del lenguaje Java escritos en nuestro lenguaje. • JavaArrayConverter. Clase base para conversores de arreglos del lenguaje Java y de nuestro lenguaje y viceversa. • FSLibAutoloader. Autoloader que permite incluir archivos de librerías ante el primer uso de una determinada clase. Conoce ubicaciones de clases especiales y como incluirlas. A su vez usa convenciones sobre nombres para incluir otras. • File. Wrapper de la clase Java.io.File, es incluido en esta librería porque es utilizado por FSLibAutoloader para verificar la existencia de archivos. Por este motivo es parte de esta librería y no de Java.Io File La clase File es un wrapper de la clase java java.io.File. Sobre la misma hemos implementado lo siguiente. Clase File, extiende de Object. Campos estáticos public static String separator public static String pathSeparator Métodos estáticos public static array(File) listRoots() public static File createTempFile(String prefix, String suffix, File directory = null) Métodos public String getName() public String getParent() public File getParentFile() public String getPath() public Boolean isAbsolute() public String getAbsolutePath() public File getAbsoluteFile() public String getCanonicalPath() 118 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier public File getCanonicalFile() public wrap_java_net_URL toURL() public wrap_java_net_URI toURI() public Boolean canRead() public Boolean canWrite() public Boolean exists() public Boolean isDirectory() public Boolean isFile() public Boolean isHidden() public Integer lastModified() public Integer length() public Boolean createNewFile() public Boolean delete() public array(String) list() public array(File) listFiles() public Boolean mkdir() public Boolean mkdirs() public Boolean renameTo(File dest) public Boolean setLastModified(Integer time) public Boolean setReadOnly() public Boolean setWritable(Boolean writable, Boolean ownerOnly = null) public Boolean setReadable(Boolean readable, Boolean ownerOnly = null) public Boolean setExecutable(Boolean executable, Boolean ownerOnly = null) public Boolean canExecute() public Integer compareTo(File pathname) public Boolean equals(Object obj) public Integer hashCode() public String toString() Constructor / método de creación public static File create(String pathname) JavaLang Sobre la librería JavaLang hemos implementado las siguientes clases. Boolean La clase Boolean es un wrapper de la clase java java.lang.Boolean. Sobre la misma hemos implementado lo siguiente. Clase Boolean, extiende de Object. Campos estáticos public static Boolean TRUE public static Boolean FALSE public static java.lang.Class TYPE 119 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Métodos estáticos public static Boolean parseBoolean(String s) public static Boolean valueOf(String s) public static String toString(Boolean b) Float La clase Float es un wrapper de la clase java java.lang.Float. Sobre la misma hemos implementado lo siguiente. Clase Float, extiende de Object. Campos estáticos public static Float POSITIVE_INFINITY public static Float NEGATIVE_INFINITY public static Float NaN public static Float MAX_VALUE public static Float MIN_NORMAL public static Float MIN_VALUE public static Integer MAX_EXPONENT public static Integer MIN_EXPONENT public static Integer SIZE public static java.lang.Class TYPE Métodos estáticos public static String toString(Float f) public static Float valueOf(String s) public static Float parseFloat(String s) public static Boolean isNaN(Float v) public static Boolean isInfinite(Float v) public static Integer compare(Float f1, Float f2) Integer La clase Integer es un wrapper de la clase java java.lang.Integer. Sobre la misma hemos implementado lo siguiente. Clase Integer, extiende de Object. Campos estáticos public static Integer MIN_VALUE public static Integer MAX_VALUE public static java.lang.Class TYPE public static Integer SIZE Métodos estáticos public static String toString(Integer i, Integer radix = null) 120 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier public static Integer parseInt(String s, Integer radix = null) public static Integer valueOf(String s, Integer radix = null) public static Integer rotateLeft(Integer i, Integer distance) public static Integer rotateRight(Integer i, Integer distance) public static Integer reverse(Integer i) public static Integer signum(Integer i) public static Integer reverseBytes(Integer i) String La clase String es un wrapper de la clase java java.lang.String. Sobre la misma hemos implementado lo siguiente. Clase String, extiende de Object. Métodos estáticos public static String format(String format, array(Object) args) JavaNet Sobre la librería JavaNet hemos implementado las siguientes clases. wrap_java_net_URI La clase wrap_java_net_URI es un wrapper de la clase java java.net.URI. Sobre la misma hemos implementado lo siguiente. Clase wrap_java_net_URI, extiende de Object. Métodos public wrap_java_net_URI parseServerAuthority() public wrap_java_net_URI normalize() public wrap_java_net_URI resolve(String str) public wrap_java_net_URI relativize(wrap_java_net_URI uri) public wrap_java_net_URL toURL() public String getScheme() public Boolean isAbsolute() public Boolean isOpaque() public String getRawSchemeSpecificPart() public String getSchemeSpecificPart() public String getRawAuthority() public String getAuthority() public String getRawUserInfo() public String getUserInfo() public String getHost() public Integer getPort() public String getRawPath() public String getPath() 121 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier public String getRawQuery() public String getQuery() public String getRawFragment() public String getFragment() public Boolean equals(Object ob) public Integer hashCode() public Integer compareTo(wrap_java_net_URI that) public String toString() public String toASCIIString() Constructor / método de creación public static wrap_java_net_URI create(String str) wrap_java_net_URL La clase wrap_java_net_URL es un wrapper de la clase java java.net.URL. Sobre la misma hemos implementado lo siguiente. Clase wrap_java_net_URL, extiende de Object. Métodos public String getQuery() public String getPath() public String getUserInfo() public String getAuthority() public Integer getPort() public Integer getDefaultPort() public String getProtocol() public String getHost() public String getFile() public String getRef() public Boolean equals(Object obj) public Integer hashCode() public Boolean sameFile(wrap_java_net_URL other) public String toString() public String toExternalForm() public wrap_java_net_URI toURI() Constructor / método de creación public static wrap_java_net_URL create(String spec) JavaIo Por el momento, no hay clases implementadas en este proyecto, pero contiene los tests para la clase File implementada en la librería Essentials. Esto es porque nos encontramos con la necesidad de contar con la clase File para implementar el Autoloader, por lo que tuvo que formar parte de la librería Essentials. 122 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier En el futuro, podremos agregar más implementaciones del paquete java.io a esta librería. JavaxXml Sobre la librería JavaxXml hemos implementado las siguientes clases. wrap_javax_xml_parsers_DocumentBuilder La clase wrap_javax_xml_parsers_DocumentBuilder es un wrapper de la clase java javax.xml.parsers.DocumentBuilder. Sobre la misma hemos implementado lo siguiente. Clase wrap_javax_xml_parsers_DocumentBuilder, extiende de Object. Métodos public Boolean isNamespaceAware() public Boolean isValidating() public Boolean isXIncludeAware() public wrap_org_w3c_dom_Document newDocument() public wrap_org_w3c_dom_Document parseUri(String uri) public wrap_org_w3c_dom_Document parseFile(File f) public void reset() wrap_javax_xml_parsers_DocumentBuilderFactory La clase wrap_javax_xml_parsers_DocumentBuilderFactory es un wrapper de la clase java javax.xml.parsers.DocumentBuilderFactory. Sobre la misma hemos implementado lo siguiente. Clase wrap_javax_xml_parsers_DocumentBuilderFactory, extiende de Object. Métodos estáticos public static wrap_javax_xml_parsers_DocumentBuilderFactory newInstance() Métodos public Boolean isCoalescing() public Boolean isIgnoringComments() public Boolean isIgnoringElementContentWhitespace() public Boolean isNamespaceAware() public Boolean isValidating() public Boolean isXIncludeAware() public wrap_javax_xml_parsers_DocumentBuilder newDocumentBuilder() public void setCoalescing(Boolean coalescing) public void setIgnoringComments(Boolean ignoreComments) public void setIgnoringElementContentWhitespace(Boolean whitespace) public void setNamespaceAware(Boolean awareness) public void setXIncludeAware(Boolean state) 123 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier wrap_javax_xml_XMLConstants La clase wrap_javax_xml_XMLConstants es un wrapper de la clase javax.xml.XMLConstants. Sobre la misma hemos implementado lo siguiente. java Clase wrap_javax_xml_XMLConstants, extiende de Object. Campos estáticos public static String DEFAULT_NS_PREFIX public static String FEATURE_SECURE_PROCESSING public static String NULL_NS_URI public static String RELAXNG_NS_URI public static String W3C_XML_SCHEMA_INSTANCE_NS_URI public static String W3C_XML_SCHEMA_NS_URI public static String W3C_XPATH_DATATYPE_NS_URI public static String XMLNS_ATTRIBUTE public static String XMLNS_ATTRIBUTE_NS_URI public static String XML_DTD_NS_URI public static String XML_NS_PREFIX public static String XML_NS_URI wrap_org_w3c_dom_Attr La clase wrap_org_w3c_dom_Attr es un wrapper de la clase java org.w3c.dom.Attr. Sobre la misma hemos implementado lo siguiente. Clase wrap_org_w3c_dom_Attr, extiende de wrap_org_w3c_dom_Node. Métodos public String getName() public wrap_org_w3c_dom_Element getOwnerElement() public wrap_org_w3c_dom_TypeInfo getSchemaTypeInfo() public Boolean getSpecified() public String getValue() public Boolean isId() public void setValue(String value) wrap_org_w3c_dom_CDATASection La clase wrap_org_w3c_dom_CDATASection es un wrapper de la clase java org.w3c.dom.CDATASection. Sobre la misma hemos implementado lo siguiente. Clase wrap_org_w3c_dom_CDATASection, extiende de wrap_org_w3c_dom_Text. wrap_org_w3c_dom_CharacterData La clase wrap_org_w3c_dom_CharacterData es un wrapper de la clase java org.w3c.dom.CharacterData. Sobre la misma hemos implementado lo siguiente. 124 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Clase wrap_org_w3c_dom_CharacterData, extiende de wrap_org_w3c_dom_Node. Métodos public void appendData(String arg) public void deleteData(Integer offset, Integer count) public String getData() public Integer getLength() public void insertData(Integer offset, String arg) public void replaceData(Integer offset, Integer count, String arg) public void setData(String data) public String substringData(Integer offset, Integer count) wrap_org_w3c_dom_Comment La clase wrap_org_w3c_dom_Comment es un wrapper de la org.w3c.dom.Comment. Sobre la misma hemos implementado lo siguiente. clase java clase java Clase wrap_org_w3c_dom_Comment, extiende de wrap_org_w3c_dom_CharacterData. wrap_org_w3c_dom_Document La clase wrap_org_w3c_dom_Document es un wrapper de la org.w3c.dom.Document. Sobre la misma hemos implementado lo siguiente. Clase wrap_org_w3c_dom_Document, extiende de wrap_org_w3c_dom_Node. Métodos public array(wrap_org_w3c_dom_Node) getElementsByTagName(String tagname) public String getXmlEncoding() public wrap_org_w3c_dom_Node importNode(wrap_org_w3c_dom_Node importedNode, Boolean deep) public wrap_org_w3c_dom_Comment createComment(String data) public wrap_org_w3c_dom_Text createTextNode(String data) public wrap_org_w3c_dom_DocumentFragment createDocumentFragment() public wrap_org_w3c_dom_Element createElement(String tagName) public wrap_org_w3c_dom_Attr createAttribute(String name) public wrap_org_w3c_dom_Element getElementById(String elementId) public void setStrictErrorChecking(Boolean strictErrorChecking) public wrap_org_w3c_dom_CDATASection createCDATASection(String data) public wrap_org_w3c_dom_Element getDocumentElement() public String getInputEncoding() public array(wrap_org_w3c_dom_Node) getElementsByTagNameNS(String namespaceURI, String localName) public Boolean getXmlStandalone() public String getXmlVersion() public void setXmlVersion(String xmlVersion) public void normalizeDocument() 125 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier public Boolean getStrictErrorChecking() public wrap_org_w3c_dom_Node renameNode(wrap_org_w3c_dom_Node n, String namespaceURI, String qualifiedName) public void setXmlStandalone(Boolean xmlStandalone) public wrap_org_w3c_dom_DOMConfiguration getDomConfig() public wrap_org_w3c_dom_Element createElementNS(String namespaceURI, String qualifiedName) public wrap_org_w3c_dom_Node adoptNode(wrap_org_w3c_dom_Node source) public wrap_org_w3c_dom_DocumentType getDoctype() public wrap_org_w3c_dom_Attr createAttributeNS(String namespaceURI, String qualifiedName) public String getDocumentURI() public void setDocumentURI(String documentURI) wrap_org_w3c_dom_DocumentFragment La clase wrap_org_w3c_dom_DocumentFragment es un wrapper de la clase java org.w3c.dom.DocumentFragment. Sobre la misma hemos implementado lo siguiente. Clase wrap_org_w3c_dom_DocumentFragment, extiende de wrap_org_w3c_dom_Node. wrap_org_w3c_dom_DocumentType La clase wrap_org_w3c_dom_DocumentType es un wrapper de la clase java org.w3c.dom.DocumentType. Sobre la misma hemos implementado lo siguiente. Clase wrap_org_w3c_dom_DocumentType, extiende de wrap_org_w3c_dom_Node. Métodos public String getName() wrap_org_w3c_dom_DOMConfiguration La clase wrap_org_w3c_dom_DOMConfiguration es un wrapper de la clase java org.w3c.dom.DOMConfiguration. Sobre la misma hemos implementado lo siguiente. Clase wrap_org_w3c_dom_DOMConfiguration, extiende de Object. Métodos public Boolean canSetParameter(String name, Object value) public Object getParameter(String name) public array(String) getParameterNames() public void setParameter(String name, Object value) wrap_org_w3c_dom_Element La clase wrap_org_w3c_dom_Element es un wrapper de la org.w3c.dom.Element. Sobre la misma hemos implementado lo siguiente. clase java 126 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Clase wrap_org_w3c_dom_Element, extiende de wrap_org_w3c_dom_Node. Métodos public String getAttribute(String name) public String getAttributeNS(String namespaceURI, String localName) public wrap_org_w3c_dom_Attr getAttributeNode(String name) public wrap_org_w3c_dom_Attr getAttributeNodeNS(String namespaceURI, String localName) public array(wrap_org_w3c_dom_Node) getElementsByTagName(String name) public array(wrap_org_w3c_dom_Node) getElementsByTagNameNS(String namespaceURI, String localName) public wrap_org_w3c_dom_TypeInfo getSchemaTypeInfo() public String getTagName() public Boolean hasAttribute(String name) public Boolean hasAttributeNS(String namespaceURI, String localName) public void removeAttribute(String name) public void removeAttributeNS(String namespaceURI, String localName) public wrap_org_w3c_dom_Attr removeAttributeNode(wrap_org_w3c_dom_Attr oldAttr) public void setAttribute(String name, String value) public void setAttributeNS(String namespaceURI, String qualifiedName, String value) public wrap_org_w3c_dom_Attr setAttributeNode(wrap_org_w3c_dom_Attr newAttr) public wrap_org_w3c_dom_Attr setAttributeNodeNS(wrap_org_w3c_dom_Attr newAttr) public void setIdAttribute(String name, Boolean isId) public void setIdAttributeNS(String namespaceURI, String localName, Boolean isId) public void setIdAttributeNode(wrap_org_w3c_dom_Attr idAttr, Boolean isId) wrap_org_w3c_dom_Node La clase wrap_org_w3c_dom_Node es un wrapper de la clase java org.w3c.dom.Node. Sobre la misma hemos implementado lo siguiente. Clase wrap_org_w3c_dom_Node, extiende de Object. Campos estáticos public static Integer ATTRIBUTE_NODE public static Integer CDATA_SECTION_NODE public static Integer COMMENT_NODE public static Integer DOCUMENT_FRAGMENT_NODE public static Integer DOCUMENT_NODE public static Integer DOCUMENT_POSITION_CONTAINED_BY public static Integer DOCUMENT_POSITION_CONTAINS public static Integer DOCUMENT_POSITION_DISCONNECTED public static Integer DOCUMENT_POSITION_FOLLOWING public static Integer DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC public static Integer DOCUMENT_POSITION_PRECEDING public static Integer DOCUMENT_TYPE_NODE public static Integer ELEMENT_NODE 127 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier public static Integer ENTITY_NODE public static Integer ENTITY_REFERENCE_NODE public static Integer NOTATION_NODE public static Integer PROCESSING_INSTRUCTION_NODE public static Integer TEXT_NODE Métodos public wrap_org_w3c_dom_Node appendChild(wrap_org_w3c_dom_Node newChild) public wrap_org_w3c_dom_Node cloneNode(Boolean deep) public Integer compareDocumentPosition(wrap_org_w3c_dom_Node other) public array(wrap_org_w3c_dom_Node) getAttributes() public String getBaseURI() public array(wrap_org_w3c_dom_Node) getChildNodes() public wrap_org_w3c_dom_Node getFirstChild() public wrap_org_w3c_dom_Node getLastChild() public String getLocalName() public String getNamespaceURI() public wrap_org_w3c_dom_Node getNextSibling() public String getNodeName() public Integer getNodeType() public String getNodeValue() public wrap_org_w3c_dom_Document getOwnerDocument() public wrap_org_w3c_dom_Node getParentNode() public String getPrefix() public wrap_org_w3c_dom_Node getPreviousSibling() public String getTextContent() public Boolean hasAttributes() public Boolean hasChildNodes() public wrap_org_w3c_dom_Node insertBefore(wrap_org_w3c_dom_Node newChild, wrap_org_w3c_dom_Node refChild) public Boolean isDefaultNamespace(String namespaceURI) public Boolean isEqualNode(wrap_org_w3c_dom_Node arg) public Boolean isSameNode(wrap_org_w3c_dom_Node other) public String lookupNamespaceURI(String prefix) public String lookupPrefix(String namespaceURI) public void normalize() public wrap_org_w3c_dom_Node removeChild(wrap_org_w3c_dom_Node oldChild) public wrap_org_w3c_dom_Node replaceChild(wrap_org_w3c_dom_Node newChild, wrap_org_w3c_dom_Node oldChild) public void setNodeValue(String nodeValue) public void setPrefix(String prefix) public void setTextContent(String textContent) wrap_org_w3c_dom_Text La clase wrap_org_w3c_dom_Text es un wrapper de la clase java org.w3c.dom.Text. Sobre la misma hemos implementado lo siguiente. 128 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Clase wrap_org_w3c_dom_Text, extiende de wrap_org_w3c_dom_CharacterData. Métodos public String getWholeText() public Boolean isElementContentWhitespace() public wrap_org_w3c_dom_Text replaceWholeText(String content) public wrap_org_w3c_dom_Text splitText(Integer offset) wrap_org_w3c_dom_TypeInfo La clase wrap_org_w3c_dom_TypeInfo es un wrapper de la org.w3c.dom.TypeInfo. Sobre la misma hemos implementado lo siguiente. clase java Clase wrap_org_w3c_dom_TypeInfo, extiende de Object. Campos estáticos public static Integer DERIVATION_EXTENSION public static Integer DERIVATION_LIST public static Integer DERIVATION_RESTRICTION public static Integer DERIVATION_UNION Métodos public String getTypeName() public String getTypeNamespace() public Boolean isDerivedFrom(String typeNamespaceArg, String typeNameArg, Integer derivationMethod) FsTemplating En este proyecto hay dos clases que servirán como base para otras clases desarrolladas como templates. Estas clases son fs_templating_Configurable, que servirá para configurar un template de manera sencilla y fs_templating_Template que servirá como base para las clases principales de templates. fs_templating_Configurable Supongamos que estamos desarrollando un template, si deseamos que este template sea configurable siguiendo las convenciones, el mismo deberá configurarse utilizando un archivo en el directorio a tal fin con el nombre del template. Para que esto pueda hacerse de manera automática, el template debe extender de la clase fs_templating_Configurable. Esta clase cuenta únicamente con un método. configure protected function void configure(String filePath) Busca en el directorio de configuración si es que existe un archivo de nombre filePath, si este existe lo incluye para configurar este objeto. 129 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Parámetros; filePath – nombre del archivo de configuración a buscar fs_templating_Template Esta clase es una clase que servirá como base para los archivos principales de los templates, extiende de fs_templating_Configurable ya que deseamos que todos nuestros templates sean configurables según los estándares. Cuenta con varios campos y métodos, algunos de los métodos deben ser reimplementados al heredar de esta clase. De este modo, simulamos un método abstracto, este tipo de métodos cuenta con una implementación que lo único que hace es disparar un error irrecuperable para informar que debe ser reimplementado. Los campos y métodos de esta clase son los siguientes. _module protected String _module Durante la ejecución de un template, cada archivo xml que se encuentre en el directorio para utilizar como entrada de la ejecución se considera un módulo del sistema a implementar. De este modo durante el desarrollo de un sistema real, podemos ir agregando archivos en este directorio a medida que avanza el proyecto y ejecutar el parser sólo sobre un conjunto de módulos. Con lo cual podremos generar código para diferentes etapas del proyecto. Este campo contendrá el nombre del módulo actual para el que se está generando. _target protected String _target Este campo contiene el subdirectorio a donde el template agregará archivos, tomando como base el directorio configurado como target, en el caso en el que no se estén utilizando módulos. Si no es sobrescrito, contiene un string vacío, con lo cual los archivos se generarán directamente sobre el directorio configurado como target. _targetIfModule protected String _targetIfModule Este campo contiene el subdirectorio donde el template agregará archivos, tomando como base el directorio configurado como target, en el caso en el que sí se están utilizando módulos para desarrollar el sistema. El string será formateado utilizando el nombre del módulo actual. Si no es sobrescrito, contiene el string “/%s”, con lo cual los archivos serán alojados en un subdirectorio con el nombre del módulo actual dentro del directorio configurado como target. open protected function void open(String fileName) Este método encapsula un llamado a la función Writer.open para un template especifico. Utilizará los valores de los campos internos de este objeto y el nombre de archivo fileName, para crear un archivo dentro de la carpeta correspondiente de nombre fileName. Parámetros: fileName – nombre del archivo en el cuál se desea escribir 130 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier close protected function void close() Encapsula al método Writer.close, en realidad lo único que hace es invocar al método close de la clase Writer. Se agregó para ser consistente con el método open de esta misma clase. getId public function String getId() Este método se considera abstracto y debe ser reimplementado, caso contrario se producirá un error irrecuperable al ser invocado. Debe retornar un identificador para este template, este identificador debe ser único con respecto a todos los templates que se utilizen. En general retorna el nombre del template. Por ejemplo, para el template phpEntities, habrá un archivo main.fgl con una clase extendiendo de esta, que retornará "php-entities". Retorna: Un identificador único setModule public function void setModule(String moduleName) Asigna el módulo para el cual se está ejecutando actualmente. Parámetros; moduleName – nombre del módulo actual setTarget public function void setTarget(String target) Asigna el subdirectorio que debe ser utilizado dentro del directorio configurado como target para generar archivos. Parámetros: target – subdirectorio a ser utilizado por este template getTarget public function String getTarget() Obtiene el subdirectorio que debe ser utilizado dentro del directorio configurado como target para generar archivos. Retorna: subdirectorio a ser utilizado para generar archivos en este template setTargetIfModule public function void setTargetIfModule(String target) Asigna el subdirectorio que debe ser utilizado dentro del directorio configurado como target para generar archivos. Para el caso en el que se están utilizando módulos durante la generación. Parámetros: target – subdirectorio a ser utilizado por este template getTargetIfModule public function String getTargetIfModule() Obtiene el subdirectorio que debe ser utilizado dentro del directorio configurado como target para generar 131 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier archivos. Para el caso en el que se están utilizando módulos durante la generación. Retorna: subdirectorio a ser utilizado para generar archivos en este template initialize public function void initialize() Este método se considera abstracto y debe ser reimplementado, caso contrario se producirá un error irrecuperable al ser invocado. Será invocado automáticamente y está pensado para que el template se inicialice. En el cuerpo de esta función podría, por ejemplo, copiar archivos al directorio target que no son dependientes de las entidades para las cuáles se está generando código. Por ejemplo si el template es encargado de generar una vista web para un conjunto de entidades, dentro de este método podría copiar archivo comunes como hojas de estilo. run public function void run(wrap_org_w3c_dom_Document document) Este método se considera abstracto y debe ser reimplementado, caso contrario se producirá un error irrecuperable al ser invocado. Será invocado automáticamente y debe generar el código basándose en el archivo xml que se le pasa como argumento. Parámetros: document – documento creado a partir de un archivo xml de entrada para la generación Dadas estas clases, contamos con una base para la implementación de templates. De este modo, cada template se encargará de un trabajo especifico para el cuál fue pensado y no debe preocuparse por otras cuestiones. CodeGeneratorTemplates En este proyecto se pueden encontrar las implementaciones de los templates que hemos desarrollado. De momento sólo encontraremos dos (pero la idea es que vaya creciendo), TemplateManager y PhpEntities. TemplateManager La librería TemplateManager contiene un único archivo, main.fgl. Este será el archivo principal con el cual el parser comenzará su ejecución. Este archivo contiene la definición de una única clase, tpl_fs_TemplateManager. Esta clase será la encargada de administrar todos los templates que haya, tomando los archivos de entrada al generador e invocando a los métodos abstractos initialize y run definidos en fs_templating_Template. tpl_fs_TemplateManager Esta clase administra todos los demás templates, es la responsable de seleccionar, inicializar y correr templates. El archivo main.fgl es el primer archivo incluido por el parser con su configuración por default. Básicamente, una instancia de esta clase incluye templates. Luego, inicializa cada uno de 132 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier ellos mediante una invocación al método initialize. Después busca por archivos xml de entrada e invoca al método run para cada uno de los templates, pasando como entrada al documento xml parseado. Esta clase implementa el patrón singleton mediante el método instance. Con lo cual obtendremos la única instancia del administrador de templates mediante la ejecución de tpl_fs_TemplateManager.instance(). No haremos una explicación extensiva de los métodos implementados, pero sí explicaremos como debe hacer un template para quedar registrado con el administrador y así correr, tal vez, junto con otros templates. Para quedar registrado, un template debe ejecutar el método register con una instancia de fs_templating_Template, por ejemplo el template PhpEntities se registra con la siguiente línea. tpl_fs_TemplateManager.instance().register(new php_entities_Template()); A partir de ese momento, los métodos que reimplementa sobre la clase fs_templating_Template serán ejecutados de manera automática por el administrador de templates. Este administrador puede ser configurado incluyendo un archivo de nombre "templatemanager.fgl" dentro del directorio configurado para buscar archivos de configuración. Algunos de los aspectos que se pueden configurar son los siguientes. • Con los métodos include y exclude, pueden agregarse identificadores de templates para incluir o excluir directamente. • Con los métodos runInclude y runExclude, los templates serán incluidos, pero se puede evitar la ejecución del método run. • Con los métodos initInclude e initExclude, puede evitarse la ejecución del método initialize sobre determinados templates. • Por default, el administrador intentará utilizar todos los archivos que encuentre en el directorio de archivos xml de entrada. Con los métodos moduleInclude y moduleExclude puede cambiarse este comportamiento. Un archivo típico de configuración podría contener líneas como las siguientes. <%% %%> this.setExclude(array("logging-template-1")); this.setInitInclude(array()); this.setRunInclude(array("logging-template-1")); this.setModulesExclude(array("module1")); this.setRunExclude(array("logging-template-2")); En todos estos casos, el administrador considerará todos los templates y módulos disponibles, salvo que se invoque al método correspondiente de inclusión. Si se ha invocado al método de exclusión, esos templates o módulos serán excluidos de la lista inicial . 133 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier PhpEntities Este template cuenta con un conjunto de clases y templates escritos en nuestro lenguaje para generar entidades (clases) para el lenguaje PHP. La entrada será un archivo xml que describirá una lista de entidades. Por cada entidad se puede describir una lista de atributos. El template generará una clase por cada entidad en el archivo xml con los atributos descriptos y los métodos de acceso para esos atributos. El contenido del archivo xml es muy variado y permite generar las clases de una manera diferente. Puede contener descripción que cambiará sobre la clase generada aspectos como: • Nombre de la clase generada. • Comentario para la clase generada. En el caso de que no se describa se generarán comentarios default. • Atributos, también con su comentario y tipo o no, en el caso de que no se describan se tomarán valores default para los comentarios. • Métodos de acceso para cada atributo, que pueden ser generados o no. Para cada método de acceso se pueden describir los comentarios o no. en el caso de que no se describan comentarios, se generarán a comentarios por default. A su vez, hay aspectos más globales, es decir que no varían para cada entidad generada si no que se pueden configurar colocando el archivo “php-entities.fgl” dentro del directorio de configuración. Ahí se pueden configurar aspectos como nombre de una clase base a ser utilizada por todas las entidades generadas, modificación del directorio en donde se generarán los archivos para las clases, posibilidad de usar el nombre de módulo para el cuál se está generando código como prefijo para el nombre de las clases. Un archivo de configuración típica podría contener las siguientes líneas. <%% this.setBaseClass("BaseEntity"); this.setTargetIfModule("/"); this.setUseModuleAsPrefix(true); %%> En cuanto a los archivos xml de entrada, son sumamente flexibles. Un ejemplo típico de su contenido es el siguiente. <?xml version="1.0" encoding="UTF-8"?> <cg:generation xmlns:cg="http://xml.freelance-soft.com/codegenerator" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xml.freelancesoft.com/codegenerator http://devel.freelance-soft.com/xml/schemas/codegenerator/phpentities.xsd"> <entities> <entity name="DefaultEntity"> <attributes> <attribute name="attr1" /> <attribute name="attr2" /> </attributes> </entity> 134 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier </entities> </cg:generation> Como puede apreciarse en el ejemplo, existe un esquema para los archivos xml de entrada que define todas las posibilidades válidas para describir las entidades. Este es de gran utilidad, tanto para validar un archivo de entrada como para conocer cuáles son todas las posibilidades y puede consultarse para mayor información. Si ejecutamos el parser únicamente con este template y sin configuración adicional, utilizando un archivo de entrada con el contenido del ejemplo anterior, encontraremos en el directorio configurado como target un archivo de nombre DefaultEntity.php con el siguiente contenido. <?php /** * Represents DefaultEntity. * */ class DefaultEntity { /** * @var mixed */ private $attr1; /** * @var mixed */ private $attr2; /** * Sets attr1. * * @param mixed $attr1 the attr1 to set * */ public function setAttr1($attr1) { $this->attr1 = $attr1; } /** * Gets attr1. * * @return mixed the attr1 * */ public function getAttr1() { return $this->attr1; } /** * Sets attr2. * * @param mixed $attr2 the attr2 to set * */ public function setAttr2($attr2) { $this->attr2 = $attr2; } /** * Gets attr2. * 135 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier * @return mixed the attr2 * */ public function getAttr2() { return $this->attr2; } /** * Constructs DefaultEntity. * * @return DefaultEntity Constructed DefaultEntity * */ public function DefaultEntity() { } } ?> Utilizando archivos de entrada más complejos, la generación será mucho mas interesante y de gran utilidad durante el desarrollo de un proyecto empresarial basado en el lenguaje PHP. FSSchemas En este proyecto hay varios archivos xml. En la raíz de los recursos encontraremos el archivo fs_checks.xml. Este archivo define las políticas del estilo de programación que utilizamos durante el desarrollo. Es el archivo utilizado por CheckStyle para verificar estas políticas al compilar nuestros proyectos. Los archivos que se encuentran en el subdirectorio schemas/codegenerator son los esquemas para los archivos xml de entrada al parser. De momento, sólo cuenta con dos archivos: entities.xsd y php-entities.xsd. Al indicar en un archivo xml que alguno de estos es su esquema, cualquier IDE como Eclipse o Netbeans validará el xml y de este modo podemos asegurarnos que los templates reciben la entrada que esperan para generar el código correspondiente. Si ejecutamos la fase install sobre este proyecto. mvn install Todos estos archivos serán subidos al servidor utilizando maven antrun. Con este plugin se eliminarán del servidor todos los archivos mediante un comando ftp, luego serán subidos al servidor todos los archivos también mediante un comando ftp. La configuración para lograr esto en el archivo pom.xml del proyecto es la siguiente: <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-antrun-plugin</artifactId> <executions> <execution> <id>ftp-xml-install</id> <phase>install</phase> <configuration> <tasks> <ftp action="del" server="${ftp.xml.server}" remotedir="/var/www/html/xml" 136 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier userid="${ftp.xml.user}" password="${ftp.xml.pass}" depends="yes" verbose="yes"> <fileset> <include name="**" /> </fileset> </ftp> <ftp action="rmdir" server="${ftp.xml.server}" remotedir="/var/www/html/xml" userid="${ftp.xml.user}" password="${ftp.xml.pass}" depends="yes" verbose="yes"> <fileset> <include name="**" /> </fileset> </ftp> <ftp action="send" server="${ftp.xml.server}" remotedir="/var/www/html/xml" userid="${ftp.xml.user}" password="${ftp.xml.pass}" depends="yes" verbose="yes"> <fileset dir="${project.build.outputDirectory}"> <include name="**" /> </fileset> </ftp> </tasks> </configuration> <goals> <goal>run</goal> </goals> </execution> </executions> entities.xsd Este archivo contiene el esquema padre para cualquier archivo xml que servirá como entrada para la generación. En sí, contiene la descripción de un archivo que identificará las entidades que se desean generar y sus atributos. <?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://xml.freelance-soft.com/codegenerator" xmlns:cg="http://xml.freelance-soft.com/codegenerator" elementFormDefault="unqualified"> <xsd:element name="generation"> <xsd:annotation> <xsd:documentation>Root node of an xml input file.</xsd:documentation> </xsd:annotation> <xsd:complexType> <xsd:sequence> <xsd:element name="entities"> <xsd:annotation> <xsd:documentation>List of entities that should be generated by freelancesoft generation language.</xsd:documentation> </xsd:annotation> <xsd:complexType> <xsd:sequence> <xsd:element name="entity" type="cg:entityType" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:complexType> </xsd:element> <xsd:complexType name="entityType"> <xsd:annotation> <xsd:documentation>This node represents a single entity, a list of entities will be passed in to defined templates and then themplates will generate code.</xsd:documentation> </xsd:annotation> <xsd:sequence> <xsd:element name="attributes" minOccurs="0" maxOccurs="unbounded"> <xsd:annotation> <xsd:documentation>List of attributes that belongs to an entity.</xsd:documentation> 137 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier </xsd:annotation> <xsd:complexType> <xsd:sequence> <xsd:element name="attribute" type="cg:attributeType" maxOccurs="unbounded"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:sequence> <xsd:attribute name="name" type="xsd:string" use="required"/> </xsd:complexType> <xsd:complexType name="attributeType"> <xsd:annotation> <xsd:documentation>An attribute of an entity. It has only a name, but each new schema could add their own properties and new attributes to this type.</xsd:documentation> </xsd:annotation> <xsd:attribute name="name" type="xsd:string" use="required"/> </xsd:complexType> </xsd:schema> php-entities.xsd Este esquema extiende al anterior agregando todos las posibilidades para un documento de entrada para el template php-entities. <?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="http://xml.freelance-soft.com/codegenerator" xmlns:cg="http://xml.freelancesoft.com/codegenerator" elementFormDefault="unqualified"> <xsd:redefine schemaLocation="${schemas.basedir}/codegenerator/entities.xsd"> <xsd:complexType name="attributeType"> <xsd:complexContent> <xsd:extension base="cg:attributeType"> <xsd:sequence> <xsd:element name="comment" minOccurs="0" type="xsd:string"> <xsd:annotation> <xsd:documentation> Custom comment, if it is present this will be the attribute comment. </xsd:documentation> </xsd:annotation> </xsd:element> <xsd:element name="setter" minOccurs="0" type="cg:accessorType"> <xsd:annotation> <xsd:documentation> Setter method for current attribute. </xsd:documentation> </xsd:annotation> </xsd:element> <xsd:element name="getter" minOccurs="0" type="cg:accessorType"> <xsd:annotation> <xsd:documentation> Getter method for current attribute. </xsd:documentation> </xsd:annotation> </xsd:element> </xsd:sequence> <xsd:attribute name="modifier" type="cg:modifierType" default="private" use="optional"> <xsd:annotation> <xsd:documentation> Change modifier used in internal attribute of the class. By default it is private. </xsd:documentation> 138 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier </xsd:annotation> </xsd:attribute> <xsd:attribute name="type" type="xsd:string" default="mixed" use="optional"> <xsd:annotation> <xsd:documentation> This type is used in comments. In this way you can use code assistance. Default is mixed. </xsd:documentation> </xsd:annotation> </xsd:attribute> </xsd:extension> </xsd:complexContent> </xsd:complexType> <xsd:complexType name="entityType"> <xsd:complexContent> <xsd:extension base="cg:entityType"> <xsd:sequence> <xsd:element name="comment" minOccurs="0" type="xsd:string"> <xsd:annotation> <xsd:documentation> Custom comment, if it is present this will be the class comment. </xsd:documentation> </xsd:annotation> </xsd:element> <xsd:element name="constructor" minOccurs="0"> <xsd:annotation> <xsd:documentation> Constructor of this entity. </xsd:documentation> </xsd:annotation> <xsd:complexType> <xsd:sequence> <xsd:element name="comment" minOccurs="0" type="xsd:string"> <xsd:annotation> <xsd:documentation> Custom comment, if it is present this will be the class comment. </xsd:documentation> </xsd:annotation> </xsd:element> </xsd:sequence> <xsd:attribute name="generate" type="xsd:boolean" default="true" use="optional"> <xsd:annotation> <xsd:documentation> Determines if a constructor should be generated. By defalt it is true. </xsd:documentation> </xsd:annotation> </xsd:attribute> <xsd:attribute name="magic" type="xsd:boolean" default="false" use="optional"> <xsd:annotation> <xsd:documentation> Determines if a should be generated with __construct name. </xsd:documentation> </xsd:annotation> </xsd:attribute> </xsd:complexType> </xsd:element> </xsd:sequence> </xsd:extension> </xsd:complexContent> </xsd:complexType> </xsd:redefine> <xsd:complexType name="accessorType" > <xsd:annotation> <xsd:documentation> Describes an accesor, ie a setter or getter method. 139 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier </xsd:documentation> </xsd:annotation> <xsd:sequence> <xsd:element name="comment" minOccurs="0" type="xsd:string"> <xsd:annotation> <xsd:documentation> Custom comment, if it is present this will be the class comment. </xsd:documentation> </xsd:annotation> </xsd:element> </xsd:sequence> <xsd:attribute name="generate" type="xsd:boolean" default="true" use="optional"> <xsd:annotation> <xsd:documentation> Determines if an accessor should be generated. By default it is true. </xsd:documentation> </xsd:annotation> </xsd:attribute> <xsd:attribute name="modifier" type="cg:modifierType" default="public" use="optional"> <xsd:annotation> <xsd:documentation> Change modifier used in accessor, ie setter or getter. Default is public. </xsd:documentation> </xsd:annotation> </xsd:attribute> </xsd:complexType> <xsd:simpleType name="modifierType"> <xsd:annotation> <xsd:documentation>Possible values for a modifier.</xsd:documentation> </xsd:annotation> <xsd:restriction base="xsd:string"> <xsd:enumeration value="private"> <xsd:annotation> <xsd:documentation>private value.</xsd:documentation> </xsd:annotation> </xsd:enumeration> <xsd:enumeration value="protected"> <xsd:annotation> <xsd:documentation>protected value.</xsd:documentation> </xsd:annotation> </xsd:enumeration> <xsd:enumeration value="public"> <xsd:annotation> <xsd:documentation>public value.</xsd:documentation> </xsd:annotation> </xsd:enumeration> <xsd:whiteSpace value="collapse" /> </xsd:restriction> </xsd:simpleType> </xsd:schema> Futuros templates que se dediquen a generar código para un sistema basado en el lenguaje PHP, podrán extender este esquema con las propiedades que deseen para generar otro tipo de código, como por ejemplo la bloques de código dedicado a persistir las entidades contra una base de datos o a vistas web para administrar las altas, bajas y modificaciones de entidades. 140 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier FscodeGeneratorProjectType (Plugin para Netbeans IDE) Con el fin de tener una interfaz amigable para utilizar el framework de generación de código realizamos una extensión de un IDE existente y popular: Netbeans. El IDE Netbeans posee una arquitectura pensada para ser extendida mediante plugins utilizando una API que está organizada en varios módulos diferentes, cada uno implementando las distintas áreas que comprende el desarrollo de un IDE moderno. Así mismo el IDE es un proyecto derivado de una estructura aún mayor: Netbeans Platform, por lo que también se pueden desarrollar aplicaciones para múltiples propósitos utilizando toda la plataforma de Netbeans. En nuestro caso, para nuestra primera versión del plugin debíamos extender al mismo de forma tal de proveer soporte para las siguientes funciones: • Tipo de proyecto: se debe dar soporte para un nuevo tipo de proyecto, en nuestro caso un proyecto de generación de código. • Tipos de archivo: nuestro framework funciona con archivos de textos, entre los cuales tenemos archivos XML para definir el modelo (ya soportado por Netbeans mediante otros plugins) y el archivo fuente del lenguaje de generación: FGL, para el cuál tuvimos que definir la extensión *.fgl y dar soporte a la misma. • Soporte del lenguaje: en este caso el IDE puede extenderse para que el editor de nuestro lenguaje resalte el código fuente, realice chequeos de sintaxis preinterpretación, sugerencia de código, etc. En la primera versión solo tendrá soporte para resaltar el código y mostrar los errores de sintáxis o ejecución una vez que se invoca al intérprete. • Acciones del framework: además de poder editar los archivos fuentes y los XML del modelo, deben proveerse las acciones para crear/eliminar dichos archivos, invocar el intérprete para generar el código resultante, etc. Dichas funciones se incorporan a las acciones del IDE y se insertan en los iconos y elementos del menú de forma correspondiente. El resultado de extender el IDE genera dos tipos de entregables: 1. Distribución completa (un IDE nuevo) formado por el IDE Netbeans base y el soporte para nuestro framework. 2. Un plugin capaz de ser instalado en una instancia de Netbeans existente. La gran ventaja de este enfoque es que puede ser incorporado al IDE que el desarrollador utiliza a diario (con todos los plugins y soporte de lenguajes con los cuales trabaja) y utilizar el framework de generación de código en el mismo entorno, teniendo la posibilidad de generar el código resultante en los directorios del proyecto en el que está trabajando. 141 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Uso del parser Existen dos formas de utilizar el parser: desde línea de comandos, con la posibilidad de pasar parámetros de configuración, o como librería para el lenguaje java. Este último enfoque es el utilizado por el plugin desarrollado para la extensión del IDE Netbeans. Veamos en detalle ambas posibilidades: Uso del parser por línea de comandos Para poder utilizar el parser por línea de comandos se ha creado un sub-proyecto denominado Code Generator Standalone. Este proyecto incluye una instancia del generador de código empaquetada como Jar, las librerías del lenguaje FGL, algunos wrappers de Java básicos para el desarrollo de templates y una librería java para la ejecución por línea de comandos. Para la invocación se utiliza el Jar CodeGenerator-*.jar, por ejemplo: $ java -jar CodeGenerator-1.0-SNAPSHOT.jar Pasando el parámetro --help nos muestra la ayuda completa: $ java -jar CodeGenerator-1.0-SNAPSHOT.jar --help usage: CodeGenerator.jar [-base <directory>] [-C <key=value>] [-ConfigPath <directory>] [-e] [-FsLibPath <directory>] [-help] [-InputPath <directory>] [-TargetPath <directory>] [-TemplateMainFile <name>] [-TemplatesPath <directory>] -base <directory> A directory to be used as base directory, default values are based on this option. If you don't use this option, all other configuration options (FsLibPath, ConfigPath, TemplatesPath, InputPath, TargetPath) are required. Default value is the current directory. -C <key=value> Set other configuration using freelancesoft.codegenerator.common.configura tion.set method. -ConfigPath <directory> Path in wich the parser should search for configuration files. Default value is "<base>/config" -e Pring excpetion stack trace when an error produced by any exception occrs. -FsLibPath <directory> Path in wich the parser should search for Freelance Soft library classes. Default value is "<base>/lib" -help Print this message. -InputPath <directory> Path in wich the parser should search for input files. Default value is "<base>/input" -TargetPath <directory> Path in wich the parser should put generated files. Default value is "<base>/target" -TemplateMainFile <name> Default name to be used as main class for templates. Default value is "main.fgl" -TemplatesPath <directory> Path in wich the parser should search for template files. Default value is "<base>/tpl" Con el parámetro -base podemos indicarle el directorio donde se encuentra todo el escenario de trabajo, sin tener que indicar el input, target, etc. Tal directorio debe contar con los siguientes subdirectorios: 142 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier • config: Aquí deben estar los archivos de configuración del generador de código, tal directorio puede estar vacío, en ese caso se utiliza la configuración por defecto. • input: Se encuentra la entrada del generador (archivos *.xml), por ejemplo la representación de las entidades de negocio de la aplicación. • lib: Incluye las librerías para el lenguaje FGL y los wrappers de Java. • tpl: Aquí debe estar todo el código FGL de la plantilla utilizada. • target: Este es el directorio por defecto en donde se generará la salida del parser, correspondiente a la entrada incluida en input y la plantilla del directorio tpl. Árbol de directorios del ejemplo incluido php-entities: Ex1/ ├── config ├── input │ └── university.xml ├── lib │ ├── fs │ │ └── templating │ │ ├── Configurable.fgl │ │ └── Template.fgl │ ├── FSLibAutoloader.fgl │ └── java │ └── wrap │ ├── java │ │ ├── io │ │ │ └── File.fgl │ │ ├── lang │ │ │ ├── Boolean.fgl │ │ │ ├── Float.fgl │ │ │ ├── Integer.fgl │ │ │ └── String.fgl │ │ └── net │ │ ├── URI.fgl │ │ └── URL.fgl │ ├── JavaArrayConverter.fgl │ ├── JavaConverter.fgl │ ├── JavaWrapper.fgl │ ├── javax │ │ └── xml │ │ ├── parsers │ │ │ ├── DocumentBuilderFactory.fgl │ │ │ └── DocumentBuilder.fgl │ │ └── XMLConstants.fgl │ └── org │ └── w3c │ └── dom │ ├── Attr.fgl │ ├── CDATASection.fgl │ ├── CharacterData.fgl │ ├── Comment.fgl │ ├── Converters.fgl │ ├── Document.fgl │ ├── DocumentFragment.fgl │ ├── DocumentType.fgl │ ├── DOMConfiguration.fgl │ ├── Element.fgl │ ├── Node.fgl │ ├── Text.fgl │ └── TypeInfo.fgl ├── target │ └── classes │ └── university │ ├── Person.php 143 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier │ ├── Student.php │ └── Teacher.php └── tpl ├── main.fgl └── php-entities ├── classes │ ├── Accessor.fgl │ ├── AccessorType.fgl │ ├── Attribute.fgl │ ├── Autoloader.fgl │ ├── Constructor.fgl │ ├── Entity.fgl │ └── Mapper.fgl ├── main.fgl └── tpls ├── attribute.fgl ├── class.fgl ├── constructor.fgl ├── getter.fgl └── setter.fgl Uso del parser como librería Para utilizar la librería desde el lenguaje java lo primero que hay que hacer es crear una instancia de LanguageParser. // Dirección absoluta del archivo principal sobre el cual se ejecutará el parser, // Si utilizamos los templates desarrollados, apuntará a la locación del archivo main.fgl // de TemplateManager testFile = ""; // Creación de la instancia del parser LanguageParser parser = new CodeGeneratorParser(new java.io.FileInputStream(testFile), testFile); Antes de cada ejecución, se recomienda ejecutar el método reInit con el cual se reinicializan todos los contextos y configuraciones de ejecuciones anteriores. // Reinicializar contextos y configuraciones parser.reInit(); Luego se deben asignar todos los valores de configuración. // Dirección absoluta donde el parser buscará archivos de librería ConfigurationManager.current().setFsLibPath(getFSLibPath()); // Dirección absoluta donde el parser buscará archivos de entrada ConfigurationManager.current().setInputPath(scenario.getInputPath()); // Dirección absoluta donde serán generados los archivos ConfigurationManager.current().setTargetPath(scenario.getTargetPath()); // Dirección abosoluta donde se buscarán archivos de configuración para los templates ConfigurationManager.current().setConfigPath(scenario.getConfigPath()); // Dirección absoluta donde se buscarán los templates ConfigurationManager.current().setTemplatesPath(scenario.getTplPath()); Una vez configurado, debemos disparar la ejecución del parser. try { // Parsea el primer archivo formando el árbol que luego debe interpretarse parser.parseUnit(); // Interpreta el árbol generado con la sentencia anterior parser.interpret(); 144 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier } catch(Throwable ex) { // Hacer algo con el error. // Si el Logger está configurado para disparar excepciones ante errores irrecuperables, // aquí se puede inspeccionar para informar de los errores. } Una vez finalizada la ejecución se pueden inspeccionar los logs de error. // Warnings producidos durante la ejecución Set<Record> warnings = LogManager.current().getRecords(Record.Type.WARNING); // Errores producidos durante la ejecución Set<Record> errors = LogManager.current().getRecords(Record.Type.ERROR); // Errores irrecuperables producidos durante la ejecución, // el Logger puede estar configurado para disparar excepciones ante errores de este tipo Set<Record> irrecuperableErrors = LogManager.current().getRecords(Record.Type.IRRECUPERABLE_ERROR); Plan de configuración Configuración del servidor A continuación se describen los pasos necesarios para configurar el servidor de desarrollo. Las pruebas se han hecho localmente en Ubuntu 10.04, y en CentOs en lo que respecta a un servidor contratado. Se adjunta con la entrega una carpeta con todo el software necesario para lograr configurar el servidor. Host Este paso debe ser aplicado únicamente en el caso de que se desee configurar todo el entorno de manera local. 1. Ejecutar. $ sudo nano /etc/hosts 2. Agregar la siguiente línea. 127.0.0.1 devel.freelance-soft.com 3. Verificar que el mapeo se hizo correctamente. ping devel.freelance-soft.com Tomcat Tomcat es una implementación de las tecnologías Java Servlet y JavaServer Pages. Es utilizado como contenedor para varias de la aplicaciones que se utilizan durante el proceso de desarrollo. A continuación describiremos como instalarlo en el servidor. 145 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier 1. Instalar tomcat. $ sudo apt-get install tomcat6* 2. Agregar usuarios para administrador y manager. Primero ejecutar. $ sudo chgrp -R tomcat6 /etc/tomcat6 $ sudo chmod -R g+w /etc/tomcat6 $ sudo gedit /etc/tomcat6/tomcat-users.xml Y agregar las siguientes líneas. < role rolename = "admin" /> < role rolename = "manager" /> < user username = "admin" password = "admin123" roles = "admin,manager" /> 4. El generador de código utiliza esquemas xml (archivos xsd) para definir el formato de los archivos de entrada. A su vez, el plugin checkstyle utiliza un archivo de configuración en el que se definen las políticas de estilo de programación que se quieran chequear. Estos archivos deben estar todos en línea para poder ser utilizados en la generación o chequeo. Para contar con un directorio en el servidor donde se puedan publicar los archivos debemos ejecutar: $ $ $ $ mkdir chgrp chmod chmod /var/www/html/xml -R apacche /var/www/html/xml/ -R u=rwx,g=rwx,o=r /var/www/html/xml/ -R g+s /var/www/html/xml/ Los archivos en general se suben por ftp, así que se debe dar permisos a los usuarios que deben subir estos archivos. Por ejemplo, para el usuario user se debería ejecutar. $ sudo usermod -a -G apache user Java de Sun 1. Para instalar Java se debe ejecutar. $ sudo add-apt-repository ppa:ferramroberto/java $ sudo apt-get update $ sudo apt-get install sun-java6-jdk sun-java6-jre sun-java6-plugin sun-java6-fonts 2. Actualizar para que las alternativas no usen la versión libre, el archivo se adjunta con la entrega $ sudo /home/tesis/Desktop/EntornoCodeGenerator/SunJava/updatealternatives.sh CVS La herramienta que se utilizado como repositorio y control de versiones para el código es CVS, a continuación se describen los pasos para instalarlo en el servidor. Se toma como base el sistema operativo Ubuntu y se marcan las diferencias con el sistema operativo CentOS en rojo. 1. Para instalar cvs ejecutar. 146 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier $ sudo apt-get install cvs 2. Luego es necesario instalar el demonio, para que esté constantemente “escuchando” por solicitudes, para lo cual hay que ejecutar. $ sudo apt-get install xinetd $ sudo gedit /etc/xinetd.d/cvspserver Y agregar. service cvspserver { port = 2401 socket_type = stream protocol = tcp user = root wait = no type = UNLISTED server = /usr/bin/cvs server_args = -f --allow-root /var/lib/cvs pserver disable = no } Después reiniciar el servicio xinetd. $ sudo /etc/init.d/xinetd restart 3. Para ver si el servicio está corriendo correctamente se puede ejecutar. $ sudo netstat -tap | grep cvs 4. Hay que crear un usuario administrador y darle todos los permisos necesarios sobre el directorio cvsroot. $ sudo adduser -system cvsadmin $ sudo adduser -system cvsadmin $ sudo passwd cvsadmin cvsadmin123 $ sudo addgroup cvs $ groupadd cvs $ sudo usermod -g cvs cvsadmin $ sudo mkdir /var/lib/cvs $ sudo mkdir /var/lib/cvs/CVSROOT $ sudo chown -R cvsadmin:cvs /var/lib/cvs $ sudo chmod -R u=rwx,g=rwx,o= /var/lib/cvs Así definido el grupo de un archivo sobre el cual se hace commit será el grupo principal del usuario que ejecutó dicho commit. Para mantener el grupo cvs se debe ejecutar el siguiente comando. $ sudo chmod -R g+s /var/lib/cvs/ 5. Cada vez que se sume un desarrollador, se le deben dar los permisos para que pueda modificar el código. Para ello, asumiendo que el nombre de usuario es user, se debe ejecutar. $ sudo usermod -a -G cvs martin 6. Hay que generar .cvspass en /home/<usuario>/.cvspass. Asumiendo que el usuario 147 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier es cvsadmin y el pass cvsadmin123. $ cvs -d :pserver:cvsadmin:cvsadmin123@localhost:2401/var/lib/cvs login $ cp /home/tesis/.cvspass /home/tesis/.cvspasstomcat $ sudo chown tomcat6 /home/tesis/.cvspasstomcat $ su tomcat $ cvs -d :pserver:cvsadmin:MacuServerFS321@localhost:2401/var/lib/cvs login En este caso, se genera en /usr/share/tomcat6/ por lo que hay que copiarlo desde allí. 7. Añadir los proyectos desde el IDE preferido o línea de comandos. Nota: El siguiente comando puede ser útil si necesitamos desvincular a CVS archivos para subirlos a un nuevo server. $ find . -name CVS -print0 | xargs -0 rm -rf Archiva Archiva es el software seleccionado como repositorio maven. A continuación se describen los pasos para instalar este software en el servidor. Primero, aclaremos que tomamos como base el sistema operativo Ubuntu. Las principales diferencias con el sistema CentOS son: • Es conveniente crear symlink en /var/lib/tomcat6 a conf. De este modo se minimizan las diferencias. • La variable ${catalina.base} está ubicada en /usr/share/tomcat6/, ahí debe ser creada la carpeta carpeta archiva y no en /var/lib/tomcat6/archiva. 1. Descargar la última versión disponible del archivo war desde el sitio oficial de apache: http://archiva.apache.org/ 2. Si se desea tener la posibilidad de subir archivos de gran tamaño, se debe cambiar el tamaño máximo de archivo en el war /WEB-INF/classes/struts.properties antes de continuar. 3. Para instalar archiva. $ sudo mkdir /var/lib/tomcat6/archiva $ sudo cp /home/tesis/Desktop/EntornoCodeGenerator/Archiva/apache-archiva-1.3.5.war /var/lib/tomcat6/archiva/ $ sudo mkdir /var/lib/tomcat6/archiva/database $ sudo mkdir /var/lib/tomcat6/archiva/repositories $ sudo mkdir /var/lib/tomcat6/archiva/repositories/internal $ sudo cp -R /home/tesis/Desktop/EntornoCodeGenerator/Archiva/internal/* /var/lib/tomcat6/archiva/repositories/internal/ $ sudo chown -R tomcat6:tomcat6 /var/lib/tomcat6/archiva/database/ /var/lib/tomcat6/archiva/repositories/ $ sudo cp -R /home/tesis/Desktop/EntornoCodeGenerator/Archiva/lib/* /usr/share/tomcat6/lib $ sudo gedit /var/lib/tomcat6/conf/Catalina/localhost/archiva.xml Y agregar el siguiente contenido. < Context path = " /archiva" docBase="${catalina.base}/archiva/apache-archiva 1.3.5.war"> 148 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier < Resource name = "jdbc/users" auth = "Container" type = "javax.sql.DataSource" username = "sa" password = "" driverClassName = "org.apache.derby.jdbc.EmbeddedDriver" url = "jdbc:derby:${catalina.base}/archiva/database/users;create=true" /> < Resource name = "jdbc/archiva" auth = "Container" type = "javax.sql.DataSource" username = "sa" password = "" driverClassName = "org.apache.derby.jdbc.EmbeddedDriver" url = "jdbc:derby:${catalina.base}/archiva/database/archiva;create=true" /> < Resource name = "mail/Session" auth = "Container" type = "javax.mail.Session" mail.smtp.host = "localhost" /> </ Context > Luego ejecutar. $ sudo gedit /var/lib/tomcat6/conf/catalina.properties Y agregar. # Archiva appserver.home=${catalina.home} appserver.base=${catalina.base} Para que tome los cambios se debe reiniciar el servicio. 4. Una vez instalado archiva, ingresaremos al sitio y crearemos un usuario administrador. 5. Agregar el repositorio para extensiones NetBeans. 149 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier 6. Y luego el conector para el repositorio. 7. En la solapa de repositorios actualizar a. /var/lib/tomcat6/archiva/repositories/internal 150 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier /var/lib/tomcat6/archiva/repositories/snapshots y reescanear los repositorios, para que tome las librerías que copiamos. 8. Actualizar las bases de datos para que tomen las librerías copiadas en el repositorio internal. Para seguridad archiva usa Redback, para evitar que los passwords expiren ejecutar. nano /usr/share/tomcat6/.m2/security.properties Y agregar la siguiente línea. security.policy.password.expiration.enabled=false Para mayor información sobre la configuración se seguridad referirse al archivo default de propiedades de Redback: http://svn.codehaus.org/redback/redback/trunk/redbackconfiguration/src/main/resources/org/codehaus/plexus/redback/config-defaults.properties 151 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Maven A continuación, se describe como instalar la herramienta Maven en el servidor. Se toma como base el sistema operativo Ubuntu y se marcan en rojo las diferencias con el sistema operativo CentOS. 1. Instalar Maven. $ sudo apt-get install maven2 En CentOS es un poco más complejo, ya que no se cuenta con la versión requerida de maven en los repositorios. Por lo que debemos ejecutar. wget http://apache.xmundo.com.ar//maven/binaries/apache-maven-2.2.1-bin.tar.gz tar -xzvf apache-maven-2.2.1-bin.tar.gz mv apache-maven-2.2.1 /usr/local/apache-maven-2.2.1 cd /usr/local/ ln -s apache-maven-2.2.1/ maven nano /etc/bashrc Y agregar las siguientes líneas. MAVEN_HOME=/usr/local/maven export MAVEN_HOME PATH=$PATH:$MAVEN_HOME/bin JAVA_HOME=/usr/java/default export JAVA_HOME 2. Luego debemos crear un repositorio local, para lo cual debemos ejecutar. $ sudo mkdir /usr/share/tomcat6/.m2 $ sudo chown -R tomcat6:tomcat6 /usr/share/tomcat6/.m2/ $ sudo chmod -R u=rwx,g=rwx,o= /usr/share/tomcat6/.m2/ 3. Y tenemos que configurar maven para que utilice únicamente a archiva como repositorio. De lo contrario irá a buscar paquetes a los repositorios oficiales. De este modo mantenemos en el server una caché con todos los paquetes que usemos. $ sudo gedit /etc/maven2/settings.xml $ nano /usr/local/maven/conf/settings.xml Agregar como repositorio local. < localRepository >/usr/share/tomcat6/.m2</ localRepository > Agregar como mirrors. < mirror > < id > archiva.default </ id > < url > http://localhost:8080/archiva/repository/internal/ </ url > < mirrorOf > * </ mirrorOf > </ mirror > < mirror > < id > archiva.apache.snapshots </ id > < url > http://localhost :8080/archiva/repository/snapshots/ </ url > < mirrorOf > apache.snapshots </ mirrorOf > </ mirror > 152 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier Suponiendo que el usuario administrador será admin y su pass admin123. Agregar los servers. < server > < id > internal </ id > < username > admin </ username > < password > admin123 </ password > </ server > < server > < id > snapshots </ id > < username > admin </ username > < password > admin123 </ password > </ server > 4. En entorno empresarial, no deseamos que cualquier persona pueda ver y descargar nuestros paquetes. Para esto, debemos quitarle los permisos de lectura al usuario guest. Si se hace esto, debemos indicarle a maven con qué usuario autenticarse con archiva agregando las siguientes líneas. < server > < id > archiva.default </ id > < username > admin </ username > < password > ******* </ password > </ server > < server > < id > archiva.apache.snapshots </ id > < username > admin </ username > < password > MacuServerFS321 </ password > </ server > Jenkins (o Hudson) Esta herramienta la hemos probado ampliamente de manera local. De momento está desactivada en el servidor ya que requiere de una cantidad de memoria considerable para ejecutar builds. Los pasos para instalarla son los siguientes. 1. Descargar el archivo de tipo war desde el sitio oficial http://jenkins-ci.org/. 2. Renombrarlo a jenkins.war. 3. Desplegarlo en http://devel.freelance-soft.com:8080/manager/html. (copiarlo en /var/lib/tomcat6/webapps/ y reiniciar tomcat es quivalente). 153 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier 4. Agregar usuario para jenkins, para ello hay que ejecutar: $ sudo chgrp -R tomcat6 /usr/share/tomcat6 $ sudo chmod -R g+w /usr/share/tomcat6/ $ sudo gedit /etc/tomcat6/tomcat-users.xml Y agregar: < user username = "hudson-admin" password = "secret" roles = "admin" /> Luego reiniciar tomcat: $ sudo service tomcat6 restart 5. Ingresar a http://devel.freelance-soft.com:8080/jenkins. Con esto se crea e inicializa automáticamente el sitio. 6. Para un entorno en un servidor público sería conveniente activar seguridad. De momento este paso no se describirá porque no tenemos la posibilidad de utilizarlo de este modo actualmente. 7. Añadir maven a jenkins: 8. Añadir CVS a jenkins: 154 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier 9. Configurar Java en jenkins: 10. Agregar los jobs, Asumiendo que tenemos los jobs definidos copiados en /home/tesis/Desktop/EntornoCodeGenerator/hudson/, debemos ejecutar: $ sudo cp -R /home/tesis/Desktop/EntornoCodeGenerator/hudson/jobs/* /usr/share/tomcat6/.jenkins/jobs/ $ sudo chown -R tomcat6:tomcat6 /usr/share/tomcat6/.jenkins/jobs/ $ sudo chmod -R g+w /usr/share/tomcat6/.jenkins/jobs/ $ sudo service tomcat6 restart 11. Una vez instalado y configurado jenkins, deberíamos correr el build nocturno para asegurarnos de que todos los pasos se han ejecutado correctamente. Configuración del cliente A continuación se describen los pasos necesarios para configurar el cliente de desarrollo, es decir una máquina para que un desarrollador pueda trabajar en el proyecto. Las pruebas se han hecho localmente en Ubuntu 10.04 y en Windows XP. Java de Sun Se deben seguir los mismos pasos que la sección para la configuración del servidor. JavaCC 1. Para instalar JavaCC en Ubuntu simplemente debemos ejecutar: $ sudo apt-get install javacc Maven 1. Para instalar maven, seguir las instrucciones de la instalación en el servidor. La 155 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier única diferencia es que debemos apuntar los repositorios de archiva a devel.freelance-soft.com. 2. Si el desarrollador debe contar con la posibilidad de correr los builds que suben esquemas o archivo de configuración para chequeo de estilos, entonces debe ejecutar: $ sudo gedit /etc/maven2/settings.xml Y, suponiendo que el nombre de usuario es user, agregar el siguiente contenido: < profile > < id > fs-default </ id > < activation > < activeByDefault > true </ activeByDefault > </ activation > < properties > < f tp.xml.user > user </ ftp.xml.user > < ftp.xml.pass > ***** </ ftp.xml.pass > </ properties > </ profile > Por supuesto, este usuario debe contar con los permisos necesarios para subir archivos por ftp al directorio. NetBeans Como IDE hemos seleccionado NetBeans. La instalación es muy sencilla, basándonos en una distro Ubuntu: 1. Descargar el instalador para linux desde http://netbeans.org/downloads/index.html. 2. Marcar el archivo como ejectuable. 3. Ejecutar el archivo. 4. Simplemente seguir las instrucciones. 5. Para lograr que el plugin de maven busque las dependencias desde nuestro servidor hay que configurarlo. Lo primero es configurar el repositorio internal apuntando a http://devel.freelance-soft.com:8080/archiva/repository/internal. 156 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier 6. Repetir el paso 5 para el repositorio de snapshots, que es donde se publicarán las librerías en desarrollo. 7. Estos últimos dos pasos son sólo para buscar dependencias, maven terminará usando archiva como mirror por configuración. 8. Para que utilice nuestra instalación de maven en lugar de la que viene incorporada con NetBeans debe ser configurado. Configuración de un cliente windows A continuación, explicaremos la configuración de un cliente windows, sólo hasta llegar a contar con las herramientas necesarias para llegar a ejecutar maven sobre todos los proyectos. Se asume que no hay grandes diferencias para el resto de las herramientas. Para algunas incluso es más sencillo y es suficiente con ejecutar un wizard de instalación, se remite a las páginas de descarga de las herramientas para mayor detalle. Para la descripción de los pasos se asume el sistema operativo Windows XP de 32 bits. En muchos caso es necesario agregar o modificar variables de entorno, para esto se debe: 1. Ir a las propiedades del sistema. 2. Hacer click sobre la pestaña “Opciones avanzadas”. 3. Hacer click sobre el botón “Variables de entorno”. 157 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier CVS Para instalar CVS en windows seguir los siguientes pasos: 1. Descargar cvs desde http://ftp.gnu.org/non-gnu/cvs/binary/stable/x86-woe/ . 2. Descomprimir en C:\Archivos de programa\CVS. 3. Agregar la ruta a la variable de entorno Path. 4. Luego debemos descargar los proyectos, para lo cual debemos ejecutar. cd c:\ mkdir CodeGeneratorProjects cd CodeGeneratorProjects cvs -d :pserver:{user}:{pass}@devel.freelance-soft.com:2401/var/lib/cvs/ login cvs -d :pserver:@devel.freelance-soft.com:2401/var/lib/cvs/ checkout CodeGeneratorPoms cvs -d :pserver:@devel.freelance-soft.com:2401/var/lib/cvs/ checkout CodeGeneratorModules cvs -d :pserver:@devel.freelance-soft.com:2401/var/lib/cvs/ checkout CodeGeneratorLibrary cvs -d :pserver:@devel.freelance-soft.com:2401/var/lib/cvs/ checkout CodeGeneratorTemplates cvs -d :pserver:@devel.freelance-soft.com:2401/var/lib/cvs/ checkout fs-schemas Java de Sun 1. Descargar la jdk de http://www.oracle.com/technetwork/java/javase/downloads/. 2. Instalar según instrucciones. 158 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier 3. Agregar las siguientes variables de entorno. JAVA_HOME = C:\Archivos de programa\Java\jdk1.7.0_01 Maven 1. Descargar desde http://www.apache.org/dyn/closer.cgi/maven/binaries/apachemaven-2.2.1-bin.zip. 2. Descomprimir el archivo en C:\Archivos de programa\Apache Software Foundation. 3. Agregar las siguientes variables de entorno. M2_HOME = C:\Archivos de programa\Apache Software Foundation\apache-maven-2.2.1 M2 = %M2_HOME%\bin Path += ;%M2% 4. Configurar el archivo C:\Archivos de programa\Apache Software Foundation\apache-maven-2.2.1\conf\settings.xml según la sección maven de este documento para clientes linux. 5. Probar ejecutando. mvn –version PHP PHP es utilizado como lenguaje de scripting para builds locales. Para instalarlo simplemente seguir los siguientes pasos: 1. Descargar de http://windows.php.net/download/ . 2. Seguir instrucciones. Una vez instaladas las herramientas, podemos ejecutar un build completo local: cd c:\ cd CodeGeneratorProjects\CodeGeneratorPoms php deployall.php Compilar en windows desde linux Para asegurar que nuestro software es cross platform, es decir que funciona tanto en windows como en linux, debemos ser capaces de correr los builds en ambas plataformas. Reiniciar el ordenador para cambiar de sistema operativo sería demasiado tedioso. Así que es de gran utilidad utilizar una máquina virtual para compilar en diferentes entornos. Hemos evaluado VMWare y VirtualBox para dar soporte a una máquina windows en la cual corran los builds. Seleccionamos VirtualBox por ser totalmente gratuito y de fácil instalación. A continuación describimos los pasos necesarios para crear una máquina con windows xp, capaz de correr los builds utilizando el comando virtualboxdeployall que se encuentra en el proyecto CodeGeneratorPoms. 159 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier 1. Para instalar VirtualBox ejecutar. $ deb http://download.virtualbox.org/virtualbox/debian lucid contrib non-free $ sudo apt-key add oracle_vbox.asc $ wget -q http://download.virtualbox.org/virtualbox/debian/oracle_vbox.asc -O- | sudo apt-key add $ sudo apt-get update $ sudo apt-get install virtualbox-4.1 2. Abrir la aplicación de gestión de máquinas virtuales en Aplicaciones → Herramientas de Sistema → Oracle VM VirtualBox. 3. Crear una máquina virtual con el nombre TesisXP. La configuración default para el sistema operativo Windows XP es suficiente. 4. Instalar el sistema operativo Windows XP en la máquina virtual. 5. Sobre la máquina virtual instalar algún servidor SSH. Hemos probado con OpenSSH sin problemas, pero cualquier server estaría bien. 6. Agregar excepción sobre el puerto 22 en el firewall de windows para poder conectarnos por consola. 160 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier 7. Seguir los pasos de la sección “Configuración de un cliente windows”, de este modo contaremos con todo el software necesario para poder ejecutar builds sobre nuestra máquina virtual. 8. Los pasos que siguen deben ejecutarse sobre la máquina host, es decir la máquina con linux. Primero instalaremos sshpass, este es usado por vboxdeployall para lograr que no se haga prompt del password cada vez que nos conectamos por ssh. Para eso ejecutar. $ sudo apt-get install sshpass 9. Hacer forward de los puertos, con esto al conectarnos por ssh al puerto 2222 si la máquina virtual está encendida las peticiones serán redireccionadas a la máquina con Windows al puerto 22. $ VBoxManage modifyvm "TesisXP" --natpf1 "guestssh,tcp,,2222,,22" Con esto, ya podemos ejecutar builds en el sistema operativo Windows XP sin necesidad 161 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier de contar con otra máquina física o reiniciar el ordenador. Utilizando el Framework de Generación de Código Ya hemos visto como utilizar el parser generado, ya sea como una librería para otra aplicación o directamente mediante la línea de comandos. Para brindar más soporte al desarrollador se ha extendido el IDE Netbeans mediante el proyecto FscodeGeneratorProjectType (descripto anteriormente en Proyectos Desarrollados), esto facilita la utilización del framework y aporta nuevas posibilidades para extenderlo en el futuro, por ejemplo para poder importar el modelo desde Bases de Datos. La primera versión estable de dicho plugin está aún en desarrollo, la siguiente es una screenshot del IDE Netbeans con un proyecto abierto: Por último hemos desarrollado un paquete para dar soporte del lenguaje FGL al editor de texto del proyecto Gnome: Gedit. Que si bien está desarrollado para sistemas Linux/Gnome es posible ejecutarlo en otros entornos de escritorio de Linux y además 162 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier cuenta con una versión para sistemas Windows por lo que podría utilizarse en el futuro también en ese ambiente de desarrollo. El paquete incluye el siguiente soporte para sistemas Linux/Gnome: • Versión del generador de código para ejecutar desde consola. • Tipo mime *.fgl para gnome, asociando estos archivos a la aplicación Gedit. • Soporte para el editor de texto Gedit: • Resaltado del código FGL. • Plantilla de colores (theme): FGL Style • Instalación de herramienta externa para invocar al parser del generador de código, para utilizarla debe activar el plugin "External Tools" en Gedit. Screenshot de la ejecución de Gedit 3 con un archivo fuente FGL y la ejecución del parser: Contenido del entregable Esta documentación se acompaña de un DVD que contiene: • Todo el código fuente de los proyectos implementados. • Una instancia binaria del generador de código para ser utilizada por consola. 163 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier • Un paquete para Linux/Gnome para instalar el tipo mime de los archivos *.fgl y dar soporte del lenguaje al editor de texto Gedit 3. • Ejemplos de plantillas implementadas: php-entities, mysql5, web-backend. • Plugin de netbeans: FScodeGeneratorProjectType-1.0-SNAPSHOT.nbm Map road Pensamos continuar con el desarrollo de los proyectos que aquí se describen. En esta entrega hemos acotado el desarrollo hasta donde menciona la documentación, pero tenemos pensado agregar aspectos como los siguientes: • Agregar configuración a los archivos pom para que el sitio maven generado sea más rico, es decir que contenga información sobre desarrolladores, links a repositorio de código fuente, etc. • Recuperación de errores en el lenguaje más robusta. El archivo de la gramática del lenguaje puede ser modificado de modo que JavaCC genere un parser con la posibilidad de recuperarse de errores y de ese modo, continuar ante un error y luego informar sobre todos los errores producidos. • Agregar más librerías al proyecto CodeGeneratorLibrary. Algunas de las librerías que pensamos podrían ser de gran utilidad son: ◦ Una librería que defina convenciones de nombres, que sea configurable y que utilicen los templates para nombres de atributos, métodos, etc. ◦ Un wrapper de la clase Xpath de Java, facilitaría el procesamiento de los archivos xml de entrada. ◦ Etc. • También queremos agregar más templates al proyecto CodeGeneratorTemplates. Algunos podrían ser. ◦ Todos los necesarios para lograr un sitio completo basado en el lenguaje PHP. Desde la persistencia hasta la administración (alta, baja y modificaciones) de entidades mediante una interfaz web. ◦ Un template para generar entidades para el lenguaje C#. ◦ Un template para generar entidades para el lenguaje Java. • Ampliar las funciones de primer y segundo orden que pueden aplicarse sobre una variable de tipo arreglo del lenguaje. • Una vez logrados los templates necesarios para generar un sitio completo basado en el lenguaje PHP, deseamos agregar tests de integración. Por ejemplo se puede ejecutar el parser con todos estos templates y utilizar la herramienta Selenium para abrir una instancia de un browser y verificar el correcto funcionamiento de la 164 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier aplicación generada en su totalidad. • Posibilidad de ejecutar builds maven en entornos windows usando plugin de jenkins y virtualbox. De este modo, en el build nocturno podríamos asegurar que todos nuestros proyectos corren tanto en entornos windows como linux. • Configurar más plugins de reportes para lograr sitios de proyecto más interesantes generados con maven. • Extender TemplateManager para lograr comunicación entre diferentes templates registrados. Seguramente se dará el caso de que un template genera diferente código dependiendo de si se está ejecutando otro o no. • Etc. Conclusiones Luego de haber trabajado durante mucho tiempo en el desarrollo de este Framework hemos observado la gran utilidad que provee la generación de código en diversas etapas del proceso de desarrollo de un producto de software, no solo en la optimización del tiempo sino también en la seguridad de un código libre de errores una vez que se han testeado y corregido las plantillas utilizadas. Si bien la idea principal de este trabajo está enfocado en la generación de código para lenguajes de programación, cabe destacar que puede ser utilizado para generar otro tipo de salida como puede ser la documentación técnica en diversos formatos: html, pdf, odt, etc. o cualquier otro uso donde exista la posibilidad de parametrizar la salida esperada. A la fecha no hemos encontrado trabajos similares, al menos en lo que se refiere a la genericidad y flexibilidad, que creemos, es el gran potencial de nuestro trabajo. Este Framework fue pensado para poder ser extendido y adaptado a los diversos usos y lenguajes de programación, sin restringir el tipo de salida generado. Por último creemos que la generación de código es cada vez más tenida en cuenta en entornos empresariales que ven la necesidad de optimizar sus costos y tiempos de desarrollo. Si bien muchos IDEs modernos ya cuentan con características de generación de código, el hecho de utilizar en todo el proceso la misma herramienta brinda consistencia e integración, además de brindar la capacidad de poder extenderse y adaptarse al modelo de trabajo de la empresa. Tenemos intenciones de continuar evolucionando este proyecto más allá de esta entrega, para más información dirigirse al sitio web: http://www.freelance-soft.com/codegenerator Referencias • Prueba unitaria: http://es.wikipedia.org/wiki/Prueba_unitaria • The Art of Unit Testing: http://artofunittesting.com/ (Web y libro) • Mock Object: http://en.wikipedia.org/wiki/Mock_object 165 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier • Continuous Integration: http://martinfowler.com/articles/continuousIntegration.html (Art. de Martin Fowler) http://en.wikipedia.org/wiki/Continuous_integration • NetBeans: http://netbeans.org/ • Eclipse: http://eclipse.org/ • JavaCC: http://javacc.java.net/ • ANTLR: http://antlr.org/ • CUP: http://www.cs.princeton.edu/~appel/modern/java/CUP/ • Jlex: http://www.cs.princeton.edu/~appel/modern/java/JLex/ • Jflex: http://jflex.de/ • Comparación entre generadores de parsers: http://en.wikipedia.org/wiki/Comparison_of_parser_generators • Generadores de parsers código abierto: http://java-source.net/open-source/parser-generators (Artículo) • MySQL: http://www.mysql.com/ • Hibernate: http://www.hibernate.org/ • JavaDoc-Tools: http://www.oracle.com/technetwork/java/javase/documentation/index-jsp135444.html • Junit: http://www.junit.org/home http://junit.sourceforge.net/ (Documentación y Getting started) • Junit y TestNG: http://www.ibm.com/developerworks/java/library/j-cq08296/ (Comparativa) • TestNG: http://testng.org/doc/index.html • Java Mock Frameworks Comparision: http://www.sizovpoint.com/2009/03/java-mock-frameworks-comparison.html • Jmock: http://www.jmock.org/ • EasyMock: http://easymock.org/ • Mockito: http://code.google.com/p/mockito • Comparison of revision control software: 166 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier http://en.wikipedia.org/wiki/Comparison_of_revision_control_software (Amplia comparación de diferentes sistemas de control de versiones, Wikipedia) • CVS: http://cvs.nongnu.org/ • Subversion: http://subversion.apache.org/ • Mercurial: http://mercurial.selenic.com/ • Git: http://git-scm.com/ • Maven: http://maven.apache.org/ http://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html http://maven.apache.org/plugins/index.html (Plugins soportados oficialmente) • Maven Plugins: Clean: http://maven.apache.org/plugins/maven-clean-plugin/ Properties: http://mojo.codehaus.org/properties-maven-plugin/plugin-info.html Surefire: http://maven.apache.org/plugins/maven-surefire-report-plugin/ Assembly: http://maven.apache.org/plugins/maven-assembly-plugin/ Dependency: http://maven.apache.org/plugins/maven-dependency-plugin/ CheckStyle: http://maven.apache.org/plugins/maven-checkstyle-plugin/ http://checkstyle.sourceforge.net/ Pmd: http://maven.apache.org/plugins/maven-pmd-plugin/ http://pmd.sourceforge.net/ FindBugs: http://mojo.codehaus.org/findbugs-maven-plugin/ http://findbugs.sourceforge.net/ Cobertura: http://mojo.codehaus.org/cobertura-maven-plugin/ http://cobertura.sourceforge.net/ Emma: http://mojo.codehaus.org/emma-maven-plugin/ http://emma.sourceforge.net/ AntRun: http://maven.apache.org/plugins/maven-antrun-plugin/ (Maven Plugin) Wagon ftp: http://maven.apache.org/wagon/wagon-providers/wagon-ftp / 167 Generación de código basado en modelos Bascal, Martín Pablo Hernández, Fernando Javier • Ant: http://ant.apache.org/ • Java Code Coverage: http://www.copperykeenclaws.com/notes-on-cobertura-vs-emma-vs-clover/ (Cobertura vs. Emma vs Clover) • Cobertura vs EMMA: http://niftybits.wordpress.com/2007/04/21/cobertura-vs-emma/ (Post comparativo) • Apache Tomcat: http://tomcat.apache.org/ • Glassfish: http://glassfish.java.net/ • Gassfish vs Tomcat: http://www.ninthavenue.com.au/blog/glassfish-vs-tomcat (Comparativa) • Archiva: http://archiva.apache.org/ • Artifactory: http://www.jfrog.com/products.php • Nexus: http://nexus.sonatype.org/ • Jenkins: http://jenkins-ci.org/ • Hudson: http://hudson-ci.org/ • Jenkins Vs Hudson: http://stackoverflow.com/questions/4973981/how-to-choosebetween-hudson-and-jenkins (Discusión sobre Jenkins y Hudson, se brinda una serie de links para comprender la separación) • Ubuntu: http://www.ubuntu.com/ http://help.ubuntu.com/ (Documentación) http://www.ubuntu.com/support/community/webforums (Foros) • CentOS: http://www.centos.org/ http://wiki.centos.org/ (Wiki) http://www.centos.org/modules/newbb/ (Foros) • VMWare: http://www.vmware.com/ • VirtualBox: http://www.virtualbox.org/ 168