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