Programación en castellano
Inicio > Tutoriales > Lenguajes orientados a objeto > Java y XML > Integración de XML y los JavaBeans
-Tutoriales

Integración de XML y los JavaBeans


Integrar el Paquete XMLBeans en el Corazón Java

Si has experimentado con XML y JavaBeans en este tutorial, habrás notado que la clase XMLBeanWriter que creamos en la página anterior es capaz de crear ficheros XML desde XMLBeans que el XMLBeanReader de la primera página no podía leer. También habrás observado que el formato del fichero XMLBeans es algo complicado, y que controlar qué propiedades se codifican en XML y qué propiedades son ignoradas requiere mucha codificación aburrida. En esta página, solucionaremos estos problemas reestructurando las clases XMLBeanReader y XMLBeanWriter. En el proceso, simplificaremos el formato del fichero mientras mantenemos la compatibilidad hacia atrás. Quizás lo más interesante, simplificaremos y generalizaremos propiedades controladoras de XMLBeans integrando XMLBeans con el paquete básico java.beans.

. Lecturas Básicas

Está página cubre algunos tópicos intermedios-avanzados sobre JavaBeans, por eso podrías necesitar algunas lecturas básicas para conseguirlos rápidamente.

Primero, asuminos que has leído y entendido las dos páginas anteriores. Si no las has leído, está página no tendrá mucho sentido para tí.

. Mejorar XMLBeans

El código que vamos a discutir es una segunda tentativa de crear un interface para leer y escribir objetos JavaBeans como documentos XML. No gastaremos mucho tiempo en discutir las debilidades del diseño anterior, lo veremos según vayamos viendo que ésta nueva implementación es más limpia y más flexible que la original.

Este página presenta cuatro importantes mejoras al paquete XMLBeans. La primera implica la reestructuración y simplificación general del código, aprovechándonos del concepto de que el valor de cualquier propiedad JavaBean puede ser un valor primitivo, un objeto, u otro JavaBean. La segunda mejora es una extensión personalizada de cómo los XMLBeans pueden leer y escribir sus propiedades en XML (o, más exactamente, como árboles DOM). Tercero, le damos al programador el control sobre la visibilidad y el comportamiento de la propiedades integrando el paquete XMLBeans con el paquete básico java.beans. Y finalmente, proporcionamos una alternativa, un formato menos restrictivo (y menos bien definido) para el XML producido y leído por el paquete.

Empezaremos yendo sobre la mejora de la reestructuración general.

. Un Valor Mediante cualquier otro Nombre

La implementación original de XMLBeans dibujó una distinción entre las propiedades JavaBeans que son "valores" y las que son "beans". Los tipos de valor eran tipos primitivos de Java ( boolean, byte, char, short, int, long, float, o double) o cualquier tipo de objeto que no sea primitivo pero se pueda construir desde un String (java.net.URL, por ejemplo). Los tipos de bean fueron codificados explícitamente en XML como <JavaBean CLASS="classname">, y las propiedades cuyos valores eran beans fueron procesadas de forma diferente a los que no eran.

Después de escribir la primera versión del paquete XMLBeans, queda claro que no hay mucha diferencia entre un tipo de propiedad que sea un valor primitivo, uno que sea un objeto, o uno que sea un JavaBean. Los tipos primitivos requieren un tratamiento especial, éso es verdad, pero el proceso básico para leer un JavaBean en términos de sus propiedades es simple:

  • Crear un ejemplar del JavaBean. El nombre de la clase del JavaBean aparece en el Element que representa el bean.

  • Por cada property Element (un Element que representa una propiedad del bean):
    • Determinar la clase de la propiedad (consultándole al PropertyDescriptor por la propiedad)

    • Crear un ejemplar de la clase de la propiedad (llamaremos a esto el propoerty-value) ya que será el valor que tendrá la propiedad.

    • Inicializar el ejemplar property-value usando los contenidos de la propiedad Element

    • Fijar la propiedad usando el ejemplar property-value y el accesor property-write (el setter)

Por eso, el proceso de escribir un JavaBean es sencillo:

  • Por cada propiedad del bean:

    • Obtenermos el valor de la propiedad desde el bean llamando al método accesor lector de propiedades (el getter para la propiedad)

    • Convertimos el valor de la propiedad a un string

    • Añadimos un elemento que representa la propiedad y contiene su representación string en el árbol DOM que estamos creando

  • Cuando se han codificado todas las propiedades, escribimos el árbol DOM como XML.

Observaremos que, en lo descrito arriba, se hace referencia al valor de la propiedad sin indicar si el valor es un bean, un valor primitivo, o un ejemplar de alguna clase no-bean. En el código fuente (que discutiremos abajo), veremos que estos casos se manejan por separado y, en algunos casos, recursivamente. Pero la estructura básica del programa refleja el concepto de que el valor de una propiedad es simplemente un valor de propiedad, y las diferencias entre los distintos tipos de propiedades (valor, objeto, o bean) son sólo detalles de programación.

Ahora veremos la segunda mejora importante.

. Personalización

Puesto que XMLBeans es un paquete de utilidad, lo que significa que puede ser utilizado por un programador, muchas de las características del paquete son facilidades de programación, tales como funciones gancho que permitan que un programador modifique el comportamiento del paquete para requisitos particulares sin hacer que su operación sea violenta.

Un ejemplo de dicho gancho es el método getAsDOM(). En la Página anterior presentamos un método de la clase XMLBeanWriter para controlar cómo los tipos de propiedades específicos y los JavaBeans se codificaban como árboles DOM. El programador tiene la opción de especificar un método en una clase del bean llamado getAsDOM(). Cuando XMLBeanWriter encuentra un JavaBean con el método getAsDOM(), difiere al propio bean la capacidad para codificarse a sí mismo como un árbol DOM. Si una clase JavaBean no define un método getAsDOM(), el JavaBean se convierte a XML de la forma estándar: codificando todas sus propiedades en XML.

El método DocumentFragment getAsDOM(Document d) por definición devuelve un árbol DOM que representa su propio objeto. La clase XMLBeanWriter reconoce esta firma tanto para los beans como para las clases del tipo propiedad.

Como observamos arriba, si un JavaBean contiene un método llamado getAsDOM() (con la firma y el tipo de retorno apropiados), XMLBeanWriter llamará a ese método y devolverá lo que devolvió este método como la representación DOM del objeto. Si no existe getAsDOM(), XMLBeanWriter coloca todas las propiedades del bean, codificando cada propiedad como un árbol DOM. Todo árbol DOM que representa una propiedad se agrega a la lista de propiedades en el árbol DOM que representa el bean, hasta que se codifican todas las propiedades. Además de usar getAsDOM para codificar un bean entero, XMLBeanWriter siempre mira para ver si cada property type define getAsDOM() y, si lo hace, usa este método para codificar la property como subárbol DOM.

Por ejemplo, imagínemos que teníamos una clase de bean llamada CustomerAccount, representando la información de un cliente determinado en un sistema de ventas en línea. El diseñador de la clase CustomerAccount podría elegir especificar un método llamado getAsDOM(). Cuando se aplica a un objeto CustomerAccount, XMLBeanWriter lo detectará y llamará al método getAsDOM(). Entonces getAsDOM() devolverá la representación DOM personalizada del JavaBean CustomerAccount, en vez de buscar todas las propiedades de CustomerAccount.

Además, imagínemos que CustomerAccount tiene una propiedad llamada Address, del tipo CustomerAddress. Si CustomerAccount no tiene un método getAsDOM(), pero si lo tiene CustomerAddress, XMLBeanWriter codifica la propiedad Address llamando a CustomerAddress.getAsDOM().

Usar getAsDOM() en cualquiera de estos dos lugares le da al diseñador del bean mucho control sobre cómo se debe representar el bean cuando se convierte a XML. De hecho, en este punto el diseñador tiene demasiado control porque la versión existente de XMLBeanReader no sabe manejar XML genérico. Como al final de la página anterior, era posible escribir JavaBeans con XMLBeanWriter que no se podrían leer con XMLBeanReader.

