experiencia
Servicios - Sepa lo que Ricardo Devis & Asociados pueden hacer por ustedPublicaciones - Consulte los documentos que ponemos a su disposiciónContacto - Conozca como ponerse en contacto con Ricardo Devis & Asociados


RPP febrero 1996

Todo Java (III): ¿Beans, judías o café en grano

Ricardo Devis Botella

 

Java es a café lo que Beans a granos de café : o sea, pura metáfora comercial que se extiende más allá de lo líquido : Beans son judías, como también una suerte de componentes con los que cocinar enchiladas software : ¿OLE? ¿OpenDoc? ¿No conocen la receta? ¡Pues cocínenlo con judías! : claro que … ¿dónde para la tortilla de patatas? [1]

 

Los modelos de componentes definen desde hace tiempo las diferentes estrategias empresariales y de visión negocial de la informática respecto de las distintas empresas implicadas: bien se procura un modelo al que habría que acogerse, bien se decide crear un pegamento que integraría al resto de los modelos. Como todos medramos bajo el sol, su subsidiaria JavaSoft no podía dejar de entrar en este pingüe negocio: Beans (Judías) son las unidades de composición de la tecnología universal de componentes interoperables propuesta por Sun en base a Java. En realidad la base práctica son otras tecnologías existentes (OpenDoc, OLE/COM/ActiveX y LiveConnect, sustantivamente), de forma que los Beans vienen a conformar, como ocurre con el lenguaje Java mismo, una adecuada, discreta y nada revolucionaria mixtura de características que, al igual que el bytecode Java, ha obtenido un estruendoso soporte industrial.

CUITAS LÉXICAS

Un “bean” es una semilla de leguminosa con forma de riñón (o sea, una habichuela, una judía: componentes básicos para cocinar … ¡software!), como también es un grano de café (acepción que engarza perfectamente con la metáfora Java) o, en americano coloquial, la cabeza (ya saben, donde se supone anida cierta pequeña cantidad de inteligencia). Naturalmente los chicos de marketing de JavaSoft se lo pensaron bastante antes de publicitar la marca “Java Beans”, y es posible que hasta la leyenda esté fraguándose en estos momentos (“Se encontraba el equipo de desarrollo zampándose unas enchiladas cuando alguien sugirió que la composición de judías, con cierta inteligencia, generaba un conjunto distinto a sus partes, además de flato, y …”). Claro que esta diversidad nos plantea un pequeño problema de traducción, que solucionaremos simplemente utilizando el barbarismo “Bean” (masculino, ya que estamos) para referirnos a cada uno de los componentes Java que a continuación revisaremos.

CONSTRUCTORES VISUALES

