Archive for the '12677' Category

Resoluciones del Nuevo Mes: Mayo 2012

Wednesday, May 2nd, 2012

Un nuevo mes comienza, y es tiempo de revisar mis resoluciones del mes pasado, y escribir la lista para este mes. Primero, el resultado de Abril:

– Completar Acquarella (comment, number detection, new styles, language extension..) parcial ver repo
– Trabajar en AjTalk en Javascript/NodeJs (class support, fileouts processing) completo ver repo ver demo en línea
– Trabajar en AjLogo in Javascript/NodeJs (canvas support) completo ver repo ver demo en línea
– Completar el soporte de verbos iniciales en SetTuples pendiente
– Dar una charla sobre Lenguajes de Programación (Javascript/NodeJs, Clojure, Erlang, Python, Ruby, Scala) completo ver video 1, video 2
– Modelo de AjContab en memoria pendiente

Además, hice:

– Dar una charla sobre Clojure completo
– Trabajar en DartSharp completo ver repo

Resoluciones para este nuevo mes de Mayo:

AjContab modelo en memoria
– Agregar verbos en SetTuples
– Trabajar en Acquarella (extender configuración por lenguaje de programación, soporte de comentarios multi línea…)
– Trabajar en AjLogo en Javascript
– Trabajar en AjTalk en Javascript
– Mejorar la compilación de AjTalk de Smalltalk a Javascript
– Dar una charla sobre implementación de lenguajes de programación en Javascript
– Comenzar con la codificación de AjConsorSite

Muchas cosas, pero gran diversión!

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

 

Objetos transaccionales en AjTalk, introducción rápida

Tuesday, November 16th, 2010

Quiero describir hoy un ejemplo corto, mostrando el manejo de objetos transaccionales en AjTalk, mi Smalltalk-like Virtual Machine interpretada escrita en C#.

¿Qué es un objeto transaccional, en este contexto? Es un objeto, que cuando ejecuta dentro de una transacción en memoria, puede manejar los valores de sus variables de instancia, monitoreando sus cambios. Si la transacción se confirma con commit, los nuevos valores del objeto comienzan a ser visibles para el resto del sistema. Si la transacción se rechaza con rollback, los objetos transaccionales afectados por la misma restauran sus valores originales, los valores que tenían al comienzo de la transacción. Nota: estas transacciones son transacciones en memoria, y no están relacionadas con transacciones en bases de datos.

¿Cómo crear un objeto AjTalk transaccional? Agregué un método al protocolo de Object:

Object methods!

asTransactional
    ^@AjTalk.Transactions.TransactionalObject new: self with: (@AjTalk.Machine current transactionManager)
! !

El objeto es “envuelto” por un decorador, un objeto nativo de .NET del tipo TransactionalObject que es el que hace el trabajo de vigilar los valores de las variables de instancias de objeto decorado. Este decorador recibe todas las peticiones de set y get de valores de instancia, y las maneja para la transacción actual, y para cualquier otra transacción que se esté ejecutando en el sistema, que acceda al objeto via el decorador. Pienso escribir un post describiendo la implementación interna. Nota: si una variable de instancia en un objeto transaccional es modificado por dos transacciones, la primera gana, y la segunda falla.

Dada una clase Rectangle:

Object subclass: #Rectangle instanceVariableNames: ‘width height’
!Rectangle methods!
width
    ^width
!
height
    ^height
!
width: newWidth
    width := newWidth
!
height: newHeight
    height := newHeight
! !

Podemos crear una instancia, poner algún valor, y crear un decorador transaccional:

rect := Rectangle new.
rect width: 500!
rect := rect asTransactional
!

Ahora, la variable rect apunta a una instancia que es transaccional. Pero no hay todavía transacción abierta: su conducta, entonces, es como cualquier otro objeto. ¿Cómo abrimos una transacción? La Machine actual tiene una nueva propiedad, que mantiene un objeto TransactionManager:

tm := @AjTalk.Machine current transactionManager.
console := @System.Console out
!

tm beginTransaction!

La instancia rect, que apunta al decorador transaccional, tiene una referencia al objeto decorador, llamado innerObject. Podemos ver sus valores:

console write: ‘rect width is ‘.
console writeLine: rect width!

console write: ‘inner rect width is ‘.
console writeLine: rect innerObject width!

La salida sería:

rect width is 500
inner rect width is 500

Ahora, cambiemos el ancho del objeto transaccional:

rect width: 700!

Si imprimimos de nuevo los valores, como antes, la salida sería:

rect width is 700
inner rect width is 500

Usualmente, se usa la instancia rect (el decorador) desde todos los procesos. Si uno imprime el valor de rect width en otro proceso/thread (que puede tener o no su propia transacción), obtendrá el valor 500. El nuevo valor 700 no está publicado todavía: es solamente visible desde la transacción actual.

Podemos hacer rollback de la transacción:

tm rollbackTransaction!

Los nuevos valores serán:

rect width is 500
inner rect width is 500

Si hubiéramos confirmado la transacción con commitTransacction, el resultado sería:

rect width is 700
inner rect width is 700

Pueden ejecutar un ejemplo, desde el código del trunk actual. Hay un comando RunTransaction.cmd en el proyecto AjTalk.Console.

Próximos pasos: escribir un post describiendo la implementación de estas características; mejorar el manejo transaccional para hacer retry automático de una transacción fallida.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