Por supuesto, la nueva versión del paquete XMLBeans elimina esta limitación, permitiendo que la especificación de un método setAsDOM() se corresponda con cualquier método getAsDOM(). Igual que como XMLBeanWriter puede utilizar getAsDOM() para extraer la representación DOM de un bean (para escribir a un fichero XML), XMLBeanReader usa setAsDOM() para inicializar un valor de propiedad en un ejemplar del objeto bean desde la estructura de un sub-árbol DOM.

Para proporcionar más flexibilidad, un programador de XMLBeans puede definir getDOMSetterName() y/o getDOMGetterName(), que deben devolver el nombre de un método para usarlo en lugar de los nombres getAsDOM() o setAsDOM(). Esto permite más flexibilidad en el nombramiento, y también permite que el objeto decida en tiempo de ejecución qué método utilizar para la traducción a XML. Esto podría ser útil en un caso en el que pudiera haber varias representaciones XML distintas de un objeto, requiriendo varias formas de getAsDOM() y de setAsDOM(), y la aplicación necesita poder elegir la pareja de métodos de traducción en tiempo de ejecución.

Podemos recordar de la página anterior que no era fácil prevenir XMLBeanWriter para codificar propiedades. La única forma de hacer eso era escribir un método getAsDOM() que codificó todas las propiedades como un árbol DOM, incluyendo las propiedades no deseadas en la salida. Aunque esta técnica produce el resultado deseado, requiere mucha programación innecesaria. La tercera mejora principal al paquete XMLBeans soluciona este problema (y otros) integrando los XMLBeans con el paquete java.beans.

. Descriptores y Editores de Propiedades XML

Típicamente, un diseñador de JavaBean controla la visibilidad de las propiedades del bean a los agentes externos creando un objeto BeanInfo para el bean. Uno de los métodos del interface BeanInfo es PropertyDescriptor[ ] getPropertyDescriptors(), que devuelve un array de objetos PropertyDescriptor, uno por cada propiedad que defina el bean.

java.beans.Introspector, la clase responsable de analizar JavaBeans, identifica propiedades del bean intentando primero encontrar un objeto BeanInfo. Si encuentra uno, llama al método getPropertyDescriptors() de BeanInfo para obtener la lista de propiedades. Solamente si esta tentativa falla hace que el propio Introspector analize la clase para determinar propiedades.

Por ejemplo, imaginemos un JavaBean ThingForSale con tres propiedades: int weight, int age, y int price. Un programador podría ocultar el atributo price simplemente definiendo un método getPropertyDescriptors() en la clase BeanInfo para el bean ThingForSaleBeanInfo que devuelve objetos PropertyDescriptor sólo para age y weight.

Un PropertyDescriptor no sólo indica la propiedad, también los métodos accesores para dicha propieddad (métodos setter y getter). Como se describe arriba, cuando analizamos un bean, el Introspector primero chequea si puede obtener una lista de PropertyDescriptor desde el BeanInfo. Si esto falla, el Introspector analiza la clase bean, construyendo una lista de métodos que coincidan con las firmas Type getProperty;() y void; setProperty(Type value). Desde la lista de métodos, el Introspector crea su propia lista de PropertyDescriptor.

Si lo pensamos por un momento, debería estar claro que getAsDOM() y setAsDOM() en un JavaBean realmente podrían usarse como los métodos setter y getter para las propiedades del bean. En lugar de expresar las propiedades en términos de su tipo real de propiedad, estos dos métodos obtienen y fijan la propiedad en téminos de un árbol DOM. De hecho, toda propiedad del bean podría tener sus propios accesores setter y getter DOM para la propiedad cuyo valor sea un Element, y obtener el valor de la propiedad como un DocumentFragment, respectivamente. Incluso podemos definir una convención de nombres para ellos, como podemos hacerlos para los accesores de propidades normales:

  • Un método con la firma Type getPropertyAsDOM() define el "getter DOM" o el "método de lectura DOM para una propiedad Property de tipo Type".

  • Un método con la firma void setPropertyAsDOM() define el método "setter DOM" o el "método de escritura DOM para una propiedad Property del tipo Type.

Además, también podemos crear una nueva subclase de java.beans.PropertyDescriptor, llamada com.javaworld.JavaBeans.XMLBeans.XMLPropertyDescriptor, que permite al programador definir los descriptores de propiedades XML desde dentro del marco de trabajo BeanInfo exitente. Como un XMLPropertyDescriptor es un PropertyDescriptor, funcionará apropiadamente en cualquier papel de un PropertyDescriptor. Además, cuando XMLBeanReader o XMLBeanWriter quieran conocer unas propiedades de JavaBean, simplemente pueden obtener la lista de propiedades desde el BeanInfo del bean. Cualquier PropertyDescriptor que sea un instanceof XMLPropertyDescriptor contendrá un método "setter DOM" y/o un método "getter DOM" para usarlos en la conversión de propiedades desde y hacia la representación DOM.

Creando el nuevo tipo XMLPropertyDescriptor, permitimos a los desarrolladores de beans que especifiquen los métodos usados para acceder a las propiedades como árboles DOM. Si el desarrollador no proporciona un objeto BeanInfo, XMLBeanReader y XMLBeanWriter buscan los métodos que coincidan con las convenciones de nombrado descritas arriba.

La segunda forma en la que el nuevo paquete XMLBeans se integra con java.beans es usar los editores de propiedades para las conversiones de tipos. El interface java.beans.PropertyEditor define tres métodos de interés para nosotros:

  1. void setValue(Object o) selecciona los objetos a editar.

  2. void setAsText(String text) selecciona el valor del objeto actual, basándose en los contneidos del String.

  3. String getAsText() codifica el objeto actual como un String y devuelve el resultado.

Estos métodos nos interesan porque nos permiten convertir un objeto desde y hacia un String de una forma general, y en XML, todo se codifica como un String. Cuando XMLBeanReader determina que un string particular (el valor de un nodo Text) es una representación string de una propiedad, construye un valor del tipo de la propiedad. Por ejemplo si un JavaBean tiene una propiedad Date BirthDate, XMLBeanReader construirá un objeto Date para pasarlo al método setter de la propiedad BirthDate. Pero ¿Cómo convertir un String a un Date? Los objetos Date Java no tienen un constructor que acpeta un String como argumento. ¿Estámos perdidos?

Por supuesto que no. Si existe un DateEditor, o si el PropertyDescriptor para el objeto Date especifica un editor personalizado , XMLBeanReader simplemente crea un editor de propiedades de la clase apropiada, maneja el objeto Date recientemente creado, y llama a setAsText(). XMLBeanReader difiere la conversión de objetos desde y hacia una representación string al PropertyEditor de la propiedad, o (si la propiedad no tinen un editor personalizado), para el tipo de propiedad. Esta idea es extremadamente buena, porque le da al programador un completo control sobre el forma del string de cada propiedad, en una básica propiedad-por-propiedad. Un PropertyEditor personalizado podría estar escrito para codificar en base64 un objeto binario. Otro podría encriptar y desencriptar información sensible. La representación texto de una propiedad ahora está bajo el completo control del programador, y de una forma totalmente integrada con el corazón java.beans.

La mejora final de XMLBeans es una simplificación opcional del formato de fichero.

. Simplificador de XML

El formato de fichero para XMLBeans es preciso, pero no le permite fácilmente procesar XML general. El lenguaje XMLBeans original constaba de un elemento <JavaBean>, con un sólo atributo llamado CLASS. Dentro del elemento <JavaBean> había un elemento <Properties>, que a su vez contenía una lista de elementos <Property>. Cada elemento <Property> tenía un atributo NAME, y contenía un String, desde el que se podría construir la propiedad, u otro <JavaBean> (si el tipo de la propiedad era un JavaBean).