Mención hecha de los aspectos léxicos, ¿qué es, en definitiva, un Bean para Java? Según la especificación del API de Java Beans 1.0, versión 1.00-A del 04-12-96, que el lector que se precie ya debería haber descargado de Internet, un Bean es “un componente software reutilizable que puede ser manipulado visualmente en una herramienta constructora”. Claro que … ¿qué demonios es una herramienta constructora? El avispado lector habrá adivinado que en este contexto nos estamos refiriendo a entornos de creación y edición visual de piezas software especialmente preparados para manejar Beans: Sunsoft Java Workshop, Borland J Builder, Symantec Visual Café, Sunsoft Java Studio, Penumbra Mojo e IBM VisualAge for Java son ejemplos de este tipo de constructores visuales. Pero fíjese el lector en la definición de Bean: “componente software reutilizable” es lo que todas las empresas afirman, como madres de sus hijos, de sus propios esquemas y productos; el resto de la frase es realmente lo que importa: la capacidad de manejo visual avanzado de componentes, embebida en estos mismos, mediante constructores/contenedores, que a su vez también serán Beans, conseguiría que se difuminara (o al menos suavizara) la separación entre construcción y uso de software. Intentaré explicarme con un ejemplo. Imaginemos la construcción de un diálogo con un típico entorno de desarrollo software (IDE, IDDE o cualquier otro estúpido acrónimo) que llamaremos Visual F*ck++ (VF++): dibujamos en una pantalla especial, con la ayuda de paletas de herramientas, la ventana que nos interesa, y añadimos componentes visuales como botones, listas, etc.; tras esto, y algunas operaciones más o menos absurdas, Visual F*ck++ genera un ficherito que, con un propietario lenguaje de script (DIALOG “MI_DIALOGO”, BUTTON 5,12, …), supone la codificación respecto de VF++ del diálogo para su veloz recuperación e interpretación por el entorno. Claro que si lo que queremos es que tal diálogo use determinados esquemas de eventos, sujetos por ejemplo a la AWT de Java, el entorno generará el código correspondiente que, tras ser compilado, producirá en el aplicativo software la ventana deseada. Lo que Visual F*ck++ nos plantea es el conocido esquema dicotómico por el que se ha regido durante mucho tiempo nuestra forma de codificar aplicaciones: una cosa son los componentes en la fase de construcción (que usualmente dependen salvajemente de la herramienta de programación) y otra usualmente bien distinta son los componentes generados que se utilizan en sistemas software de uso práctico. Pero ¿por qué construir dos modelos para una misma entidad? ¿No equivale esto a disponer de una esposa para el trabajo doméstico y de una amante para el trato sexual [2]? ¿Acaso utilizamos un televisor distinto para cada canal? Humm, parece difícil creer que un determinado componente es reutilizable si ni siquiera puede utilizarse a la vez en las fases de construcción y uso. Claro que es posible que, como en el símil canallesco, uno vista determinada ropa para desempeñar ciertas funciones y porte otra (o carezca de ella) para distintas situaciones, pero esto es lo razonable, claro, suponiendo igual a la persona. Oh, el lector ya habrá adivinado que, por ejemplo, los muy mentados lenguajes 4G utilizan el diabólico esquema dicotómico mencionado, de forma que su supuesta facilidad de (re)uso no es sino un ingrediente adictivo y de auto-conformidad, como la basura que las compañías tabaqueras añaden a los cigarrillos para generar en los usuarios el hábito de su consumo. Respecto de los 4G la pregunta resuena evidente: “¿más fácil que … qué?”. La prudencia nos sugiere que con cuantos menos paradigmas trabajemos nuestra comprensión será más rápida y nuestro trabajo más eficiente. Si frente al anterior esquema dicotómico trabajáramos en la etapa de construcción visual con exactamente los mismos componentes que vamos luego a insertar en el aplicativo software, no tendríamos que aprender dos modus operandi distintos. “¡Pero -explota el lector- eso ya lo tenemos con los controles ActiveX!”. Bueno, ciertamente los Beans están más cerca de ActiveX que de OpenDoc, pero hay algunas diferencias que apreciaremos mejor un poco más adelante. Pero, retomando la exposición sobre la construcción visual de componentes, el lector debe asumir claramente que es esta capacidad la que distingue a un Bean y lo separa conceptualmente de, verbigracia, una biblioteca de clases. Una tal biblioteca de acceso, por ejemplo, a bases de datos (cual es la JDBC) no se preocupa de las cuitas visuales, de forma que es perfectamente operativa respecto de las tareas programáticas habituales, pero sin duda su complemento ideal sería un conjunto de Beans con capacidades de manejo visual. Note el lector que al hablar de visualización de Beans me refiero tanto a los que poseen una representación GUI como a los que no la poseen (también llamados Beans Invisibles), pero que al fin serán mostrados en el contenedor de Beans como una suerte de iconos.

MENSAJES Y ESTRATEGIAS

Si Java pretende la independencia respecto de las plataformas hardware/software, lo que los Beans además postulan es la integración de tales plataformas. Y es que los applets, por ejemplo, son componentes multiplataforma, pero no hay forma sencilla de intercomunicarlos en una mísera página web si no es mediante pegamentos de script que desde luego no superan la prueba del algodón (o también mediante su conversión en objetos COM, claro). Hay, pues, que integrar (aunque a más de un e x le dé lo mismo [3]). Pero esto era tan de esperar como el final feliz en las películas de Walt Disney. Y si éste es el mensaje, la estrategia pasa, naturalmente, por la simplicidad y la explotación de las características del lenguaje Java, de manera que los usuarios puedan ser inmediatamente productivos y la curva de aprendizaje sea … en fin, que sea leve [4].

Figura 1 : La Curva del Aprendizaje de los Beans