Web Crawler usando el nuevo AjAgents

Tuesday, November 9th, 2010

Hace poco escribí una nueva implementación de mi proyecto AjAgents, descripta en:

AjAgents: a new implementation
AjAgents: una nueva implementación

Ahora, quiero describir una aplicación de prueba, de nuevo el Web Crawler: visitar y bajar la página desde una URL inicial, ir descubriendo enlaces al mismo sitio, y procesar el resto de las páginas. Anteriores versiones:

Web Crawler Using Agents and AjSharp
Web Crawler usando AjSharp
Distributed Web Crawler using AjMessages
Web Crawler distribuido usando AjMessages

La nueva versión del web crawler usa la nueva interface:

    public interface IAgent<T>
    {
        void Post(Action<T> action);
    }

En vez de escribir un tipo que hereda o implementa un agente, éste es un “wrapper” aplicado a un tipo normal cualquiera de .NET.

La nueva implementación de AjAgentes, y el ejemplo de web crawler (Proyecto AjAgents.WebCrawler), puede ser bajado desde:

http://code.google.com/p/ajcodekatas/source/browse/#svn/trunk/AjAgents

En una de mis anteriores implementaciones tenía:

Ahora, descarté el Dispatcher. Tengo tres clases:

El Resolver recibe un enlace a bajar y procesar. Decide si lo procesa o no (puede que el enlace ya haya sido procesado, o que el link no corresponda al sitio de partida, o se alcanzó un límite en la profundidad de enlaces desde la página inicial de proceso). Si el enlace es aceptado, la inforación es enviada al Downloader. Este objeto baja el contenido de la página, y lo envía al Harvester (podría enviarlo a otros agentes, también). El Harvester examina el contenido, extrae nuevos enlaces para procesar, y a cada uno lo envía al Resolver.

Estas clases son tipos .NET. Pero las referencias que mantienen entre sí son del tipo Agent<Downloader>, Agent<Harvester>, Agent<Resolver>. El código que arma un grupo inicial de objetos/agentes interconectados está en Program.cs:

       static Agent<Resolver> MakeAgent()
        {
            Resolver resolver = new Resolver();
            Harvester harvester = new Harvester();
            Downloader downloader = new Downloader();
            Agent<Resolver> aresolver = new Agent<Resolver>(resolver);
            Agent<Harvester> aharvester = new Agent<Harvester>(harvester);
            Agent<Downloader> adownloader = new Agent<Downloader>(downloader);
            resolver.Downloader = adownloader;
            harvester.Resolver = aresolver;
            downloader.Harvester = aharvester;
            return aresolver;
        }

 

La aplicación de consola AjAgents.WebCrawler aceptar un parámetro, con el enlace inicial:

AjAgents.WebCrawler http://ajlopez.zoomblog.com

Su salida:

Este ejemplo es un “proof of concept”. Me gustaría mejorarlo:

Agent<IDownloader>, que haga balanceo de carga con varias instancias de Agent<Downloader>.

Agent<T> instanciado en diferentes máquinas, implementando así un web crawler distribuido.

Nos leemos!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

Objetos Distribuidos en AjTalk

Wednesday, November 3rd, 2010

