Andami es un framework orientado a plugins construido sobre
swing que permite la construcción de forma rápida y extensible
de aplicaciones MDI (Multiple Document Interface). Tiene ya implementadas muchas
de las funcionalidades requeridas para este tipo de aplicaciones tales como
un menú Ventana, en la que van apareciendo las ventanas que se abren,
soporte para el traducciones, configuración personalizada para cada usuario,
actualizaciones automáticas, tanto del propio Andami como de los plugins,
persistencia del tamaño y posición de la ventana, del idioma,
distintos tipos de vista, con la finalidad de facilitar la programación
de ventanas especiales, ... Todo esto además de tener ya solventados
los problemas típicos de la programación de un entorno como éste
que no son pocos.
Además, Andami está diseñada de forma
que la propia lógica MDI puede ser reemplazada. Si en lugar de una aplicación
estilo arcView, se quiere que cada vista que se añada, se haga en una
ventana de windows nueva (de las que aparecen en la barra de estado) o una vista
como la de Eclipse, no hay más que desarrollar el plugin adecuado.
Andami está diseñada para ser amistosa con
el usuario y para ello incorpora la posibilidad de añadir tooltips y
enabletexts a los botones. El tooltip es el texto que aparece cuando el ratón
se detiene sobre un botón o menú. El enableText es el texto que
aparece en el caso anterior cuando el botón está desactivado,
permitiendo mostrar al usuario qué es lo que debe hacer para que dicho
botón se active (o cualquier otro mensaje).
Por último, Andami tiene una gestión de errores
que agradará a cualquier programador, ya que en casos de errores graves,
el propio framework avisa de dicho fallo y aconseja al usuario salvar los cambios
y reiniciar el programa, a parte de que toda excepción no capturada por
el usuario se redirige a un fichero de log configurable en el que se escribirá
su traza.
Andami gira en torno al concepto de plugin. Andami mantiene
un directorio como directorio de los plugins, que se puede cambiar en cualquier
momento. Un plugin viene definido por la existencia de un subdirectorio dentro
del directorio de los plugins, siendo el nombre del plugin el nombre de dicho
directorio. Dentro de dicho directorio debe haber un fichero config.xml en el
que se configuran los puntos de entrada y salida del plugin (menúes,
barras de herramientas, etiquetas de la barra de estado, menúes contextuales),
las librerías que va a usar, el paquete de traducciones, los plugins
de los que depende, etc. El fichero plugin-config.xsd contiene el esquema que
ha de seguir este fichero y están comentados todos los elementos. En
el CorePlugin que viene con Adami se puede ver un ejemplo de config.xml.
Las rutas de los directorios son siempre relativas al directorio
del plugin y los textos de los menúes, tooltips y enabletext's son claves
en el fichero de traducciones en caso de que haya y textos literales en caso
de que no haya traducciones. El fichero de traducciones es un fichero de propiedades
común: ver ResourceBundle en la API de Java.
Dentro de los plugins aparece el concepto de extensión.
Una extensión es instalada por los plugins mediante la implementación
de la interfaz com.iver.andami.plugins.Extension y la instalación de
unos controles geobernados por ésta en el fichero config.xml. Mediante
esta implementación se le dice a Andami la condición que se debe
cumplir para que los controles sean visibles o estén activos. Además
se implementa la acción a llevar a cabo cuando se selecciona uno de los
menúes o botones asociados a la extensión. Cabe resaltar que Andami
crea una instancia de cada extensión configurada en config.xml, por lo
que las clases que implementen la interfaz Extension deben de tener un constructor
sin argumentos.
En los tag 'extension' existe un atributo class-name en el
que se especifica la clase que gobierna el punto de extensión que se
está definiendo. Esta clase deberá implementar la interfaz com.iver.andami.plugins.Extension
y será mediante ésta que gobernará los menúes y
botones asociados a este punto de extensión
Una problema común en este framework es no ver cómo
se mantiene la información del proyecto concreto que se está desarrollando.
El lugar adecuado es una de las extensiones instaladas. Por ejemplo en el caso
de gvSIG está la extensión com.iver.cit.gvsig.ProjectExtension,
en la cual hay un atributo Project que es la raíz del árbol jerárquico
del cual penden las vistas, mapas, tablas, ... Además esta extensión
tiene un método getProject que devuelve la referencia al proyecto, de
manera que se puede acceder desde cualquier punto de la aplicación. Para
ver el acceso a las instancias de las extensiones ver acceso
a las extensiones
Funcionamiento
del class loader
En Andami, el class loader de cada plugin delega primero en el classloader
del sistemaes decir, que si se ejecuta desde eclipse buscará por todos
los jars que haya en el classpath del proyecto, y si se ejecuta desde la linea
de comandos, buscará en la variable de entorno CLASSPATH o en el argumento
-classpath que se pase como parámetro a java.
Si el class loader del sistema no satisface la búsqueda, se buscará
en los jars del directorio especificado por el config.xml del plugin que intenta
cargar la clase y si no se encuentra en dichos jars, se buscará en los
jars de los plugins de los cuales depende el plugin que intenta cargar la clase.
Servicios
a los plugins
Andami ofrece a los plugins distintos servicios
a través de la clase PluginServices en cuyo javadoc se puede obtener
información sobre como usarlos. Existen unos servicios genéricos
que vienen dados por métodos estáticos de dicha clase y luego
está el método getPluginServices que obtiene una instancia de
esta clase específica del plugin, mediante la cual puede acceder a servicios
concretos de cada plugin, traducciones, persistencia, directorio del plugin,
...
Ejecución en segundo
plano
Es conveniente que la interfaz gráfica esté
siempre en funcionamiento, nunca bloqueada, aunque sea sólo para mostrar
al usuario que el programa está procesando. Para ello hay que realizar
las tareas que puedan tomar demasiado tiempo en un thread a parte. La clase
PluginServices proporciona un método estático backgroundExecution
al cual se le pasa un objeto Runnable, que es ejecutado en segundo plano, dejando
el thread de la interfaz libre para responder pero con sus eventos bloqueados
con el fin de que la interfaz responda y se redibuje, pero se ignoren los eventos
que produce el usuario mientras se procesa la petición
Acceso a las extensiones
Para acceder a la instancia de una extensión
se puede usar simplemente el método de PluginServices getExtension(Class)
a la cual habrá que pasar como parámetro la clase de la extensión
a la que se quiere acceder. Dicho método retorna un objeto Extensión
y por tanto habrá que hacer casting a la clase concreta de dicha extensión,
habiendo obtenido así la referencia a la instancia de la extensión
deseada.
A la hora de desarrollar habrá que tener en el build
path del entorno de desarrollo que se use, el jar del plugin dentro del cual
está la extensión que se quiere obtener, para poder pasarle como
parámetro a getExtension(Class) la clase de la misma.
Persistencia de
los plugins
Uno de los servicios que ofrece Andami a los
plugins es la facilidad de guardar datos genéricos de los mismos en el
directorio del usuario de manera que cada usuario mantiene su propia configuración
de los plugins. Para ello, las instancias de PluginServices contienen una propiedad
persistentXML que puede ser obtenida y asignada y que es de tipo XMLEntity,
pudiendo añadir información de tipo básico (String, int,
long, ...) a dicha instancia y siendo esta información persistente entre
ejecuciones.
Pop-up menus
Otro servicio proporcionado por Andami es el de pop-up
menu's extensibles. Mediante el XML se puede definir un pop-up menu con un nombre
y unas entradas al igual que cualquier otro menú, con la única
diferencia que para mostrar el popup menú habrá que registrar
un listener de la siguiente manera:
<>public void addPopupMenuListener(String name, Component c, ActionListener listener)>
<popupMenu name="cascada">y tenemos otro plugin que quiere añadir una entrada a dicho menú. Para ello deberá de incluir un fragmento similar a este en su fichero config.xml:
<entry text="Cascada"
tooltip="cascada_tooltip"
enableText="cascada_enable" actionCommand="CASCADA"/>
<entry text="Tile"
tooltip="tile_tooltip"
enableText="cascada_enable" actionCommand="CASCADA"/>
</popupMenu>
<popupMenu name="com.iver.cit.gvsig.cascada">y además deberá de registrarse como listener de la manera que se explicó anteriormente.
<entry text="Nueva entrada" actionCommand="NUEVA"/>
</popupMenu>
PluginServices.getMDIManager().getViewInfo(this).setTitle("Nuevo título");Puede ser necesario que algunas vistas realicen algún tipo de procesamiento al ser activadas pero esto no se sabe cuando ocurre ya que lo que se entrega al manager MDI es un JPanel. Para recibir los eventos sobre las vistas, además de implementar View hay que implementar ViewListener la cual proporcionará los métodos que serán invocados cuando sucedan los eventos oportunos en las vistas.