En definitiva se trata de que los Beans observen una adecuada granularidad en cuanto componentes (algunos Beans serán aplicaciones per se, mientras que otros habrán de ser sólo pequeños trozos embebidos en aplicativos de cualquier tipo), transportabilidad (no sólo respecto de las plataformas, ya procurada por Java, sino también respecto de otros modelos de componentes software como ActiveX/OLE/COM, OpenDoc o Netscape LiveConnect, hacia los cuales se han tendido Beans que ofician de puentes bidireccionales), simplicidad (de forma parecida a cómo se lanzaron los applets, el paquete de Beans va a procurar una funcionalidad mínima que cada componente habrá de desarrollar de forma externa al conjunto de bibliotecas procurado por Sun) y, sobre todo, unos APIs uniformes y de alta calidad (al fin llegamos) que cubren las siguientes áreas principales: Persistencia, gestión de Eventos, Introspección y, finalmente, Personalización o APIs de constructores de aplicaciones. Mucho de lo que en adelante se tratará ha sido incorporado como novedad o modificado en la versión 1.1 del JDK (Java Development Kit), que ha supuesto un cambio grueso respecto de la anterior versión 1.0. En tal sentido, cada vez que se hable en lo sucesivo de adiciones o modificaciones a la plataforma Java, se entenderán como novedades respecto de la versión 1.0. Se citarán, también, clases e interfaces del paquete (package) java.beans y del BDK (Beans Development Kit) de JavaSoft, que se recomienda encarecidamente al lector interesado en estas cuitas. De momento vamos a centrarnos en los APIs.

PERSISTENCIA

Son APIs que permiten a los Beans almacenar externamente su estado interno mediante un comportamiento por defecto basado en la serialización provista por la plataforma Java, pero previendo, a la vez, sofisticados APIs de persistencia para aquellos componentes que requieran un control más explícito sobre su estado de persistencia. Y es que, debido a su deseada granularidad como componentes, los Beans deben cubrir desde la simple gestión de un objeto de configuración (o sea, la versión no-cutre de un ficherito con parámetros de inicialización, para entendernos) hasta la persistencia en su embebimiento en complejos documentos compuestos (en el más puro estilo OLE). Como se anunció antes, Java provee una solución de persistencia por defecto, que incluye almacenamiento y recuperación de objetos, basada en el mecanismo de Serialización de Objetos de Java (una parte importante de la plataforma Java incluida en JDK 1.1, bien relacionada con la capacidad de distribución de código, según veremos más adelante). La idea es conceptualmente clara: se trata de un mecanismo que permite, mediante la implementación de java.io.Serializable, convertir bidireccionalmente un objeto Java en una secuencia (stream) de bits, que podrá ser transportada por la red o almacenada en un fichero con extensión “.ser”:

public class Judia implements java.io.Serializable {

// las variables transitorias deben indicarse

transient Object copiaTemporal;

// el resto serán persistentes

DatosPrivados miDireccion;

// etc. etc.

}

// almacenamiento en un OutputStream

ObjectOutputStream ost = new ObjectOutputStream( miSecuencia );

out.writeObject( new Judia() );

// recuperación (reconstrucción) del objeto almacenado

ObjectInputStream ist = new ObjectInputStream( miSecuencia );

Judia miJudia = (Judia)ist.readObject();

El mecanismo de serialización prevee también la adición de información arbitraria extra (no extraíble directamente de los miembros de la clase) mediante la implementación de “writeObject” y “readObject” (de java.io.Serializable), además de poder gestionar distintas versiones de una misma clase (diferenciadas por un valor “static final long SerialVersionUID” definido en cada clase), de manera que objetos serializados de una clase dada podrían ser asimilados a nuevas versiones de la misma clase suponiendo que no se han eliminado campos, cambiado la posición jerárquica de la clase o modificado el tipo o condición transitoria/persistente de algún campo.

Aparte del mecanismo por defecto notado, se ha previsto también otro de “externalización”, mediante la implementación del interfaz java.io.Externalizable, que únicamente troca persistente la identidad de la clase afectada y deja a ésta la responsabilidad de almacenar y recuperar su contenido. Este enfoque proporciona un mayor control sobre el formato de almacenamiento, que así podrá mimetizar otros conocidos, pero deja todo el trabajo en manos del programador y esto, como saben, no suele producir los resultados esperados.

GESTIÓN DE EVENTOS