He agregado objetos distributos a mi proyecto AjTalk (una virtual machine interpretada tipo Smalltalk, escrita en C#). Estoy usando .NET Remoting para serializar objetos de un proceso a otra, y tengo proxies a objetos remotos. Como siempre, pueden bajar el código fuente actual de:

http://code.google.com/p/ajtalk/

Primero, puse un nuevo método en objetos:

nil subclass: #Object
!Object methods!
asRemote
    ^@AjTalk.Hosting.RemoteObject new: self
! !

(Estoy usando la notación AjTalk para acceder a tipos y métodos .NET, ver AjTalk: accediendo a objetos y tipos .NET)

La clase RemoteObject es un decorador alrededor de IObject (interface implementada por los objetos de AjTalk), que hereda desde MarshalByRefObject. En .NET, esos objetos no son serializados: una referencia es enviada al otro proceso. Cada llamada a ese proxy, envía la invocación al objeto original, que todavía reside en su proceso, donde fue creado:

    public class RemoteObject : MarshalByRefObject, IObject
    {
        private IObject obj;
        private Machine machine;
        public RemoteObject(IObject obj)
        {
            this.obj = obj;
            this.machine = Machine.Current;
        }
    // ...    
    }

Hay un host, que puede recibir llamadas desde otros procesos. Podemos lanzarlo en el proceso A:

Object subclass: #Host nativeType: @AjTalk.Hosting.RemotingHostServer

!

host := Host new: @AjTalk.Machine !!current with: 20000 with: ‘Server2000’

!

Desde otro proceso, podemos conectarnos a ese host. Ejecutamos en el proceso B:

Object subclass: #RemoteHost nativeType: @AjTalk.Hosting.RemotingHostClient

!RemoteHost methods!

export: aClass

    self execute: (aClass !!toOutputString)

! !

remote := RemoteHost new: ‘localhost’ with: 20000 with: ‘Server2000’

!

Ahora, remote es un objeto en el proceso B que puede ser usado para comunicarse con el proceso remoto A.

El método export serializa en un string la definición de una clase. De esta manera, si tenemos una nueva clase en el proceso B, podemos enviar su definición al proceso A remoto. Una vez hecho esto, podemos crear objetos en el host remoto, que sean de esa clase. Veamos un ejemplo. Definimos una nueva clase en el proceso B:

Object subclass: #Rectangle instanceVariableNames: ‘width height’

!Rectangle methods!

width

    ^width

!

height

    ^height

!

width: newWidth

    width := newWidth

!

height: newHeight

    height := newHeight

! !

Entonces, podemos exportar la clase al proceso A:

remote export: Rectangle

!

Ahora, creamos un nuevo Rectangle, en el proceso A, ejecutando el siguiente código desde el proceso B:

remote execute: ‘rect := Rectangle new. rect width: 100. rect height: 20’

!

El método execute envía un string, que es ejecutado en la máquina remota. Podemos recibir objetos serializados. De nuevo, ejecutando en el proceso B:

rect := remote evaluate: ‘rect’.

rect width: 200.

rect height: 30

!

El método evaluate envía un string al proceso A, lo evalúa, y el resultado (en este caso, el objeto Rectangle) es serializado y reificado (en una copia) en el proceso B. Entonces, los cambios en su tamaño son evaluados sobre la copia en el proceso B. Pero tenemos otro camino: recuperar un proxy al objeto rect, que sigue residiendo en el proceso A:

rect2 := remote evaluate: ‘rect asRemote’.

rect2 width: 300.

rect2 height: 40

!

asRemote es la clave: en vez de retornar un objeto serializado, retorna una referencia al objeto que sigue residiendo en el proceso A. Los cambios en el tamaño de rect2 (en realidad, la invocación de métodos width: y height:) son enviados y ejecutados en el ambiente del objeto remoto.

En resumen, con estas clases y métodos, podemos:

– Crear un host, y conectarnos a él desde otro proceso/máquina.

– Enviar un string para ser ejecutado en el host remoto.

– Exportar una definición de clase local al host remoto

– Evaluar una expresión en el host remoto y recuperarla como resultado serializado

– Evaluar una expresión en el host remoto y recuperar su resultado como objeto remoto accesible via un proxy

Debería escribir un caso de uso con esta funcionalidad.

Nos leemos!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

Trabajando en AjTalk: una Virtual Machine tipo Smalltalk, escrita en C#

Monday, October 25th, 2010

En estos días, estoy trabajando en mi proyecto AjTalk, una virtual machine interpretada, tipo Smalltalk, escrita en C#. El año pasado, escribí algunos posts sobre el proyecto. Es tiempo de refrescar su estado: puede manejar más de una VM en un proceso .NET, tiene agentes, procesamiento en paralelo, objetos distribuidos, acceso a la librería de clases .NET, y está por tener transacciones de objetos. Espero que sirva este posts para retomar el tema de cómo funciona.

Pueden bajar el código fuente desde (Work in progress):

http://code.google.com/p/ajtalk/

El aspecto actual de la solución (escrita usando Visual Studio 2008):

La librería núcleo es AjTalk, y todo su código fue escrito usando TDD. Los tests están en el proyecto AjTalk.Tests.

AjTalk está preparado para manejar sus propios tipos de objectos, que implementan la interfaz IObject:

y también puede manejar tipos y objetos nativos .NET. Strings, enteros y números en general son creados y manejados como objetos nativos .NET. Escribiré un post dedicado al tema del acceso a estos objetos nativos desde el lenguaje AjTalk (similar a Smalltalk).

La principal implementación de IObject es BaseObject. Internamente, mantiene una referencia a un objeto IBehavior y a un arreglo interno de variables de instancia:

    [Serializable]
    public class BaseObject : IObject, ISerializable
    {
        private IBehavior behavior;
        private object[] variables;
        public BaseObject()
        {
            this.behavior = null;
            this.variables = null;
        }
        public BaseObject(IBehavior behavior, int nvars)
        {
            this.behavior = behavior;
            this.variables = new object[nvars];
        }
//...
    }

Noten que las variables de instancias no son IObjects: son objetos generales, pueden tanto referenciar a objetos nativos .NET o a objetos IObject. Yo tenía planes de acceder a los objetos nativas via un decorador que implement IObject, en vez de referenciarlos directamente, pero decidí no hacerlo. La referencia es directa.

IBehavior está definida como:

Sus responsabilitidades son definir y recuperar métodos por nombre, crear instancias, y mantener la jerarquía de superclases. Los métodos son objetos que implementan IMethod, que deriva de IBlock:

Actualmente, todas estas interfaces están implementadas usando clases nativas. De esta manera, puedo usarlas sin tener una librería de clases escrita en el propio AjTalk. Es una suerte de “scaffolding”: una forma de tener una base inicial sobre la cual apoyar el resto de lo que estoy construyendo.

Hay 201 tests, todos en verde:

Buen code coverage, pero podría mejorar el número:

Próximos tópicos a tratar por aquí: la implementación de Machine, Blocks, acceso a objetos y tipos .NET, agentes, objetos distribuidos, y objetos transaccionales (Work in progress).

Nos leemos!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

AjGenesis y Generación de Código: viendo la luz

Thursday, October 21st, 2010

Ya tenía pendiente este post, desde hace unos meses. El bueno de @ferclaverino comentó su experiencia sobre generación de código en general, y con AjGenesis en particular, en su post:

Generación de Código, seguimos sin ver la luz?

(visiten el blog de @ferclaverino, recomiendo su material sobre Scrum y desarrollo ágil). Le agradezco desde acá, de forma especial, porque no es común encontrar un post sobre el uso de AjGenesis. Leo ahí:

En el pasado agile open de programación, junto con @claudiomeschini, @mariodallago y otra persona más que no recuerdo quien era, participamos de una sesión que los muchachos llamaron "Generación de código, seguimos sin ver la luz?" … El material del encuentro lo pueden encontrar acá: http://www.agiles.org/agile-open-tour/ba-2010-coding/resultados

Bien ahí! No sabía que el tema interesó en una conferencia ágil. Eso es bueno: discutir los pros y contras de este camino.

Pero notable encontrar también experiencia de alguien usando AjGenesis:

Yo comenté sobre una experiencia de hace unos años usando AjGénesis en un proyecto real y la de hace unas semanas, donde a partir de un modelo muy simple, generamos una solución web mvc con las características que venimos usando últimamente (service layer, domain model, repository, log4net, structure map, manejo de errores c/páginas amigables, etc). También generamos los scripts de nAnt p/integración continua con cruise control y deploy automatico al ambiente de QA.

Qué bueno! Sigo leyendo (yo comentaría todo el post, vayan y lean el post completo, destaco acá algunos fragmentos):

Una distinción importante para hacer en el modelo es separar el modelo de la tecnología o aspectos de implementación (como podrían ser paths, lenguage de programación, etc).

Exactamente. Algo que promuevo desde el comienzo del diseño de AjGenesis.

Algo interesante de AjGénesis es que el modelo se puede cargar de cualquier lado. Por ejemplo se podría cargar una parte del modelo a partir de las clases de una dll. Y luego completar ese modelo con más información, cómo por ejemplo que campos son requeridos p/armar un ABM.

Bien de nuevo! Tomar el modelo de donde sea, y completarlo. Más puntos incorporados en AjGenesis desde su “inception”.

Otra cosa que puede servir a la hora de diseñar un modelo es la de favorecer convención sobre configuración, para que sea menor la cantidad de cosas a definir en el mismo. Por ejemplo, podría asumir que todos los campos de mis entidades son requeridos y especificar puntualmente cuales no.

Siempre uno puede decidir. Y más en AjGenesis, si vieron los templates y tasks de ejemplo. ¿Qué pasa con el mantenimiento de los templates y de lo generado? Hay que separar bien lo manual, de lo generado automáticamente, de lo generado por única vez y luego seguimos manual. Fernando escribe:

Una manera de lidiar con esto vimos que es importante crear buenas abstracciones que separen la parte del problema que puede ser resuelta con generación de código, de la que no. De esa forma, hacer un cambio se traduce a modificar el modelo y volver a generar el código.

Eso es un gran subproducto de la actividad de diseño con generación de código desde un modelo: separar los problemas de la solución, lo importante de las “technicalities”. Hasta donde ví, muchas de los detalles pueden generarse automáticamente. Por ejemplo, en un proyecto real, cambiamos la solución de persistencia, y prácticamente todo el resto sobrevivió. Regeneramos los DAOs para que usaran el nuevo “approach”. También cambiamos las validaciones de las entidades.

Y más:

Conversamos también sobre algunos criterios para evaluar una herramienta de generación de código:

  • El código generado debería ser el mismo que escribiría a mano sin usar la herramienta.
  • Una prueba ácida sería que a partir del mismo modelo, debería ser posible generar código para otra tecnología.
  • El modelo a definir debe ser libre.
  • Posibilidad de cargar este modelo de cualquier lado: xml, excel, txt, dll, tablas, etc.

Notable! Hay alguien más que mi tía Carlota leyendo lo que escribo  …. 😉 😉

Es interesante encontrar este tipo de post y discusión. Siempre recomiendo que tomen alguna vez el camino de generación de código, con o sin AjGenesis (aunque si van por otra herramienta, dudo que consigan los puntos mencionados arriba). Vean si les sirve o no.

Nos leemos!

Angel “Java” Lopez
http://www.ajlopez.com
http://twitter.com/ajlopez

AjAgents: una nueva implementación

Thursday, October 14th, 2010

Hace ya un tiempo, escribí sobre mi proyecto AjAgents, describiéndolo, implementando algoritmos genéticos y otras demos mínimas. El proyecto se basa en el envío de mensajes a “agentes” (podría llamarlos actores, o algo así). Los agentes reciben los mensajes y los procesan uno por uno. Cada instancia de un agente no necesita manejar la concurrencia: los mensajes son encolados en una cola interna por cada agente (podría más adelante, reimplementar esto usando una cola compartida entre varios agentes, o consumida por un pool de threads fijo que vaya consumiendo trabajo pendiente de esa cola, derivándo cada uno al agente destino).

Pero mis anteriores implementaciones se basaban en el uso de Reflection, o, alguna vez, en la librería CCR de concurrency de Microsoft Robotics (ver mis anteriores posts para más detalles). Ahora, tomo otro camino: un agente es un wrapper alrededor de un objeto normal. El código fuente puede ser bajado desde mi  proyecto AjCodeKatas Google Code, en el directorio trunk/AjAgents.

La interface principal, clave, es:

    public interface IAgent<T>
    {
        void Post(Action<T> action);
    }

La nueva idea: usar una clase genérica. T es el tipo de nuestro tipo “clásico” (que no es un agente), y Post es el método para invocar una acción sobre el objet interno: una acción que es una “rutina” que recibe un parámetro instancia de tipo T. De esta manera, le indicamos al objeto interno. Si éste tiene un método:

inner.DoSomething(parameter);

puede ser invocado como un mensaje desde el agente como:

agent.Post(x => x.DoSomething(1));

Esta es la implementación base de agente:

    public class Agent<T> : IAgent<T>
    {
        private T instance;
        private AgentQueue<T> queue;
        private bool running;
        public Agent(T instance)
        {
            this.instance = instance;
        }
        public void Post(Action<T> action)
        {
            if (!this.running)
                this.Start();
            this.queue.Enqueue(action);
        }
        private void Start()
        {
            lock (this)
            {
                if (this.running)
                    return;
                this.queue = new AgentQueue<T>();
                Thread thread = new Thread(new ThreadStart(this.Execute));
                thread.IsBackground = true;
                thread.Start();
                this.running = true;
            }
        }
//...
    }

Noten que el método Post encola la acción en la cola interna del agente. El proceso de esta cola está en el método .Execute (que no se muestra arriba). La cola es una cola bloqueante (sé que hay algo así en .NET 4.0, pero escribí mi implementación para poder ejecutar todo esto en 3.x, o aún 2.x):

    public class AgentQueue<T>
    {
        private Queue<Action<T>> queue = new Queue<Action<T>>();
        private int maxsize;
        public AgentQueue()
            : this(100)
        {
        }
        public AgentQueue(int maxsize)
        {
            if (maxsize <= 0)
                throw new InvalidOperationException("AgentQueue needs a positive maxsize");
            this.maxsize = maxsize;
        }
        public void Enqueue(Action<T> action)
        {
            lock (this)
            {
                while (this.queue.Count >= this.maxsize)
                    Monitor.Wait(this);
                this.queue.Enqueue(action);
                Monitor.PulseAll(this);
            }
        }
        public Action<T> Dequeue()
        {
            lock (this)
            {
                while (this.queue.Count == 0)
                    Monitor.Wait(this);
                Action<T> action = this.queue.Dequeue();
                Monitor.PulseAll(this);
                return action;
            }
        }
    }

Apliqué TDD para el desarrollo de todas estas clases, un ejemplo de test:

        [TestMethod]
        public void InvokeIncrement()
        {
            ManualResetEvent handle = new ManualResetEvent(false);
            Counter counter = new Counter();
            Agent<Counter> agent = new Agent<Counter>(counter);
            agent.Post(c => { c.Increment(); handle.Set(); });
            handle.WaitOne();
            Assert.AreEqual(1, counter.Count);
        }

Mucho del código fue derivado (bueno… casi copy and paste 😉 de mi trabajo previo con AjSharp (channels, agentes, queues, goroutines… ).

Tengo preparada una demo “prueba de concepto” usando esta nueva implementación de AjAgents. Es mi “clásico” ejemplo de Web Crawler. Pero eso es tema para otro post.

Nos leemos!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

Armando una aplicación usando AjGenesis (Parte 6)

Thursday, October 7th, 2010

En el post anterior de esta serie, agregué mucho código para manejar la generación de proyectos y soluciones .NET. Esos archivos son complicados. En contraste, generar algo similar para Java, no estan complejo, porque solo necesitamos generar las estructuras de directorios, y algun archivo para Ant o para Maven. Pero en .NET, tenemos que armar los archivos de proyectos (que no son simples) así como los de las soluciones.

El código de este post pueden bajarlo de AppExampleStep06.zip. Necesitan los binarios de AjGenesisTrunkBinaries.zip. Como siempre el project AjGenesis, incluyendo los ejemplos de esta serie de posts, puede ser bajado desde http://ajgenesis.codeplex.com.

Una de las partes complicadas, en el anterior post, fue agregar los archivos .cs y .vb al archivo del proyecto. Escribí entonces:

for each Entity in Project.Model.Entities
  TransformerManager.Transform("Templates/CSharp/EntityClass.tpl", "${Project.BuildDirectory}/${Project.Name}.Entities/${Entity.Name}.cs", Environment)
  PrjEntities.Includes.Add(CreateFileCs("${Entity.Name}"))
end for

La list Includes dentro del objeto PrjEntities mantiene la información de los archivos a agregar en la generación del archivo de proyecto. En vez de agregar los archivos de esta forma, ahora, en este post, agrego los archivos que están en los directorios. Es decir, podemos ahora tener archivos manuales, escritos por nosotros, dentro del directorio resultado de la generación, y también serán reconocidos dentro de los archivos de proyecto C# y Visual Basic. De esta manera, podemos ir extendiendo los proyectos generados con nuestros propios archivos, que serán conservados en los proyectos despues de la generación de los archivos automáticos. En nuevo código está dentro de  Utilities/DotNetUtilities.ajg:

Sub AddVbFilesToProject(project)
  di = new System.IO.DirectoryInfo(project.Directory)
  prefix = ""
  AddVbFiles(project, name, di)
End Sub
Sub AddVbFiles(project, prefix, di)
  for each fi in di.GetFiles("*.vb")
    name = fi.Name.Substring(0, fi.Name.Length - 3)
    project.Includes.Add(CreateFileVb(prefix & name))
  end for
  
  sdirs = di.GetDirectories()
  
  for each sdir in sdirs
    AddVbFiles(project, prefix & sdir.Name & "\", sdir)
  end for
End Sub
Sub AddCsFilesToProject(project)
  di = new System.IO.DirectoryInfo(project.Directory)
  prefix = ""
  AddCsFiles(project, name, di)
End Sub
Sub AddCsFiles(project, prefix, di)
  for each fi in di.GetFiles("*.cs")
    name = fi.Name.Substring(0, fi.Name.Length - 3)
    project.Includes.Add(CreateFileCs(prefix & name))
  end for
  
  sdirs = di.GetDirectories()
  
  for each sdir in sdirs
    AddCsFiles(project, prefix & sdir.Name & "\", sdir)
  end for
End Sub

Otra “feature” que agregué en este paso: en el anterior post, cada vez que corríamos la generación de código, creaba y pisaba los archivos generados, aunque el contenido fuera al fin igual. Ahora, uso una rutina en Utilities/TransformUtilities.ajg:

Sub TransformFile(tpl, target, tm, env)
  if System.IO.File.Exists(target) then
    target2 = target & ".tmp"
    tm.Transform(tpl, target2, env)
    content1 = System.IO.File.ReadAllText(target)
    content2 = System.IO.File.ReadAllText(target2)
    if content1 <> content2 then
      System.IO.File.Copy(target2, target, true)
    end if
    System.IO.File.Delete(target2)
  else
    tm.Transform(tpl, target, env)
  end if
End Sub

Que sólo pisa el archivo final si es distinto del anterior. Entonces, en vez de invocar directamente al objeto predefinido TransformerManager:

  TransformerManager.Transform("Templates/CSharp/EntityClass.tpl", "${Project.BuildDirectory}/${Project.Name}.Entities/${Entity.Name}.cs", Environment)

las tareas invocak a la nueva rutina, comparando el archivo anterior con el nuevo, para ver si reemplaza o no el archivo destino:

  TransformFile("Templates/CSharp/EntityClass.tpl", "${Project.BuildDirectory}/${Project.Name}.Entities/${Entity.Name}.cs", TransformerManager, Environment)

Próximos pasos, para los siguientes posts: incluir identidad en las entidades del modelo, generar una base de datos desde el modelo, generar código de DAOs, y hasta volver a generar Java, quizás con Ant.

Nos leemos

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

Armando una aplicación usando AjGenesis (Parte 5)

Tuesday, September 14th, 2010

Publiqué un nuevo paso en esta serie de post sobre cómo ir generando una aplicación con AjGenesis. Anteriores posts:

Building An Application Using AjGenesis (Part 1)
Armando una Aplicación usando AjGenesis (Parte 1)
Building An Application Using AjGenesis (Part 2)
Armando una Aplicación usando AjGenesis (Parte 2)
Building an Application Using AjGenesis (Part 3)
Armando una Aplicación usando AjGenesis (Parte 3)
Building an Application Using AjGenesis (Part 4)
Armando una Aplicación usando AjGenesis (Parte 4)
Building an Application Using AjGenesis (Part 5)

El código puede ser bajado desde AppExampleStep05. El ejemplo:

Como en los anteriores ejemplos, necesitamos los últimos binarios de AjGenesis. Podemos bajarlo desde AjGenesisTrunkBinaries.zip. (el código fuente completo está en el repositorio de AjGenesis en Codeplex). Tienen que agregar el directorio bin al path, para ejecutar el ejemplo.

En el directorio raíz, seguimos teniendo los mismos tres comando que en el anterior post.

GenerateCSharp.cmd
GenerateJava.cmd
GenerateVbNet.cmd

El modelo es el mismo. Pero se agregaron los nuevos Tasks/GenerateCSharp.ajg, Tasks/GenerateVbNet.ajg. Ahora, podemos generar una solución .NET y proyecto. Este es el nuevo GenerateCSharp.ajg:

IncludeCode "Utilities/Utilities.ajg"
IncludeCode "Utilities/DotNetUtilities.ajg"
' Create Build Directories
FileManager.CreateDirectory(Project.BuildDirectory)
FileManager.CreateDirectory("${Project.BuildDirectory}")
FileManager.CreateDirectory("${Project.BuildDirectory}/${Project.Name}.Entities")
FileManager.CreateDirectory("${Project.BuildDirectory}/${Project.Name}.Entities/Properties")
Project.Solution = CreateObject()
Project.Solution.Guid = "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC"
Project.Solution.WebGuid = "E24C65DC-7377-472B-9ABA-BC803B73C61A"
Project.Solution.Projects = CreateList()
PrjEntities = CreateObject()
PrjEntities.Name = "${Project.Name}.Entities"
PrjEntities.Directory = "${Project.BuildDirectory}/${Project.Name}.Entities"
PrjEntities.Includes = CreateList()
PrjEntities.Projects = CreateList()
PrjEntities.Libraries = CreateList()
PrjEntities.Guid = CreateGuid()
PrjEntities.COMGuid = CreateGuid()
Project.Solution.Projects.Add(PrjEntities)
for each Entity in Project.Model.Entities
  TransformerManager.Transform("Templates/CSharp/EntityClass.tpl", "${Project.BuildDirectory}/${Project.Name}.Entities/${Entity.Name}.cs", Environment)
  PrjEntities.Includes.Add(CreateFileCs("${Entity.Name}"))
end for
for each CsProject in Project.Solution.Projects
  TransformerManager.Transform("Templates/CSharp/CsProject.tpl", "${CsProject.Directory}/${CsProject.Name}.csproj", Environment)
  TransformerManager.Transform("Templates/CSharp/AssemblyInfoCs.tpl", "${CsProject.Directory}/Properties/AssemblyInfo.cs", Environment)
end for
TransformerManager.Transform("Templates/CSharp/Solution.tpl", "${Project.BuildDirectory}/${Project.Name}.sln", Environment)

Muchas cosas nuevas! Pero el punto principal es: ahora, generamos un archivo de solución y un archivo de proyecto. Hay nuevos archivos de funciones utilitarios, y nuevos templates. Notemos la creación del objeto dinámico Project.Solution y de PrjEntities. Ver el CreateObject() y el CreateList() en Utilities/Utilities.ajg.

La solución generada en C#:

Una solución y proyecto es generado para VB.NET, también:

La generación de Java sigue igual que en el anterior post.

Próximos pasos: no generar de nuevo los archivos que no cambiaron, permitir archivos de código manual en los proyectos y mantenerlos luego de una nueva generación; nuevos proyectos (persistencias, interfaz de web, …)

Recuerden: hay otro camino para armar una aplicación, lo comenté en AjGenetizando una aplicación: partir de una aplícación ya armada, y comenzar a abstraer desde ahí, y hasta iterar: generar manual, abstraer, poner en generación automática, mantener lo manual, etc…. En cambio, esta serie de posts pretende ser un tutorial para quien recién se inicia con esta herramienta.

Nos leemos!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez

Armando una aplicación usando AjGenesis (Parte 4)

Friday, July 16th, 2010

En este post, generaremos, desde el mismo modelo, archivos de texto para C#, Java, y VB.NET. Anteriores post:

Building An Application Using AjGenesis (Part 1)
Armando una Aplicación usando AjGenesis (Parte 1)
Building An Application Using AjGenesis (Part 2)
Armando una Aplicación usando AjGenesis (Parte 2)
Building an Application Using AjGenesis (Part 3)
Armando una Aplicación usando AjGenesis (Parte 3)

El código de este post puede bajarse desde AppExampleStep04.zip.

Necesitamos los binarios últimos de AjGenesis. Podemos bajarlo desde AjGenesisTrunkBinaries.zip. (el código fuente completo está en el repositorio de AjGenesis en Codeplex). Tienen que agregar el directorio bin al path, para ejecutar los ejemplos de este post.

El ejemplo ahora tiene más estructura:

Projects\AjApp es el folder conteniendo el modelo del ejemplo, en Project.xml:

<Project>
	<Name>AjApp</Name>
	<Description>Building an Application using AjGenesis</Description>
	<Model>
		<Entities>
			<Entity Source="Entities/Customer.xml"/>
			<Entity Source="Entities/Supplier.xml"/>
		</Entities>
	</Model>
</Project>

Podría, entonces, agregar más carpetas, con otros proyectos, si lo necesitara.

En el directorio inicial, tenemos tres comandos:

GenerateCSharp.cmd

GenerateJava.cmd

GenerateVbNet.cmd

Los tres comandos tienen un contenido similar. Por ejemplo, veamos el de GenerateCSharp.cmd:

AjGenesis.Console Projects\AjApp\Project.xml Projects\AjApp\Technologies\CSharp.xml Tasks\Complete.ajg Tasks\Generate.ajg

Notemos que hay dos modelos que se cargan en modelo: Project.xml es como en los anteriores posts, tiene el modelo independiente de la tecnología. El nuevo, CSharp.xml, describe la tecnología a usar:

<Technology>
	<Name>CSharp</Name>
</Technology>

Por ahora, contiene solo el nombre del lenguaje a usar. Podría extender este modelo, en próximos posts, para que contenga la base de datos a usar, el webserver a usar, etc…  Entonces: Project.xml es el modelo abstracto. Technologies\CSharp.xml, Technologies\VbNet.xml, Technologies\Java.xml son los modelos que describen la tecnología a usar. Cada uno de los comandos Generate*.cmd carga el modelo abstracto, Y uno de los tecnológicos.

La tarea Complete.ajg:

' Set Build Directory
if not Project.BuildDirectory then
	Project.BuildDirectory = "Build/${Project.Name}/${Technology.Name}"
end if
FileManager.CreateDirectory(Project.BuildDirectory)
IncludeCode "Tasks/Complete${Technology.Name}.ajg"

Noten el uso de un nuevo truco: incluir el código a ejecutar desde otro archivo, usando un string dinámico. Si Technology.Name == “CSharp”, la tarea de arriba termina ejecutando CompleteCSharp.ajg:

' Some functions
' Name to use for variables
function CSharpVariableName(name)
	firstletter = name.Substring(0,1)
	
	return firstletter.ToLower() & name.Substring(1)
end function
' Name to use for Classes, Properties..
function CSharpName(name)
	firstletter = name.Substring(0,1)
	
	return firstletter.ToUpper() & name.Substring(1)
end function
function CSharpType(type)
	type = type.ToLower()
	
	if type="text" then
		return "string"
	end if
	
	if type="integer" then
		return "int"
	end if
	
	return type
end function
' Set namespace to use in CSharp code
if not Project.CSharp.Namespace then
	Project.CSharp.Namespace = CSharpName(Project.Name)
end if
' Complete Entities
for each Entity in Project.Model.Entities
	' Set the variable name to use for an entity
	if not Entity.CSharp.VariableName then
		Entity.CSharp.VariableName = CSharpVariableName(Entity.Name)
	end if
	
	for each Property in Entity.Properties
		' Set the CSharp to use in each property
		if not Property.CSharp.Type then
			Property.CSharp.Type = CSharpType(Property.Type)
		end if
	end for
end for

Hay tareas escritas similares a la de arriba, para otras tecnologías: CompleteVbNet.ajg, CompleteJava.ajg. Estas tareas completan el modelo en memoria (asignando namespaces, packages, nombres para usar en las variables y propiedaes, el directorio a usar para dejar lo generado, etc…)

La segunda tarea es Generate.ajg:

IncludeCode "Tasks/Generate${Technology.Name}.ajg"

De nuevo, el viejo truco de un include de código dinámico. Esta es la subtarea GenerateCSharp.ajg:

for each Entity in Project.Model.Entities
	TransformerManager.Transform("Templates/CSharp/EntityClass.tpl", "${Project.BuildDirectory}/${Entity.Name}.cs", Environment)
end for

Compare, con CompleteJava.ajg:

' Some functions
' Name to use for variables
function JavaVariableName(name)
	firstletter = name.Substring(0,1)
	
	return firstletter.ToLower() & name.Substring(1)
end function
' Name to use for Classes, Properties..
function JavaName(name)
	firstletter = name.Substring(0,1)
	
	return firstletter.ToUpper() & name.Substring(1)
end function
function JavaType(type)
	type = type.ToLower()
	
	if type="text" then
		return "String"
	end if
	
	if type="integer" then
		return "int"
	end if
	
	return type
end function
' Set package to use in Java code
if not Project.Java.Package then
	Project.Java.Package = JavaName(Project.Name).ToLower()
end if
' Complete Entities
for each Entity in Project.Model.Entities
	' Set the variable name to use for an entity
	if not Entity.Java.VariableName then
		Entity.Java.VariableName = JavaVariableName(Entity.Name)
	end if
	
	for each Property in Entity.Properties
		if not Property.Java.VariableName then
			Property.Java.VariableName = JavaVariableName(Property.Name)
		end if
		
		' Set the Java to use in each property
		if not Property.Java.Type then
			Property.Java.Type = JavaType(Property.Type)
		end if
	end for
end for

y el GenerateJava.ajg:

for each Entity in Project.Model.Entities
	TransformerManager.Transform("Templates/Java/EntityClass.tpl", "${Project.BuildDirectory}/${Entity.Name}.java", Environment)
end for

Hay templates para cada tecnología. Ejemplo, este es el EntityClass.tpl para CSharp:

// Entity Class, generated with AjGenesis (http://ajgenesis.codeplex.com)
namespace ${Project.CSharp.Namespace} {
	public class ${Entity.Name}
	{
<#
	for each Property in Entity.Properties
#>
		public ${Property.CSharp.Type} ${Property.Name} { get; set; }
<#
	end for
#>
	}
}

y éste es el de Java EntityClass.tpl:

// Entity Class, generated with AjGenesis (http://ajgenesis.codeplex.com)
package ${Project.Java.Package};
public class ${Entity.Name}
{
<#
	for each Property in Entity.Properties
#>
		private ${Property.Java.Type} ${Property.Java.VariableName};
<#
	end for
	for each Property in Entity.Properties
#>
		public ${Property.Java.Type} get${Property.Name}()
		{
			return this.${Property.Java.VariableName};
		}
		public void set${Property.Name}(${Property.Java.Type} value)
		{
			this.${Property.Java.VariableName} = value;
		}
<#
	end for
#>
}

Ejecutando los tres comandos Generate*.cmds, se crea el directorio Build, con Build\AjApp, Build\AjApp\CSharp, Build\AjApp\Java, Build\AjApp\VbNet, un directorio por el proyecto, con subdirectorios por tecnología.

Veamos el Customer.cs generado:

// Entity Class, generated with AjGenesis (http://ajgenesis.codeplex.com)
namespace AjApp {
	public class Customer
	{
		public string Name { get; set; }
		public string Address { get; set; }
	}
}

El Customer.vb generado:

' Entity Class, generated with AjGenesis (http://ajgenesis.codeplex.com)
Namespace AjApp
	Public Class Customer
		Private mName as String
		Private mAddress as String
		Public Property Name as String
			Get
				return Me.mName
			End Get
			Set(value as String)
				Me.mName = value
			End Value
		End Property		
		Public Property Address as String
			Get
				return Me.mAddress
			End Get
			Set(value as String)
				Me.mAddress = value
			End Value
		End Property		
	End Class
End Namespace

Y el Customer.java generado:

// Entity Class, generated with AjGenesis (http://ajgenesis.codeplex.com)
package ajapp;
public class Customer
{
		private String name;
		private String address;
		public String getName()
		{
			return this.name;
		}
		public void setName(String value)
		{
			this.name = value;
		}
		public String getAddress()
		{
			return this.address;
		}
		public void setAddress(String value)
		{
			this.address = value;
		}
}

Próximos pasos: generar proyectos C# o VB.NET, listos para cargar en Visual Studio, o un proyecto Eclipse para Java.

Nos leemos!

Angel “Java” Lopez

http://www.ajlopez.com

http://twitter.com/ajlopez