Es un lenguaje bastante sencillo. El DTD (data type definition) para este pequeño lenguaje aparece en el listado 1 de abajo:

<!ELEMENT JavaBean (Properties)>
<!ATTLIST JavaBean CLASS CDATA>
<!ELEMENT Properties (Property*)>
<!ELEMENT Property (#PCDATA)>
<!ATTLIST Property NAME CDATA>

Listado 1.

Este DTD le dice al analizador XML cómo chequerar el XML del JavaBean para corregirlo antes de intentar procesarlo. Si usamos un analizador validante con este DTD, podemos estar seguro de que cualquier árbol XML que leamos desde un fichero sigue las reglas de un XMLBean bien formateado.

El formato de fichero es un poco inflexible. Podríamos haber notado que nadie excepto nosotros usa este dialécto particular de "JavaBean XML." ¿Qué pasa si queremos analizar cualquier documento XML y convertirlo en un JavaBean?

El nuevo paquete XMLBeans hace esto. Mientras que el lenguaje JavaBean XML definido arriba todavía funciona, la clase XMLBeanReader ahora acepta cualquier XML bien-formateado, e intenta construir un bean de lo que encuentra. Hace esto asumiendo que:

  1. El elemento Element de más alto nivel de la entrada XML es un JavaBean, y que el nombre de la clase está en la etiequeta del elemento o en el valor del atributo CLASS del elemento, si existe.

  2. Todo Element contenido dentro del Element de más alto nivel representa una propiedad del bean, cuyo nombre se da en la etiqueta del elemento.

  3. Si una propiedad de elemento (de la asumción dos de arriba) contiene un nodo Text no-vacío, entonces el se puede construir el valor de la propiedad desde ese String.

  4. Si una propiedad de elemento (de la asumción dos) contiene otros Elements, entonces la propiedad es un JavaBean, y el valor es el resultado de cargar ese subárbol de elemento como un JavaBean

  5. La lista de propiedades podia estar encerrada opcionalmente en un elemento Properties, pero no necesariamente.

Por eso, por ejemplo, digamos que teníamos una clase JavaBean llamada Player (conocida de las páginas anteriores). Algunas de las propiedades son JavaBeans, que también tienen propiedades. Las clases JavaBean y las propiedades en nuestro ejemplo son las siguientes:

Clase Propiedad Tipo de
Propiedad
Comentarios
Player name class PersonName

 

Player stats classStatistics

 

Player number int

 

Player grades class Grades

 

PersonName first String

 

PersonName last String

 

Statistics year int

 

Statistics atbats int

 

Statistics runs int

 

Statistics hits int

 

Statistics homeruns int

 

Statistics runsbattedin int

 

Statistics strikeouts int

 

Grade grade(i) String propiedad indexada
Grade classDesc(i) String propiedad indexada

En nuestro dialecto XML original, usando el DTD en el Listado 1, las primeras líneas de un XMLBean se podrían parecer al Listado 2:

<JavaBean CLASS="Player">
  <Properties>
    <Property NAME="Name">
      <JavaBean CLASS="PersonName">
        <Properties>
          <Property NAME="First">Benjamin</Property>
          <Property NAME="Last">Hur</Property>
        </Properties>
      </JavaBean>
    </Property>
    <Property NAME="Number">12</Property>
    <!-- and so on... -->
  </Properties>
</JavaBean>

Listado 2.

Codificar la misma información en el nuevo formato es mucho más claro, como en el Listado 3:

<Player>
  <Name CLASS="PersonName">
    <First>Benjamin</First>
    <Last>Hur</Last>
  </Name>
  <Number>12</Number>
  <!-- and so on... -->
</Player>

Listado 3.

Observa que se han eliminado casi todas las marcas especificas del lenguaje específico XMLBeans. Lo único que queda es el atributo CLASS sobre la propiedad Name.

El listado 3 ejemplifica todas las siguientes reglas para la nueva síntaxis excepto la quinta:

  1. El elemento de más alto nivel es Player, por eso este objeto es un JavaBean de la clase Player. El nombre de la clase, Player, viene del nombre de la etiqueta.

  2. Player tiene las propiedades Name y Number (y probablemente algunas otras, no mostradas) ya que son elementos hijos del elemento Player.

  3. La propiedad Number contiene sólo un elemento de texto no-vacío, por eso su valor se crea usando el string "12" (y es convertido al tipo apropiado).

  4. La propiedad Name contiene otros Element s, por eso la propiedad Name es un JavaBean (cuya clase es PersonName, dada por el atributo CLASS).

  5. Todos los nodos dentro del nodo Player podrían haber sido encerrados en un nodo <Properties>.

Un par de detalles menores sobre esta nueva síntaxis merecen una pequeña explicación. Primero, ¿Por qué mantenemos el atributo CLASS? ¿Por qué no requerir sólo que el nombre de la etiqueta sea el nombre de la clase? Hay dos razones para esta decisión. Primero, el atributo CLASS opcional proporciona una identificación no ambigüa de la clase de la propiedad. ¿Qué pasa si hubiera dos o más clases llamadas Player en nuestro CLASSPATH? ¿Cuál debería elegir el XMLBeanReader? Segundo, y más importante, el atributo class puede usarse para identificar qué subclase usar si un tipo de propiedad es abstracto. ¿Qué pasaría si PersonName fuera una clase base abstracta, con subclases de MarriedName, MaidenName, y NickName? ¿Qué clase debería usar XMLBeanReader cuando cree un nuevo JavaBean?

El elemento <Properties> podría haber sido eliminado enteramente; y de hecho, es opcional. La razón por la que lo hemos dejado en la síntaxis es para futuras expansiones. Actualmente lo único almacenado sobre XMLBeans son las propiedades JavaBeans. En el futuro, podríamos querer almacenar conjuntos de eventos, datos cacheados, firmas digitales, u otra información sobre el bean que no está disponible desde las propiedades. El elemento <Properties> nos permite indicar qué subnodos del bean son propiedades. En este ejemplo, no lo necesitamos porque todo lo que realmente procesamos es la lista de propiedades.

Ya hemos descrito las mejoras a XMLBeans. Ahora veamos algunos ejemplos.

. Clases Beans de Ejemplo

Estos beans de ejemplo no forman parte del paquete XMLBeans; en vez de eso, se escribieron exclusivamente para demostrar cómo XMLBeans podría operar sobre beans reales. Puesto que los hemos modificado para demostrar cómo funciona la personalización de XMLBeans, pasaremos a través de las clases y describiremos lo que hacen, y qué características de personalización de XMLBeans utiliza. La clase bean Player del ejemplo representa a un miembro del equipo en un deporte de una High School; en este caso, béisbol (porque el objeto Statistics codifica la estadística de béisbol). El nombre de cada clase está enlazado a un fichero fuente para esa clase.

Además, hay ficheros XML de ejemplo, Bean1.xml y Bean2.xml, que muestran ejemplos de un bean Player codificado en el viejo y nuevo formatos XML. Podemos usarlos como entradas para los métodos main() de las clases XMLBeans.

Clase Player
La clase Player representa un jugador de béisbol. Tiene las siguientes propiedas:

  • PersonName name el nombre del jugador

  • Statistics stats las estadísticas del jugador para este deporte

  • int number el número del jugador en el equipo

  • String highSchool el nombre de la High School a la que asiste

  • Grades grades el expediente académico del jugador

Las propiedades name, stats, y grades son todas JavaBeans , la propiedad number es un valor primitivo, y highSchool es un objeto String (un objeto no primitivo ni JavaBean). Sólo existen para demostrar cómo XMLBeanWriter y XMLBeanReader codifican y decodifican cada tipo de propiedad.

Clase Statistics
Esta clase contiene las estadísticas de bésibol para el Player. Los nombres de las propiedades (year, atbats, runs, hits, homeruns, runsbattedin, y strikeouts) son todos del tipo int. Dos de ellas se manejan de forma especial:

  • hits Por ninguna razón en particular, hemos decidido codificar el número de hits que recibió el jugador en una sesión en formato hexadecimal. Las propiedades int nomalmente se codifican en decimal, pero getHitsAsDOM() y setHitsAsDOM() personalizan la codificación de la propiedad hits del bean Statistics. Estos dos métodos esencialmente permiten a la clase Statistics controlar cómo se codifica y decodifcia la propiedad hits; en este caso, codifica y decodifica en hexadecimal. Cuando XMLBeanReader selecciona hits en el bean que está creando, detecta la existencia de Statistics.setHitsAsDOM(), y le pasa el elemento correspondiente a ese método en lugar de llamar al "setter" estándar. Igualmente, cuando XMLBeanWriter crea una representación DOM de Statistics, nota que Statistics define getHitsAsDOM(), y difiere la codificación de hits como un árbol DOM a ese método.

  • strikeouts Para proteger la privacidad de un atleta adolescente, hemos decidido encriptar la propiedad strikeouts. Si mirámos el código observaremos que la propiedad strikeouts no tiene unos métodos accesores normales. En este sentido, no es realmante una propiedad!! Sin embargo, observaremos que el método print() del objeto muestra el número de strikeouts en su informe, por eso podemos ver que la cantidad fue correctamente decodificada. Los métodos getEncryptedStrikeouts y setEncryptedStrikeouts obtienen y seleccionan la propiedad como un árbol DOM, y encriptan y desencriptan la cantida, para que la gente extraña que lea el XML no sepa lo que significa el valor de la propiedad.
    ¿Cómo XMLBeanReader y XMLBeanWriter saben usar este método? Intentan obtener información de la clase StatisticsBeanInfo, que tienen un método llamado getPropertyDescriptors(). El descriptor de propiedades para la propiedad strikeouts es un XMLPropertyDescriptor. Luego, la XMLPropertyDescriptor especifica que el "setter" del DOM para strikeouts es setEncryptedStrikeouts(), y el "getter" DOM para strikeouts es getEncryptedStrikeouts. XMLBeanReader y XMLBeanWriter usan el descriptor de propiedad devuelto por getPropertyDescriptors para determinar cómo obtener y seleccionar las propiedades, y elige los accesores DOM sobre los accesores tradicionales cuando es posible.

Clase PersonName
La clase PersonName representa el nombre y los apellidos de una persona. No tiene personalización DOM y existe como un ejemplo de cómo XMLBeans lee y escribe objetos que no saben nada de XMLBeans.

Clase Grades
La clase Grades representa un expediénte académico de un Player. Particularmente son interesantes dos de sus propiedades classDesc y grade porque son propiedades indexadas, y XMLBeans actualmente no maneja propiedades indexadas. Para evitar esta limitación, Grades define getAsDOM() y setAsDOM(), que son totalmente responsables de codificiar y decodificar todo el bean Grades como XML. Hay una tercera propiedad, gradePointAverage, que es interesante porque es de sólo-lectura, y se calcula desde los valores de la lista de valores grade. Esta propiedad incluso no está encerrada en el XML, porque eso sería redundante. Simplemente usa los valores de la propiedad grade para calcular la nota al estilo Americano (en un escada de 0 a 4).

Clase PlayerBeanInfo
La clase PlayerBeanInfo especifica las propiedades XMLBeansPackage del bean Player. Extiende la clase XMLBeans XMLSimpleBeanInfo. Muestra un ejemplo de un objeto BeanInfo estándar que especifica una lista de propieades tradicionales (es decir, no-XMLBeans).

Clase StatisticsBeanInfo
Otra subclase de XMLSimpleBeanInfo, StatisticsBeanInfo especifica propiedades Statistics. Lo interesante de esta es que, en getPropertyDescriptors(), especifica un XMLPropertyDescriptor. Puede que otros contenedores JavaBeans distintos de XMLBeans, y XMLPropertyDescriptor se parezcan exactamente como un PropertyDescriptor, por esta simple razón este es uno (su subclase). Pero suministra información adicional (los accesores DOM) al XMLBeans.

Ahora que hemos visto lo que hace la nueva implementación, veamos cómo trabaja.

. Una Mirada al Código

El codigo de XMLBeanReader es el más díficil de los dos métodos del paquete, por eso lo veremos primero.

. XMLBeanReader

Veamos los métodos de XMLBeanReader El método main(), que no vamos a mostrar aquí, simplemente pasa un nombre de fichero a readXMLBean(), y luego imprime el JavaBean resultante llamando al método print() del objeto (si existe).

readXMLBean()
El método readXMLBean() viene en tres sabores, sólo uno hace realmente el trabajo (el que acepta un Reader como argumento). readXMLBean(), aparece en el listado 4, crea un analizador XML, lee el fichero XML de entrada, identifica el primer elemento del documento leído, y llama a instantiateBean() para crear un ejemplar del bean e inicializarlo. Este método analiza XML en un árbol DOM llamando a un analziador. Aquí usamos XML4J de IBM pero podríamos haber elegido otro analizador y el cambio sería muy sencillo.

816/** Read a bean's state from a character stream
817 * @param r The Reader from which to read the
       * JavaBean's state.
818* @return Object The newly-created, 
      * initialized JavaBean.
819* @exception IOException
820* @exception ClassNotFoundException
821* @exception IntrospectionException
822*/
823public static Object readXMLBean(Reader r)
824     throws IOException, ClassNotFoundException, 
            IntrospectionException {
825 
826     // Read document from XML file
827     String sParserClassname = "";
828     Parser parser = null;
829 
830     // Create a SAX parser
831     try {
832         parser = ParserFactory.makeParser("com.ibm.xml.parsers.NonValidatingDOMParser");
833     } catch (Exception exc) {
834         System.err.println("Exception");
835         exc.printStackTrace();
836     }
837
838     P("Created parser");
839 
840     // Run the SAX parser against the input stream
841     try {
842         parser.parse(new org.xml.sax.InputSource(r));
843      } catch (SAXException sx) {
844         System.err.println("Exception: "  + sx.toString());
845         sx.printStackTrace();
846     } catch (Exception ex) {
847         System.err.println("Threw " + ex.getClass().getName());
848         ex.printStackTrace();
849     }
850
851     // Since we know the SAX parser is also 
           // a DOM parser,
852     // we can ask it for its resulting document.
853     Document d = ((NonValidatingDOMParser)parser).getDocument();
854     P("Got document " + isNull(d));
855 
856     Element eJavaBean = d.getDocumentElement();
857     P("eJavaBean is " + isNull(eJavaBean));
858
859     Object o = instantiateBean(eJavaBean);
860 
861     return o;
862 }

Listado 4.

instantiateBean()
El método instantiateBean() (listado 5) simplemente obtiene el nombre de la clase del Bean y luego carga el bean como si fuera un valor de propiedad y otro bean llamando a loadValue(), el método real del paquete.

477/** Instantiate the JavaBean, and set all of 
       * its properties
478 * The element must be of type "JavaBean"
479 * @param eJavaBean The DOM Element object 
       * representing the JavaBean.
480 * It is the root of a DOM document tree that 
       * contains the information
481 * used to instantiate and initialize the 
       * JavaBean. The element's
482 * tag must be JavaBean.
483 * @return java.lang.Object
484 * @exception IOException
485 * @exception ClassNotFoundException
486 * @exception IntrospectionException
487 */
488 protected static Object instantiateBean( Element eJavaBean)
489     throws IOException, ClassNotFoundException, 
            IntrospectionException {
490 
491     // Load the value from the node
492     Object jb = loadValue(classOfBean,  eJavaBean, null);
494 
495     // Return the newly-instantiated JavaBean. 
496     return jb;
497 }

Listado 5.

loadValue()
El método loadValue() (Listado 6) es responsable de cargar todas las propiedades del JavaBean cuya clase recibe. Siempre recibe la clase del objeto a crear y un Element qiue es la raíz de un subárbol DOM que codifica el valor. También hay un PropertyDescriptor que podría contener información sobre el valor y cómo seleccionarlo. Si el valor a devolver no es una propiedad de cualquier otro objeto (que es el caso sólo si es el JavaBean de más alto nivel que está siendo codificado, ver la llamada en instantiateBean() arriba), el PropertyDescriptor es null.

565 /**
566  * Load a value from an element node. The
        * resulting value will either be a
567  * JavaBean or a primitive wrapper object;
        * in either case, the result is almost
568  * always passed to a setter method. If the PropertyDescriptor is not null,
569  * then this object is a property of another
        * object, and the descriptor describes it.
570  * @return boolean
571  * @param bean java.lang.Object
572  * @param node org.w3c.dom.Node
573  */
574 public static Object loadValue( Class objectType, Element element, PropertyDescriptor pd) {
575     Object value = null;
576     Pe("Loading element " + element.getTagName()  + " for object "  + objectType.getName());
577 
578     // If this is a primitive property, we want to
           // return an object
579     // of the appropriate type for the property
           // descriptor's setter method
580     if (pd != null) {
581         if (objectType.isPrimitive()) {
582             value = getPrimitive(objectType, element);
583             return value;
584         }
585     }
586 
587     // Create an instance of this bean. This 
           // will be our return value.
588     try {
589         value = Beans.instantiate(null,  objectType.getName());
590     } catch (Exception ex) {
591         return null;
592     }
593 
594     // First, find out if this object knows how to
           // read itself
595     // from a DOM tree. If it does, we simply
           // defer to the object
596     // itself, and we have no further work to do.
597     Method domSetter = getDOMSetter(value, null);
598     if (domSetter != null) {
599         try {
600             domSetter.invoke(value, new Object[] {element});
601         } catch (Exception ex) {
602             Pe("loadValue:");
603             Pe(ex.getClass().getName());
604             ex.printStackTrace();
605         }
606         return value;
607     }
608 
609     // Get the first child of the element that
           // is either nonwhite text (in
610     // which case we try to initialize the
           // value from that); or a noncomment
611     // nonterminal node, in which case the value
           // must be a bean.
612     Node n =Util.getFirstInterestingChild(element);
613 
614     // If the syntax is <
           // Property NAME="foo"><
           // JavaBean CLASS="bar">...,
615     // then we want to look under the <
           // JavaBean> node for properties,
616     // not under the <Property> node.
617     Element topElement = element;
618     if (n instanceof Element && ((Element)n).getTagName().equals("JavaBean")) {
619         topElement = (Element)n;
620     }
621 
622     // If n is null, initialize value with a
           // blank string.
623     // If n is Text, initialize value with the 
           // value of the text.
624     String scalarValue = null;
625     if (n == null) {
626         scalarValue = "";
627     } else {
628         if (n instanceof Text) {
629             scalarValue = ((Text) n).getData();
630         }
631     }
632 
633     // If scalarValue is not null, then we want to
           // set the value from
634     // a string. This makes it easy for objects to
           // simply serialize
635     // themselves to flat strings, and then
           // deserialize themselves
636     // back from those strings. Essentially, the
           // PropertyEditor works
637     // as a tiny parser for the string value.
638     if (scalarValue != null && pd != null) {
639         // Use the property editor to initialize
               // the value.
640         if ((value = loadValue(objectType, scalarValue, pd)) != null) {
641             return value;
642         }
643         Pe("loadValue() failed for property " + pd.getName() + ", value = '" + scalarValue + "'");
644     }
645 
646     // Since the object didn't know how to set
           // itself from a DOM,
647     // and the DOM that represents it contains
           // multiple nodes, we're
648     // going to have to enumerate all of the
           // properties. Assume that
649     // each subnode is a property, creating and
           // initializing a value for
650     // each one, and then using the property 
           // setter to set that value.
651     // If a <Properties> node exists in my
           // subnodes, that's the list of
652     // my properties, instead of all
           // of my subnodes.
653     Node propertyList = topElement;
654     NodeList nl = topElement.getChildNodes();
655     int i;
656     for (i = 0; i < nl.getLength(); i++) {
657         if (nl.item(i) instanceof Element) {
658             Element e = (Element) (nl.item(i));
659             if (e.getTagName().equals("Properties")) {
660                 propertyList = e;
661                 break;
662             }
663         }
664     }
665 
666     // Introspect object to get properties, then
           // create a hash table
667     // with property names as keys and descriptors
           // as contents
668     // Use the introspector to get the property
           // descriptors
669     // for this bean.
670     PropertyDescriptor[] pds = getPropertyDescriptors(value.getClass());
671 
672     // Create a hash table of property names
           // and property descriptors
673     Hashtable htpd = new Hashtable();
674     for (i = 0; i < pds.length; i++) {
675         htpd.put(pds[i].getName().toLowerCase(), pds[i]);
676     }
677 
678     // Now iterate through all of the properties
           // in the propertyList,
679     // loading each one recursively, and
           // passing the returned value
680     // to the setter method.
681     nl = propertyList.getChildNodes();
682     for (i = 0; i < nl.getLength(); i++) {
683 
684         // Get the name of the property,
               // ignoring rubbish
685         String propertyName =getPropertyName(nl.item(i));
686         if (propertyName == null)
687             continue;
688 
689         p("Property name = '"  + propertyName + "'");
690 
691         // Must be an element, since it wouldn't 
               // have a propertyName if not
692         Element e = (Element) (nl.item(i));
693 
694         // Get the property descriptor for the property,
               // ignoring
695         // if there's no descriptor for it
696         PropertyDescriptor thispd = (PropertyDescriptor) (htpd.get(propertyName.toLowerCase()));
697         if (thispd == null) {
698             Pe("Couldn't get PropertyDescriptor for property " + propertyName);
699             continue;
700         }
701 
702         // See if either the property descriptor or 
               // the bean knows how
703         // to set the value as a DOM tree
704         Method mDOMSetter = getDOMSetter(value, thispd);
705 
706         if (mDOMSetter != null) {
707             P(" DOM Setter = "+ mDOMSetter.getName());
708             try {
709                 mDOMSetter.invoke(value, new Object[] {e});
710                 continue;
711             } catch (Exception ee) {
712                 Pe("Exception occurred while setting " + propertyName + " as DOM: " + ee.getMessage());
713                 ee.printStackTrace();
714                 return null;
715             }
716         } else {
717             p(", no DOM setter");
718         }
719 
720         // Call the setter directly. To do this, 
               // we have to get the setter method for
721         // the property, create an instance of its 
               // property type, and
722         // try to recursively loadValue() a value for it.
723         // That should handle both properties that are
               // JavaBeans
724         // and properties that can somehow be 
               // constructed from a string.
725         Method mSetter = thispd.getWriteMethod();
726         if (mSetter != null) {
727             P(", Setter " + mSetter.getName());
728             try {
729                 // Get the class of the property
730                 Class cValue = getPropertyType(e, thispd);
731                 Object setterArg = null;
732 
733                 // The setter argument is the object 
                       // referred to by the
734                 // current element e. Calling loadValue() 
                       // recursively here
735                 // returns the argument for the setter.
736                 if ((setterArg = loadValue(cValue, e, thispd)) != null) {
737                     mSetter.invoke(value, new Object[] {setterArg});
738                 } else {
739                     Pe("Couldn't set " + cValue.getName() + " " + thispd.getName());
740                 }
741             } catch (Exception ex) {
742                 Pe("Couldn't create or set "  + thispd.getPropertyType().getName() 
                             + " for property " + pd.getName());
743                 ex.printStackTrace();
744             }
745         } else {
746             P(", Setter null");
747         }
748     }
749 
750     // Return the value we've just created.
751     return value;
752 }

Listado 6.

Refierendose al código, loadValue() realiza las siguientes tareas:

Líneas Descripción
578-585 getPrimitive() codifica una propiedad primitiva (véase abajo). Una propiedad que no sea primitiva debe tener un constructor por defecto, así que usamos java.beans.Beans.instantiate() para crear un ejemplar para el resultado. (Usar este método no es enteramente correcto, puesto que el resultado puede ser un objeto no-bean. El código debe controlar si la clase del resultado es un bean o no, y usar new si no lo es.
588-591 Crear el "value" para ser incializado desde Element. El valor es simplemente un ejemplar del tipo de retorno.
594-607 Si el objeto conoce cómo inicializarse a sí mismo desde un árbol DOM (es decir, si la clase valor define setAsDOM()), simplemente pasa el árbol DOM al método "setter" DOM y se crea el Bean. El método getDOMSetter() devuelve un objeto Method que representa al "setter" DOM para la propiedad, o null si no se pudo encontrar ninguno. Si se encontró un "setter" DOM, esta sección lo llama y retorna. getDOMSetter() (ver el fichero fuente) encuentra un método de una de estas formas: (1) si pd es un XMLPropertyDescriptor, devuelve el "setter" DOM desde el descriptor (llamando al método getDOMWriteMethod() del descriptor); o (2) si la clase del valor define getPropertynameAsDOM(), se devuelve ese método.
609-620 Los contenidos del nodo valor podrían ser una de dos cosas. Podia ser una simple cadena de texto, en cuyo caso, el valor de la propiedad podría construirse desde un string como este: <Size>12</Size> o, podrían ser otros nodos Element definiendo una lista de propiedades, como esta: <Size>12</Size> <Color>Red</Color> Los espacios en blanco mostrados en la entrada crean nodos Text que contiene espacios en blanco, y también podrían ser nodos Comment que son saltados en el XML . getFirstInterestingChild() es un método de utilidad que encuentra el primer hijo que es un nodo Text no vacío, o cualquier otro tipo de nodo que no sea Comment.
622-644 Si el objeto que estamos creando va a ser inicializado desde un nodo Text (es decir, un String), llamamos desde otra forma de loadValue() que está especializada en la inicialización de un objeto desde un String:
666-752 Este código es el método estándar para crear XMLBean en ausencia de cualquier personalización. El objeto que está siendo creado es inspeccionado para conseguir una lista de sus propiedades, y entonces se crea una tabla que indexa los nombres de propiedades contra sus correspondientes descriptores. (Estas son las propiedades del valor creado.) Puesto que cada hijo del Element actual es una propiedad del valor que es siendo creado, el código busca entre todos los elementos hijos de este nodo, obteniendo un valor para cada uno (mediante una llamada recursiva a loadValue()), y después fijándolo con el método "setter" de esa propiedad, llamado sobre el ejemplar que estamos creando. Es un poco confuso al principio. Simplemente estamos llamando a loadValue() recursivamente para conseguir el valor de cada propiedad de valor que está siendo creada actualmente. El bean Player, por ejemplo, necesita llamar a loadValue() para conseguir los valores para sus propiedades stats, grades y name, y utiliza loadValue() para hacerlo.

loadValue(), de nuevo
Las líneas 526-563 en XMLBeanReader, sólo concierne a la creacción de aquellos valores que pueden ser construidos desde objeto String. Crea un nuevo ejemplar de la clase valor, usa el PropertyEditor de la clase valor para inicializar el valor, y devolver el resultado.

getPrimitive()
Los tipos primitivos son un poco complicados porque necesitan una clase envoltura para pasarlos a un objeto Method (que es nuestra única forma de acceder al "setter"). getPrimitive() acepta el objeto Class primitivo y construye un objeto envoltura apropiado usando el String del árbol DOM para inicializar el valor. La envoltura la crea getPrimitiveSetterArg(), que es interesante si queremos ver cómo construir un objeto cuando incluso no conocemos la clase del objeto en el momento de la ejecución: simplemente obtenemos el constructor y lo llamamos!

Los otros métodos de la clase XMLBeanReader son simples utilidades. Entendiendo la primera forma de loadValue(), no tendremos problemas en entender cómo funciona el resto de la clase.

. XMLBeanWriter

XMLBeanWriter crea un árbol DOM que representa un JavaBean. El árbol DOM resultante puede luego ser escrito en un fichero XML. XMLBeanWriter es más simple que XMLBeanReader. El único método que hace algún trabajo interesante es getAsDOM(), que viene en dos formas: una para un bean, y otra para las propiedades de un bean.

getAsDOM()
Listado 7 contiene el método getAsDOM() usado para beans enteros. La otra versión de getAsDOM() devuelve un DocumentFragment que codifica una propiedad y es usado por la primera versión. En el listado 8 podrás encontrar una descripción de la segunda forma de getAsDOM().

022 /**
023  * Build a DOM DocumentFragment representing 
        * the class and properties of
024  * a JavaBean.
025  * @return org.w3c.dom.DocumentFragment - 
        * the document representing the JavaBean.
026  * @param doc Creates nodes using this 
        * document as a factory.
027  * @param bean The JavaBean for which we 
        * want the DOM tree
028  * @exception java.beans.IntrospectionException 
        * An error occurred during
029 * introspection of the JavaBean.
030 */
031 public static DocumentFragment getAsDOM(Document doc, Object bean) throws 
                IntrospectionException, InstantiationException,  IllegalAccessException {
032 
033 	// Create the fragment we'll return
034 	DocumentFragment dfResult = null;
035 
036 	// Analyze the bean
037 	Class classOfBean = bean.getClass();
038 
039 	// See if the bean has a custom name for its 
              // DOM setter
040 	String nameOfGetter = "getAsDOM";
041 	try {
042 	Method mNameGetter = classOfBean.getMethod("getDOMGetterName",new Class[] {});
043        nameOfGetter = (String) (mNameGetter.invoke(bean, new Object[] {}));
044 	} catch (Exception ex) {
045 	; // Ignore exceptionsthis 
              // either works or it doesn't
046 	}
047 
048 	// If the bean knows how to encode itself 
              // in XML, then
049 	// use the DOM document it returns.
050 	Method mGetAsDOM = null;
051 	try {
052 	mGetAsDOM =classOfBean.getMethod(nameOfGetter,new Class[] {org.w3c.dom.Document.class });
053 		if (mGetAsDOM != null) {
054 			dfResult =(DocumentFragment) (mGetAsDOM.invoke(bean,new Object[] {doc}));
055 			return dfResult;
056 		}
057 	} catch (Exception e) {
058 		; // Ignore exceptions
059 	}
060 
061     // If we found a DOM read method, invoke it,
           // and we're done.
062     if (mGetAsDOM != null) {
063         try {
064             dfResult = (DocumentFragment) mGetAsDOM.invoke(bean, new Object[] {doc});
065             return dfResult;
066         } catch (Exception e) {
067 	; // Ignore exceptions
068 	}
069}
070 
071     // Get a BeanInfo for the bean.
072     BeanInfo bi =  Introspector.getBeanInfo(classOfBean);
073 
074     // If the bean doesn't know how to encode
           // itself in XML,
075     // then create a DOM document by
           // introspecting the bean and
076     // inserting nodes that represent the
           // bean's properties.
077     if (dfResult == null) {
078         dfResult = doc.createDocumentFragment();
079         PropertyDescriptor[] pds = bi.getPropertyDescriptors();
080 
081         // Add an Element indicating that this
               // is a JavaBean.
082         // The element has a single attribute, 
               // which is that Element's class.
083         Element eBean =  doc.createElement("JavaBean");
084         dfResult.appendChild(eBean);
085         eBean.setAttribute("CLASS", classOfBean.getName());
086         Element eProperties = doc.createElement("Properties");
087         eBean.appendChild(eProperties);
088 
089         // For each property of the bean, get a
               // DocumentFragment that
090         // represents the individual property.
               // Append that DocumentFragment
091         // to the Properties element of the document
092         for (int i = 0; i < pds.length; i++) {
093             PropertyDescriptor pd = pds[i];
094             DocumentFragment df = getAsDOM(doc, bean, pd);
095             if (df != null) {
096 	        // Create a Property element and add to
                      // Properties element
097 	        Element eProperty =doc.createElement("Property");
098                eProperties.appendChild(eProperty);
099 
100 	        // Create NAME attribute, add it to
                      //Property element,
101 	        // and set it to name of property
102 	        eProperty.setAttribute("NAME", pd.getName());
103 
104                 // Append the DocumentFragment to the
                       //Property element
105 	         // This "splices" the entire
                       // DOM representation of the
106 	         // Property into the tree at this point.
107 	        eProperty.appendChild(df);
108 	        }
109         }
110     }
111     return dfResult;
112 }

Listado 7.

La versión bean de getAsDOM() codifica un JavaBean completo como un DocumentFragment DOM.

Líneas Descripción
33-69 getAsDOM() primero obtiene la clase del bean y determina si esa clase define un método personalizado que codifique el bean como un árbol DOM. Busca un método cuyo nombre sea getAsDOM, a menos que la clase defina un método String getDOMGetterName(), en cuyo caso este método busca un método con ese nombre. (Por favor, no confundamos el método que vamos aquí, XMLBeanWriter.getAsDOM(), y el método que la clase del bean define para codificarse como un árbol DOM.) Si se encuentra un método "getter" DOM, se le llama, y lo que devuelve es el valor de retorno de este bean. Esto permite que un programador de beans tome un completo control sobre cómo la clase del bean se representa como un árbol DOM (y, por lo tanto, como un documento XML).
74-110 Si el bean no sabe cómo codificarse a sí mismo como un árbol DOM, getAsDOM codifica el bean creando un Element que representa el bean, y añadiendo los subnodos que representan las propiedades del bean. La obtención de las propiedades del bean como árbol DOM implica la lectura de todas las propiedades del bean, la codificación de cada propiedad como un subárbol DOM (representado por un DocumentFragment), y luego la inserción del subárbol de la propiedad en la lista de propiedades del bean. La línea 72 obtiene el objeto BeanInfo para el JavaBean desde el Introspector, y la línea 79 obtiene la lista de PropertyDescriptors que describe las propiedades del bean. Las líneas 83 a 87 creean el nodo superior del árbol DOM resultante. Las líneas 92 a 110 pasan a través de los PropertyDescriptor del bean, creando un Element por cada propiedad, codificando la propiedad del JavaBean como un subárbol (llamando a la segunda forma de getAsDOM(), e insertando el nuevo Element en la sección <Properties> del árbol resultante. Cuando se hayan codificado y añadido todas las propiedades al árbol, el árbol resultante vuelve a llamador en la forma de un objeto DocumentFragment (línea 111).

Esto es todo lo que hace la forma del bean getAsDOM(). La forma del getAsDOM() que codifica propiedades aparece en el listado 8:

113 /**
114  * Return a DOM DocumentFragment representing a
        * property of a JavaBean
115  * @return org.w3c.dom.DocumentFragment
116  * @param doc The document to use as a factory for
117  * subelements of the tree.
118  * @param bean The object that is the value of 
        * the property.
119  * This object must be a JavaBean.
120  * @param pd A property descriptor describing
121  * the property of which the object is a value.
122  * @exception IllegalAccessException
123  * @exception InstantiationException
124  * @exception IntrospectionException
125  */
126  public static DocumentFragment getAsDOM(Document doc, Object bean, 
                    PropertyDescriptor pd) throws IntrospectionException, 
                    InstantiationException, IllegalAccessException {
127 	Class classOfBean = bean.getClass();
128 	Class classOfProperty = pd.getPropertyType();
129 	DocumentFragment dfResult = null;
130 	String sValueAsText = null;
131 	Class[] paramsNone = {};
132 	Object[] argsNone = {};
133 
134 	// If the property is "class," and
              // the type is java.lang.class, then
135 	// this is the class of the bean, which 
              // we've already encoded.
136 	// So, in this special case, return null.
137 	if (pd.getName().equals("class") && classOfProperty.equals(java.lang.Class.class)) {
138 		return null;
139 	}
140 
141      // 0. If pd is an XML property descriptor, and
            // we can call its
142      // getter method, do so and return. If the
            // programmer
143     // specifies a DOM getter method, we return
           // what it returns, no questions asked.
144     if (pd instanceof XMLPropertyDescriptor) {
145         Method getter;
146         if ((getter = ((XMLPropertyDescriptor) pd).getDOMReadMethod()) != null) {
147 	    try {
148 	        Class[] params =  {org.w3c.dom.Document.class};
149 	        Object[] args = {doc};
150 	        dfResult = (DocumentFragment) (getter.invoke(bean, args));
151 	    } catch (Exception ee) {
152 	        ; // Ignore... couldn't get the method
153 	    }
154 	    return dfResult;
155 	 }
156     }
157 
158     // 1. Try to represent the property as XML.
159     // This bean may know how to describe itself,
           // or parts of
160     // itself, as XML. There are two possibilities:
161     // [a] The bean has a method called 
           // get<Propname>AsDOM()
162     // [b] The property class has a method called 
           // getAsDOM()
163     // We'll try both of these, and the first 
           // (if any) that
164     // works will be the DocumentFragment we want
           // to return.
165     // If none of these are true, then we try to
           // find the object's
166     // value as text
167 
168     // [1a] Does the bean have a method called
           // get<Propname>AsDOM()?
169     // Capitalize property name
170     StringBuffer sPropname = new StringBuffer(pd.getName());
171     char c = sPropname.charAt(0);
172     if (c >= 'a' && c <= 'z') {
173 		c += 'A' - 'a';
174     }
175     sPropname.setCharAt(0, c);
176     String sXMLGetterName = "get" + sPropname + "AsDOM";
177 
178     // If both of these methods succeed, then
           // dfResult will be set
179     // to non-null; that is, the method existed
           // and returned a
180     // DocumentFragment
181     try {
182         Class[] params = {org.w3c.dom.Document.class};
183         Method mXMLGetter = classOfBean.getMethod(sXMLGetterName, params);
184 	Object[] args = {doc};
185 	dfResult = (DocumentFragment) (mXMLGetter.invoke(bean, args));
186     } catch (Exception ee) {
187 	 ; // Ignore... couldn't get the method
188     }
189 
190     // Hereafter, we're trying to create a 
           // representation of the property
191     // based somehow on the property's value.
192     // The very first thing we need to do is get 
           // the value of the
193     // property as an object. If we can't do that,
           // we can get no
194     // representation of the property at all.
195     Object oPropertyValue = null;
196     try {
197         Method getter = pd.getReadMethod();
198         if (getter != null) {
199             oPropertyValue = getter.invoke(bean, argsNone);
200 	}
201     } catch (InvocationTargetException ex) {
202 	 ; // Couldn't get value. Probably 
                // should be an error.
203     }
204 
205     // [1b] If we don't have a DocumentFragment,
           // the previous block failed.
206     // So, let's find out if the property's 
           // class has a method called
207     // getAsDOM() and, if it does, call that instead.
208     if (dfResult == null) {
209         try {
210             Class[] params = {org.w3c.dom.Document.class};
211             Method mXMLGetter = classOfProperty.getMethod("getAsDOM", params);
212             Object[] args = {doc};
213             dfResult = (DocumentFragment) (mXMLGetter.invoke(oPropertyValue, args));
214         } catch (Exception ee) {
215 	    ; // Ignorewho cares why it failed?
216 	 }
217     }
218 
219 
220     // 2. Try to represent the property as a String.
221     // See if this property's value
222     // is something we can represent as Text, or if
           // it's something
223     // that must be represented as a JavaBean. 
           // Let's assume that this
224     // object can be represented as text if:
225     // [a] it has a PropertyEditor associated with
           // it, because
226     // PropertyEditors always have setAsText() and
           // getAsText()
227     // If it can't be represented as text, then 
           // we pass it to
228     // getAsDOM(Document, Object) and return 
           // the result.
229 
230     if (dfResult == null) {
231 
232         // [2a] Can we get either a custom or 
               // built-in property editor?
233         // If the PropertyDescriptor returns an 
               // editor class, we
234         // create an instance of it; otherwise, 
               // we ask the system for
235         // a default editor for that class.
236         Class pedClass = pd.getPropertyEditorClass();
237         PropertyEditor propEditor = null;
238         if (pedClass != null) {
239             propEditor = (PropertyEditor) (pedClass.newInstance());
240         } else {
241 	    propEditor = PropertyEditorManager.findEditor(classOfProperty);
242 	}
243 
244         // If the property editor's not null, pass
               // the property's
245         // value to the PropertyEditor, and then ask 
               // the PropertyEditor
246         // for a text representation of the object.
247         if (propEditor != null) {
248             propEditor.setValue(oPropertyValue);
249 	    sValueAsText = propEditor.getAsText();
250         }
251 
252         // If somewhere above we found a string value,
               // then create
253         // a DocumentFragment to return, and append 
               // to it
254         // a Text element.
255         if (sValueAsText != null) {
256             dfResult = doc.createDocumentFragment();
257 	    Text textValue = doc.createTextNode(sValueAsText);
258 	    dfResult.appendChild(textValue);
259         }
260     }
261 
262     // 3. Try to represent the property value as a
           // JavaBean
263     // If we don't have a DocumentFragment yet, we'll
264     // have to introspect the value of the object, because
265     // it's apparently something that can't be
           // represented
266     // as flat text. We'll assume it's a JavaBean.
267     // If it isn't... oh, well.
268     //
269     if (dfResult == null && oPropertyValue != null) {
270         dfResult = getAsDOM(doc, oPropertyValue);
271     }
272     return dfResult;
273 }

Listado 8.
Líneas Descripción
127-128 El método primero obtiene la clase del bean, y la clase de la propiedad dentro del bean.
137-139 Algunas veces, el Class class muestra una lista de propiedades, en cuyo caso es ignorada.
141-156 Si el PropertyDescriptor también es un XMLPropertyDescriptor, usamos el método read del DOM indicado por el XMLPropertyDescriptor para codificar la propiedad como un árbol DOM.
158-188 Si la clase bean define un método llamado getPropertynameAsDOM(), se utiliza un método para recuperar la propiedad como un árbol DOM.
190-203 Desde la línea 190, la única forma en la que podemos obtener una representación DOM de una propiedad es obteniendo el valor de la propiedad, formatear ese valor como un String y embeber ese String dentro de un elemento <Property>. Las líneas 190 a 203 recuperan el valor de la propiedad llamando al método "getter" tradicional de la propiedad indicado por el PropertyDescriptor.
205-217 Si la clase property tiene un método llamando getAsDOM(), llamamos a ese método sobre el valor que acabamos de recuperar, y el resultado será la propiedad codificada como un árbol DOM. Observa que esto es diferente a pedirle a la clase bean que codifique la propiedad como un árbol DOM.
220-260 Si aún no hemos tenido éxito en la creacción de un DocumentFragment para devolverlo, entonces tenemos que crear el nodo <Property> e insertarlo dentro del valor de la propiedad codificado como un String. Utilizamos el PropertyEditor del tipo de la propiedad para realizar esta conversión.
262-271 Si todo esto falla, usamos la primera verisión de getAsDOM() para codificar la propiedad como un JavaBean. Observa que esto significa que las dos versiones de getAsDOM() son mutuamente recursivas. Al final de esta sección de código, el DocumentFragment resultante representa la propiedad devuelta al llamador.

. Otras Clases XMLBeans

Estas clases en el paquete XMLBeans las usan los programadores que quieren añadir funcionalidades XMLBeans a sus clases.

Clase XMLSimpleBeanInfo
La clase XMLSimpleBeanInfo existe sólo para corregir un bug en la implementación que IBM hace de java.beans.SimpleBeanInfo (que ha sucedido cuando lo usamos para desarrollar el código de esta página). XMLSimpleBeanInfo podría concebirse para extenderse y reportar más información sobre un XMLBean al paquete XMLBeans, pero hacer esto requiere el re-desarrollo (o al menos subclasificación) de java.beans.Introspector, que podría ser un tópico demasiado largo para esta página.

Clase XMLPropertyDescriptor
La clase XMLPropertyDescriptor, es un descriptor de propiedad XML, extiende java.beans.PropertyDescriptor permitiendo al programador especificar los métodos "setter" y "getter" DOM para cada propiedad: Al igual que con los objetos PropertyDescriptor tradicionales, los métodos podrían especifcarse por su nombre, o como objetos Method. XMLBeans busca los accesores DOM, y los usa preferiblemente sobre los accesores tradicionales, proporcionando, por lo tanto, la personalización XMLBeans descrita arriba. Esta clase probablemente podría haberse hecho más pequeña teniendo el introspector en la clase bean, buscando por getAsDOM(), etc., en lugar de mantener la lógica en las clases XMLBeanReader y XMLBeanWriter; pero de nuevo, esto requeriría subclasificar Introspector.

Clase Util
La clase Util contiene un conjunto de clases sencillas para codificaciones sencillas. Ninguna es lo suficientemene complicada como para merecer una explicación aquí.

. Trabajo Posterior

XMLBeanReader y XMLBeanWriter son simplemente un punto de partida adicional para las posibilidades existentes de experimentación después de todo lo leído hasta aquí.

Como hemos mencionado en estas páginas, este paquete no maneja propiedades indexadas de una forma general, aunque las propiedades indexadas específicas se pueden soportar si las codificamos a mano, como demuestra el ejemplo de Grades.

Hay algunas cosas menores que necesitamos añadir a este paquete para poder hacerlo real. En particular, el paquete XMLBeans no codifica los caracteres (<, > y &) como debería. Cada vez que se crea un elemento Text los datos añadidos al elemento Text deberíamos primero reemplazar cada caracter de mayor que (>) con la secuencia &gt;, cada caracter de menor que (<) con &lt;, y cada caracter ampersand con &amp;. Esta tradución también necesitamos hacerla a la inversa cuando leemos datos desde elementos Text. Estas codificaciones son requirimientos duros-y-rápidos de XML, y son la mayor debilidad de este paquete en su implementación actual.

Otra modificación necesaria (quizas menor) es un cambio en el código que maneja secciones CDATA, que son formas especiales de incluir grandes bloques de texto pre-formateado en un fichero XML. Las secciones CDATA deberían permitirse en cualquier lugar en el que se permita un elemento Text.

Una última forma para mejorar el paquete sería modificar el paquete XMLBeanWriter para crear ficheros XMLBeans usando el nuevo formato XMLBeans, en lugar de usar el viejo formato

Esta implementación de XML JavaBeans realmente se precipita un poco cuando intenta convertir un objeto a un String. La última cosa que el XMLBeanWriter debería hacer es comprobar si el objeto implements Serializable. Si lo hace, XMLBeanWriter podría serializar el objeto a un array de bytes, y luego codificar el array como un string BDC o base64. La operación inversa tendría que ser realizada mientras se lee el JavaBean desde XML.

 
Patrocinados
 

Copyright © 1999-2007 Programación en castellano. Todos los derechos reservados.
Formulario de Contacto - Datos legales - Publicidad
Mantenida por: Claudio y Dani.

Hospedaje web y servidores dedicados linux por Ferca Network

red internet: jugar gratis | amor | navidad 2009 | registro de dominios | servidores dedicados
más internet: comprar | gratis | posicionamiento en buscadores | decoración libre | gifs animados