Encajan aquí los APIs que posibilitan que un componente pueda “descargar” eventos sobre otros componentes, de forma que puedan conectarse eventos de orígenes arbitrarios con “sumideros” (sinks [5]) de eventos. En realidad se trata de la generalización del nuevo modelo de eventos de la AWT (pues el anterior correspondiente al JDK 1.0, como he notado hasta la saciedad, es notoriamente deficiente), disponible en el JDK 1.1, que se ha querido aplicar a los Beans tras considerar los equipos de Java Beans, AWT y los licenciatarios Java un buen número de diseños diferentes. Tal nuevo modelo incorpora facilidades para la delegación de eventos y, naturalmente, para su integración en constructores visuales. De hecho, el nuevo modelo de eventos es uno de los núcleos esenciales de la arquitectura de los Beans, pues permite a estos ser insertados indoloramente en constructores visuales y, a la vez, calificarlos bien como destinos bien como emisores de eventos manteniendo un cúmulo de características que fueron establecidas como metas del diseño de la arquitectura de gestión de eventos: Extensibilidad (por medio de un modelo de propagación de eventos que permita la fácil incorporación de nuevos tipos de eventos), robustez , con soporte para la Introspección (o capacidad de inquirir a un Bean por sus detalles internos, como veremos más adelante, posibilitando así la interconexión de Beans) y simplicidad (de forma que buena parte de la inteligencia de interconexión y comunicación de eventos sea independiente tanto del usuario como del continente de los Beans). Personalmente he de decir que es la última característica la que me parece más importante, pues -supuesto el éxito universal de Java, Java Beans y Java XXX- su consecución obligaría al desmantelamiento de entornos de codificación propietarios, incompatibles y penosos en cuanto a transportabilidad. Y es que el futuro también puede depararnos sucesos agradables, a veces.

El Nuevo Modelo de Eventos

Tal nuevo modelo es, simplemente, un mecanismo para conectar Beans, y su unidad es, evidentemente, el “evento”, que no es sino una notificación entre un Bean origen (source) y uno o más Beans destinos (listeners: oyentes) interesados en ser notificados de la emisión de tal notificación.

 

Figura 2: Nuevo Modelo de Eventos de la AWT

 

Como puede verse en la Figura 2, el Bean origen, tras lanzar un evento determinado, revisa qué otros Beans (si acaso alguno) se registraron, mediante un método accesible en la clase del Bean origen, para ser notificados cuando tal lanzamiento se produjera, y seguidamente efectúa la propagación del evento en los Beans destinatarios (listeners) mediante la invocación de un método correspondiente a las clases de éstos.

Estados Internos de Eventos

Decíamos que un evento es una mera notificación, así que ¿dónde se almacena la información asociada? Pues en un objeto de tipo “Estado de Evento”, correspondiente a una clase derivada de java.util.EventObject, y que ya en su construcción

public EventObject( Object source );

debe ineludiblemente asociarse a un objeto que actuará como emisor del evento mismo. Usualmente las Clases de Estados de Eventos no poseen un interfaz de modificación -esto es, los objetos permanecen inalterados desde su creación-, de forma que las propiedades de notificación, encapsuladas en cada objeto son accesibles normalmente sólo para lectura. Es posible, también, utilizar una tal clase para definir un conjunto de propiedades aplicables a distintos tipos de Eventos/Notificaciones. Así la clase

public class EventoDeTeclado extends java.util.EventObject {

public int getModificadores();

public char getCaracterTecla();

}

puede utilizarse para representar múltiples eventos del tipo “TeclaPulsada” o “TeclaLiberada”.

El problema de la Contingencia

Hemos visto que un objeto Evento se asocia en su creación a un objeto calificado como su origen, pero ¿cómo se identifica per se tal objeto como emisor de notificaciones? Pues … implementando los métodos cuyos prototipos de identificadores se dan a continuación, y que no son sino métodos de registro para añadir y eliminar objetos interesados en ser notificados de los eventos que pueda emitir la clase:

// <TipoDeListener> deberá ser sustituido, en el código práctico,

// por el identificador de la clase calificada como Listener

// (destino) y cuyo mismo identificador acabará en “Listener”

public void add<TipoDeListener>( <TipoDeListener> destino );

public void remove<TipoDeListener>( <TipoDeListener> destino );

// Los métodos anteriores determinan una fuente de eventos

// “multicast”: esto es, que puede notificar a varios Listeners.

// Si lo que se quiere es una fuente (source) “unicast”, lo único

// que hay que hacer es indicar en la signatura del método la

// excepción que se nota a continuación.

public void add<TipoDeListener>( <TipoDeListener> destino )

throws java.util.TooManyListenersException;

public void remove<TipoDeListener>( <TipoDeListener> destino );

Oh, ya me estoy imaginando el pasmo del lector: ¿Cómo la mera declaración de unos métodos con nombres por determinar cualifica a una clase como emisora de unos eventos dados? La respuesta es simple: el paquete de Beans cuenta con mecanismos automáticos que permiten, tras inspeccionar una clase, determinar (o mejor: inferir), en base a unas reglas de codificación de identificadores, si tal clase puede o no hacer tal cosa. Tal inspección se encuadra en el conjunto de APIs dedicados a la Introspección, que veremos más adelante. Así que si nos encontramos con una clase que contiene:

public class TextoCódigoFuente {

public void addTecladoListener(TecladoListener tl);

public void removeTecladoListener(TecladoListener tl);

//…resto de la clase

}

su análisis automático por Introspección comunicará al usuario que se trata de una clase emisora multicast de eventos que pueden ser manejados por cualquier clase que implemente el interfaz “TecladoListener”. Ésta es toda la información que necesitamos para, si tenemos un objeto de una tal clase listener, proceder a registrarlo en la clase origen “EditorDeCodigo” mediante el método “addTecladoListener”:

public class TextoCódigoFuente {

// En el siguiente vector (que bien podría haber sido

// cualquier otra estructura de datos) se almacenarán

// los destinos (listeners) de los eventos de Teclado

// según los objetos vayan registrándose o borrándose

private Vector listeners = new Vector();

// la sincronización evita problemas de “carreras” en

// la emisión de notificaciones. De hecho es una

// recomendación de JavaSoft trabajar con métodos

// sincronizados en los emisores de eventos.

public synchronized void

addTecladoListener(TecladoListener tl) {

listeners.addElement( tl );

}

public synchronized void

removeTecladoListener(TecladoListener tl) {

listeners.removeElement( tl );

}

// …resto de la clase, incluido el método de despacho

// de notificaciones a cada uno de los “listener”

// registrados (iterando por el vector privado), en

// los que finalmente se invocará un método dado.

}

Receptores de Eventos

En los párrafos anteriores han aparecido constantemente los denominados “Event listeners” (o Receptores de Eventos), y éstos no son más que clases que implementan un interfaz derivado de java.util.EventListener, cuyos nombres, por convenio, finalizan en “Listener”, y que contienen un método por cada tipo de evento que pueden recibir, con el siguiente esquema de codificación (o patrón de diseño):

void <NombreMetodoOcurrenciaEvento>( <SubclaseDeEventObject> e );

Lo usual es que se agrupen en el mismo interfaz los métodos manejadores de eventos relacionados, como por ejemplo:

public interface TecladoListener extendes java.util.EventListener {

void teclaPulsada( EventoDeTeclado evento );

void teclaSoltada( EventoDeTeclado evento );

}

de forma que la clase que engarzaría al emisor y al receptor de eventos podría ser:

public class PruebaEventos implements TecladoListener {

public PruebaEventos() {

TextoCódigoFuente texto = new TextoCódigoFuente();

texto.addMyKeyListener( this );

}

// ahora se implementan los métodos del interfaz Listener

public void teclaPulsada( EventoDeTeclado e ) {…}

public void teclaSoltada( EventoDeTeclado e ) {…}

}

Hay que notar que el despacho de eventos es síncrono respecto del emisor de eventos, de forma que cuando desde el emisor se invoca un método en la clase receptora (listener), tal llamada es una invocación normal Java y se ejecuta síncronamente en la misma hebra del que llama.

Como el lector habrá podido intuir, los aspectos relacionados con el orden en el despacho de eventos desde la clase emisora (multicast, naturalmente, pues en otro caso no hay cuestión) dependen de su implementación.

Adaptadores

Un adaptador es una clase que implementa uno o más interfaces “listener” manejadores de eventos, y posee una importancia capital en el modelo de eventos de Beans.

 

Figura 3: Adaptadores de Eventos

 


El adaptador se interpone entre el emisor y el receptor de eventos para proveer comportamientos diferenciales sobre el modelo básico revisado anteriormente. Los adaptadores suelen ser usados como filtros de eventos, como soporte de implementación de mecanismos de colas de eventos, como gestores genéricos entre emisores y receptores y, por último y más frecuentemente, como desmultiplexores de varios emisores de eventos respecto de un único receptor (listener). Me explicaré: si un receptor se registra en muchos emisores de eventos, como sólo puede implementar un interfaz dado una sola vez, se encontrará con que tiene que ingeniárselas para averiguar quién es el emisor de cada evento en cada caso. Además usualmente interesa invocar métodos distintos en el receptor como respuesta a eventos del mismo tipo provenientes de distintos emisores (como por ejemplo el evento “PulsaciónDeBoton”, que si proviene de un botón de cancelación debería llamar a un método en el receptor del evento, mientras que si proviene de un botón de conformidad (OK) debiera llamar a un método distinto). La solución en este caso pasaría por crear una clase adaptadora para el evento a captar en razón del emisor. Así se crearía una clase “AdaptadorDeCancelación” y otra “AdaptadorDeConformidad”, y ambas -privadas- implementarían el interfaz, por ejemplo, “PushButtonListener”, pero de forma distinta, de manera que la captación del mismo evento daría lugar en cada adaptador a la invocación de un método distinto en la clase final receptora. Naturalmente nuestra clase originariamente única receptora (que podría ser un diálogo GUI) será la encargada de registrar como “listeners”, tal como se muestra en la Figura 3, a los dos adaptadores en cada uno de los botones emisores (en lugar de registrarse el diálogo mismo como receptor), de forma que cuando el botón de aceptar emita el evento de pulsación se notificará al “AdaptadorDeConformidad” y éste llamará al método adecuado en el Diálogo final. El problema de esta técnica es que el número de adaptadores puede aumentar vertiginosamente, pero existen otras técnicas que podrían solucionar este problema, aun a costa de complicar grandemente la legibilidad y comprensión del código: se trata de usar el paquete de “reflection” que se detalla en el epígrafe siguiente, de manera que podrían invocarse por un adaptador genérico distintos métodos, dependiendo del emisor del evento, al usarse el método “invoke” que en tiempo de ejecución puede realizar llamadas con argumentos de composición dinámica. La verdad es que esta técnica viene que ni pintada para los generadores automáticos de código, como por ejemplo los contenedores de Beans, ya que no resulta muy práctica para su codificación manual.

CONTINUARÁ …

En la próxima entrega terminaremos de revisar los APIs de Java Beans y veremos Propiedades, Introspección, Reflexión y Personalización (la verdad es este conjunto me rememora los ejercicios espirituales de San Ignacio de Loyola). También conoceremos las judías enlatadas y terminaremos con un postre muy dulce. Too Beans or not Too Beans? That is the reflection!

[1] Al decir de Brillat-Savarin, “La gastronomía es la preferencia apasionada, racional y habitual de cuantos objetos lisonjean el gusto”. El hecho de que cada vez más el software sea cuestión de horneados univianda debiera impeler a las personas sensibles a dedicarse a otros menesteres.

[2] Obviaré aquí las respuestas entusiastas de un cierto grupo de lectores y aclararé un malentendido: polimorfismo no es eso.

[3] “e x” o “ex” es el título que se les da a quienes fueron, o tuvieron ínfulas de ser, y actualmente no son, por lo que casi todo les importa un pimiento (¡Oh, es un ex-político arrojándose a un ex-tractor, mamá!)

[4] La adjetivación literaria de las llamadas “curvas de aprendizaje” se está convirtiendo en un lugar común en el que la multitud huele y todos los árboles son seculares. Respecto de la curva: es bueno si la aplastas, como también es beneficioso si la acortas, a la vez que resulta fantástico si la dominas: en fin, que el asunto del aprendizaje se basa en maltratar y doblegar a la dichosa e inocente curva. La analogía surge clara.

[5] Dado que el nombre se presta a la comparación fácil (The Sinking of the Titanic: El hundimiento del Titanic), me maravilla no encontrar todavía en la prensa informática amarilla referencias malévolas hacia esta nueva arquitectura. Quizás es que no hay mucho que objetar, a no ser que uno se queje del minimalismo conciliador, pero eso supondría atentar contra la obra musical homónima de Gavin Bryars. ¡Al paredón!

 
     
 
volver a la página de publicaciones
 
 
 Pº. Castellana 188, 14º e · 28046 - Madrid · info@a4devis.com