Programación en castellano
Inicio > Tutoriales > Java y XML > El API JAXB
-Tutoriales

El API JAXB


Unir un Esquema a las Clases

Este capítulo demuestra cómo usar JAXB para unir un DTD a un conjunto de clases Java. Para generar la clases, realizamos estos pasos:

  1. Escribir un esquema de unión, que contiene las instrucciones de cómo unir un DTD a las clases.
  2. Ejecutar el compilador de esquema con el DTD y el esquema de unión como entradas para generar el código fuente.
  3. Compilar el código fuente para generar las clases.

Los dos capítulos siguientes muestran cómo utilizar las clases para construir representaciones de datos desde documentos XML y trabajar con los datos. El ejemplo que estos capítulos usan es la sencilla aplicación de libro de cheques que se describió en el escenario 1: Balancear un Libro de Cheques. Con esta aplicación, podremos registrar transacciones en un libro de cheques y determinar el balance de la cuenta controlada.

El ejemplo checkbook usa el checkook.dtd, que está localizado en el directorio examples/checkbook de nuestra instalación de JAXP. La primera sección de este capítulo explica el DTD checkbook.

. El DTD de Ejemplo: "checkook.dtd"

Antes de crear la aplicación checkbook, deberíamos entender el DTD en el que está basada. Esta sección describe brevemente el checkook.dtd mostrado aquí:

<!ELEMENT checkbook ( transactions, balance ) > 
<!ELEMENT transactions ( deposit | check | withdrawal )* > 
<!ELEMENT deposit ( date, name, amount )> 
<!ATTLIST deposit category ( salary | interest-income | other ) #IMPLIED > 
<!ELEMENT check ( date, name, amount, ( pending | void | cleared ), memo? ) > 
<!ATTLIST check number CDATA #REQUIRED category ( rent | groceries | other ) #IMPLIED > 
<!ELEMENT withdrawal ( date, amount ) > 
<!ELEMENT balance (#PCDATA) > 
<!ELEMENT date (#PCDATA ) > 
<!ELEMENT name (#PCDATA) > 
<!ELEMENT amount (#PCDATA) > 
<!ELEMENT memo (#PCDATA) > 
<!ELEMENT pending EMPTY > 
<!ELEMENT void EMPTY > 
<!ELEMENT cleared EMPTY >

Un documento XML debe tener exactamente un elemento de raíz, pero el documento puede seleccionar que elemento utilizar como su elemento raíz del DTD. Para nuestro ejemplo del checkbook, tanto checkbook como transactions se pueden utilizar como elementos raíz por documentos XML de este ejemplar de DTD. La sección Crear el Esquema de Unión Mínimo Requerido muestra cómo especificar en el esquema de unión qué elementos de un documento XML válido pueden utilizarse como elementos raíz. Según el DTD, un checkbook contiene transacciones que se hacen contra la cuenta y un balance que representa la cantidad de dinero que la cuenta contiene. El conjunto de transacciones, según la definición del elemento de las transacciones, contiene cero o más depósitos, cheques, o reintegros.

Una transación deposit consta de:

  • La fecha en que se hizo.
  • El nombre de la persona o empresa que proporcionó el dinero para el depósito.
  • La cantidad depositada.

El elemento deposit también tiene un atributo, llamado category, que describe la razón del depósito.

Una transación check consta de :

  • La fecha en la que se firmo el cheque.
  • El nombre de la persona o empresa que recibe el cheque.
  • La cantidad del cheque.
  • El estado del cheque: cleared, void, o pending (todavía no se ha cobrado).
  • Un memo opcional.

El check también tiene dos atributos: number y category. El atributo number representa el número de cheque. El atributo category representa el motivo por el que se ha extendido el cheque.

La transación withdrawal (reintegro) consta sólo de la fecha en la que se hizo y la cantidad reintegrada.

Los elementos balance , date , name , amount , y memo se han definido para tener contenido de carácter. Este capítulo muestra cómo generar varios tipos para el contenido de los elementos balance, data y amount.

Los elementos pending , void , y cleared no tienen contenido, por lo que se han definido como EMPTY.

. Escribir el Esquema de Unión

No necesitamos proporcionar una declaración de unión para cada componente del DTD. El compilador de esquema asume declaraciones de uniones por defecto si no se proporciona una unión particular. Sin embargo, si no estámos satisfechos con las uniones por defecto, necesitamos proporcionar declaraciones de unión en el esquema de unión para generar las clases que deseamos.

Esta sección nos muestra:

  1. Cómo escribir el esquema de unión mínimo, que es un esquema de unión que contiene las declaraciones mínimas que permitirán al compilador de esquema generar las clases.
  2. Qué código generará el compilador de esquema basándose en el esquema de unión mínimo y el DTD.
  3. Cómo añadir declaraciones al esquema de unión mínimo para que el compilador de esquema genere el código que queremos, no sólo el código por defecto.

Esta guía explica detalladamente cómo escribir el esquema de unión. El esquema de unión es lo que utilizamos para controlar qué tipo de código genera el compilador de esquema. Por lo tanto, es importante que entendamos cómo el compilador de esquema interpreta las declaraciones que proporcionamos en el esquema de unión y lo que el compilador de esquema asume si no proporcionamos las declaraciones de unión para una declaración del DTD. Además, el lenguaje de unión y el compilador de esquema que interpreta el esquema de unión son los suficientemente proderosos como para hacer asunciones razonables, pero todavía nos permiten mucha flexibilidad para definir cómo el DTD está unido a las clases.

Aunque el compilador del esquema puede producir una unión razonable en el caso simple, cuando veamos qué clase de código genera el compilador de esquema basandose en el DTD checkbook y un esquema de unión mínimo, entenderemos la importancia de proporcionar declaraciones de unión de modo que podamos generar el código apropiado para nuestra aplicación.

. Crear el Esquema de Unión Mínimo Requerido

Tanto si deseamos o no validar las declaraciones de unión por defecto asumidas por el compilador de esquema, necesitamos proporcionar un esquema de unión. Para crear el esquema de unión para el DTD del checkbook:

  1. Creamos un nuevo fichero de texto llamado checkook.xjs.
  2. En checkook.xjs, escribimos:
    <xml-java-binding-schema version="1.0ea">
    

    Esta etiqueta identifica el fichero como un esquema de unión.

  3. Todos los esquemas de unión deben declarar al menos un elemento raíz. En nuestro ejemplo, queremos declarar dos elementos raíz: checkbook y transactions. Para declarar los elementos raiz, escribimos:
    <element name=checkbook type=class root=true />
    <element name=transactions type=class root=true />
    

    Para declarar estos elementos raíz, utilizamos la declaración de unión del elemento para unir un tipo de elemento a una clase. El valor del atributo debe ser el nombre del elemento tal como aparece en el DTD. El valor del tipo de atributo es class en este caso porque éstos son elementos raíz y se deben limitar a las clases. El atributo root debe ser igual a true porque queremos que los ejemplares del documento XML del DTD checkbook pudieran declarar checkbook o transactions como elementos raíz.

  4. Introducimos la etiqueta final para el elemento xml-java-binding-schema:
    </xml-java-binding-schema>
    

    Ahora tenemos un esquema de unión legal desde el cual el compilador de esquema puede generar clases. De acuerdo con el DTD, el compilador de esquema hace asunciones con respecto a cómo unir las otras declaraciones del DTD para los cuales no proporcionamos declaraciones de unión. La siguiente sección explica las declaraciones de unión asumidas por el compilador de esquema basándose en el DTD checkbook y el esquema de unión actual. Una vez que entendamos qué tipo de código se produce desde un esquema de unión mínimo, la escritura el resto del esquema de unión es fácil.

. Entender las Declaraciones por Defecto

Las declaraciones de unión que el compilador de esquema asume basándose en el DTD checkbook y el esquema de unión mínimo son:

<element name="checkbook" type="class" root=true> 
    <content> 
        <element-ref name="transactions"/> 
        <element-ref name="balance"/>
    </content>
</element> 
<element name="transactions" type="class" root=true> 
    <content property="content"/>
</element> 
<element name="deposit" type="class"> 
    <attribute name="category"/>
    <content> 
        <element-ref name="date"/> 
        <element-ref name="name"/> 
        <element-ref name="amount"/>
    </content>
</element> 
<element name="check" type="class"> 
    <attribute name="number"/> 
    <attribute name="category"/> 
    <content property="content"/>
</element> 
<element name="withdrawal" type="class"> 
    <content> 
        <element-ref name="date"/> 
        <element-ref name="amount"/>
    </content>
</element> 
<element name="balance" type="value"/> 
<element name="date" type="value"/> 
<element name="name" type="value"/> 
<element name="amount" type="value"/> 
<element name="memo" type="value"/> 
<element name="pending" type="class"/> 
<element name="void" type="class"/> 
<element name="cleared" type="class"/>

El resto de esta sección explica cada una de estas declaraciones de union.

. Declaración de Unión de Elementos

El compilador de esquema asume diversas declaraciones de unión para los elementos dependiendo de qué tipo de contenido tienen o si tienen atributos. Para los elementos simples, que tienen solamente el contenido del tipo carácter y ningun atributo, el compilador de esquema asume que los elementos están unidos a las propiedades dentro de la clase de su elemento padre. Los elementos balance, date, name y amount son elementos simples y por eso el compilador de esquema asume estas uniones para ellos:

<element name="balance" type="value"/> 
<element name="date" type="value"/> 
<element name="name" type="value"/> 
<element name="amount" type="value"/> 
<element name="memo" type="value"/>

El nombre de los atributos debe ser el nombre del elemento tal como aparece en el DTD. El tipo del atributo es value en estos casos porque estos elementos están unidos a propiedades, no a clases. Éstas declaraciones de unión harán que compilador de esquema genere estas propiedades:

String getBalance(); 
void setBalance(String x); 
String getDate(); 
void setDate(String x); 
String getName(); 
void setName(String x); 
String getAmount(); 
void setAmount(String x);
String getMemo(); 
void setMemo(String x);

Una propiedad String está muy bien para los elementos name y memo. Sin embargo, para balance y amount, necesitamos un tipo que represente mejor valores de moneda. De forma similar date se debe limitar a una propiedad que acepte y devuelva una cierta clase de tipo que represente mejor una fecha. La sección Especificar Tipos mostrará cómo generar las propiedades que aceptan y devuelven diferentes tipos.

Para el resto de los tipos de elementos, el compilador de esquema asume que los elementos están unidos a clases. Si un elemento contiene cualquier cosa distinta de contenido de caracteres o tiene atributos, el compilador de esquema lo unirá a una clase. Los elementos deposit, check, y withdrawal tienen elementos de contenido, y deposit y check tiene atributos. Estos elementos también están unidos a clases. Las uniones por defecto para estos elementos son:

<element name="deposit" type="class" > 
<element name="check" type="class" > 
<element name="withdrawal" type="class" >

Observa que no necesitamos especificar el atributo root como hicimos en la sección Crear el Esquema de Unión Mínimo Requerido. El compilador de esquema asume que el valor de este atributo es false. Sólo necesitamos especificar el valor de este atributo como true si queremos el ejemplar del documento XML tenga la posibilidad de usar ese elemento como elemento raíz. De la declaración de unión del elemento deposit el compilador de esquema genera esta definición de clase y este constructor:

public class Deposit extends MarshallableObject { 
    public void Deposit();

Las clases Check , y Withdrawal se parecerán mucho a ésta.

Los elementos pending , void , y cleared tienen contenido EMPTY y están includos en un modelo de grupo de elección, y por eso están unidos a clases, como se especifica en sus declaraciones de tipos de atributos:

<element name="pending" type="class"/> 
<element name="void" type="class"/> 
<element name="cleared" type="class"/>

. Declaración de Unión de Atributos

El DTD checkbook define tres atributos. El elemento deposit tiene un atributo category, y el elemento check tiene un atributo number y también un atributo category:

<!ELEMENT deposit ... 
<!ATTLIST deposit category ( salary | interest-income | other ) #IMPLIED > 
<!ELEMENT check ... 
<!ATTLIST check number CDATA #REQUIRED category ( rent | groceries | other ) #IMPLIED >

Todos estos atributos toman valores atómicos, en vez de valores compuestos, y por eso el compilador de esquema asume que estos atributos están unidos a propiedades String, como se especifica en esta declaraciones de unión por defecto:

<element name=deposit type=class > 
    <attribute name=category />
... 
<element name="check" type="class"> <attribute name=number />
    <attribute name=category />
...

Dentro de la clase Deposit, el compilador de esquema genera esta propiedad para representar el atributo category:

void setCategory(String x); 
String getCategory();

Dentro de la clase Check, el compilador de esquema genera estas propiedades para representar a los atributos number y category:

void setNumber(String x); 
String getNumber(); 
void setCategory(String x); 
String getCategory();

Observa que la propiedad number acepta y devuelve un String. La sección Especificar Tipos mostrará cómo modificar el esquema para requisitos de uniones particulares de modo que el compilador de esquema genere una propiedad number que acepte y devuelva un int. Asimismo, la sección Crear Tipos Enumerados mostrará cómo generar un tipo enumerado para la propiedad category.

. Declaración de Unión de Contenido

El declaración de unión de contenido es lo más complicado, reflejando el número infinito de maneras en que podemos especificar el contenido XML, pero JAXB lo hace fácil para nosotros. El tipo más común de modelo de contenido es una secuencia simple, no-repetitiva, por ejemplo (a, b, c, d). Si utilizamos este tipo de modelo de contenido, muy probablemente no necesitaremos especificar un declaración de unión para él porque el compilador de esquema genera una propiedad separada para cada elemento de la secuencia, que es generalmente lo que deseamos.

La mayoría de los elementos que hay en checkook.dtd tienen contenido secuencial simple y no repetitivo. Para estos elementos, el compilador de esquema asume las declaraciones de unión mostradas en negrita:

<element name="checkbook" type="class" root=true> 
    <content>
        <element-ref name="transactions"/>
        <element-ref name="balance"/>
    </content> 
</element> 
<element name="deposit" type="class"> 
    <attribute name="category"/> 
    <content>
        <element-ref name="date"/>
        <element-ref name="name"/>
        <element-ref name="amount"/>
    </content> 
</element> 
<element name="withdrawal" type="class"> 
    <content>
        <element-ref name="date"/>
        <element-ref name="amount"/>
    </content> 
</element>

El declaración de unión element-ref se utiliza para unir un ejemplar de un elemento en un modelo de contenido a una propiedad en la clase del elemento padre. La declaración del elemento que corresponde al ejemplar del elemento une el elemento a sí mismo, incluyendo la unión del elemento a su tipo. Por defecto, el compilador de esquema genera propiedades String desde todas las declaraciones de uniones element-ref que se refieran a elementos simples. La propiedad por defecto para el elemento name es:

public class Deposit { 
    ... 
    String getName();
    void setName(String x);

El compilador de esquema también genera una propiedad String para balance, date, y amount. La clase Checkbook contiene una propiedad para el elemento balance. Las clases Deposit y Withdrawal tienen propiedades para los elementos date y amount. En la sección Especificar Tipos veremos que los tipos de estas propiedades cambian cuando utilizamos el atributo convert en las declaraciones de unión correspondientes al elemento.

Como especificamos que el elemento transactions está unido a una clase, la declaración de unión element-ref para el elemento transactions hará que el compilador de esquema genere esta propiedad en el clase Checkbook:

Transactions getTransactions(); 
void setTransactions(Transactions x);

Si un elemento contiene algo distinto a una secuencia simple no repetitiva, el compilador de esquema asumirá la declaración de unión de la propiedad general-content, que es:

<content property=content />

Los elementos transactions y check no tienen secuencias simples y no repetitivas como contenidos, por eso el compilador de esquema asume estas declaraciones de unión para ellos:

<element name="transactions" type="class" root=true> 
    <content property="content"/> 
</element> 
... 
<element name="check" type="class"> 
    <attribute name="number"/> 
    <attribute name="category"/> 
    <content property="content"/> 
</element>

Se usa la declaración general-content para unir un modelode grupo completo, incluyendo modelos de grupos anidados, a una propiedad. Este declaración no es muy útil para nuestros propósitos porque deseamos tener acceso a los elementos individuales en estos modelos de contenido. Esta declaración es útil para definir uniones más flexibles si anticipamos que nuestro DTD cambiará en el futuro. Para más información sobre esta declaración, puedes ver la sección Manejar la Evolución del Esquema.

Desde estas declaraciones de unión, el compilador de esquema genera esta propiedad en las clases Transactions y Check:

List getContent(); 
void emptyContent(); 
void deleteContent();

El método getContent devuelve una lista modificable que contiene el valor actual de las propiedades. El método emptyContent descarta los valores de la lista y crea una nueva lista, vacía. El método deleteContent borra la lista.

. Personalizar el Esquema de Unión

Ahora que entendemos las declaraciones de unión que el compilador del esquema asumirá basándose en el DTD y el esquema de unión mínimo, es fácil escribir el esquema de unión: Todo lo que necesitamos escribir son las declaraciones de uniones que no son asumidas por el compilador de esquema. Esta sección explica cada una de las personalizaciones para requisitos particulares que podemos hacer al esquema de unión para conseguir las clases que deseamos.

El esquema de unión que usaremos para la aplicación checkbook es:

<xml-java-binding-schema version="1.0ea"> 
<element name="checkbook" type="class" root="true" /> 
<element name="transactions" type="class" root="true"> 
    <content> 
        <choice property="entries" collection="list" supertype="Entry" /> 
    </content> 
</element> 
<element name="balance" type="value" convert="BigDecimal"/> 
<element name="amount" type="value" convert="BigDecimal" /> 
<element name="date" type="value" convert="TransDate" /> 
<element name="deposit" type="class" > 
    <attribute name="category" convert="DepCategory" /> 
</element> 
<element name="check" type="class" > 
    <content> 
        <element-ref name="date"/> 
        <element-ref name="name" /> 
        <element-ref name="amount" /> 
        <choice property="pend-void-clrd"/> 
    </content> 
    <attribute name="number" convert="int" /> 
    <attribute name="category" convert="CheckCategory" />
</element> 
<conversion name="BigDecimal" type="java.math.BigDecimal" /> 
<conversion name="TransDate" type="java.util.Date" parse="TransDate.parseDate" 
				print="TransDate.printDate" /> 
<enumeration name="DepCategory" members="salary interest-income other"/> 
<enumeration name="CheckCategory" members="rent groceries other"/> 
<interface name="Entry" members="Deposit Check Withdrawal" properties="date amount" /> 
</xml-java-binding-schema>

Esta sección nos lleva a través de este esquema de unión como si lo hubieramos escrito desde él principio. Ya hemos incorporado las uniones del elemento de raíz en la sección Crear el Esquema de Unión Mínimo Requerido, por eso empecemos con ella:

  1. Reemplazamos la etiqueta de elemento vacío (/>) con un angulo a la derecha (>) y añadimos una etiqueta final para la declaración de elemento raíz transactions porque le vamos a añadir declaraciones de contenido:
    <xml-java-binding-schema version="1.0ea">
        <element name="checkbook" type="class" root="true" />
        <element name="transactions" type="class" root="true" > 
        </element> 
    
  2. Antes de que empezar a escribir la declaración de unión personalizada, las primeras declaraciones que necesitamos en nuestro esquema de unión son las declaraciones de unión de elemento para los elementos deposit y check porque especificaremos tipos para sus atributos y personalizaremos el modelo de contenido del elemento check . El compilador de esquema necesita estas declaraciones para poder generar las propiedads para los atributos y el contenido de elección en la clase correcta y para los elementos correctos.

    Detntro del elemeno raíz de nuestro esquema de unión, después de las declariones de unión de elementos raíces, introducimos:

    <element name=deposit type=class >
    
    </element>
    ...
    <element name=check type=class >
    
    </element>
    

    Más tarde añadiremos el atributo personalizado y las declaraciones de unión de contenidos dentro de estos elementos.

. Especificar Tipos

Por defecto, el compilador de esquema genera métodos get que devuelven un String y métodos set que aceptan un String para todos los elementos y atributos simples. Por ejemplo, consideremos estas declaraciones de unión por defecto:

<element-ref name=amount /> 
... 
<element name=amo unt type=value />

Desde estas declaraciones de unión, el compilador de esquema genera estos dos métodos:

public String getAmount(); 
public void setAmount(String amount);

Si deseamos realizar algunos cálculos con amount, necesitamos convertirla de un String a un algún otro tipo que permita que lo utilicemos en un cálculos. Para los cálculos que implican valores de moneda, el tipo BigDecimal es una buena opción porque representa números decimales con signo de precisión arbitraría, y la clase BigDecimal proporciona los métodos para aritmética básica.

Para especificar un tipo, utilizamos el declaración de conversión para definir la conversión y el atributo convert de la declaración de elemento o de atributo, dependiendo de si estámos convirtiendo el tipo de una propiedad de elemento o de una propiedad de atributo, para referenciar la declaración de conversión.

Especificar Tipos No Primitivos

Para definir una conversión de String a BigDecimal:

  1. Añadimos esta declaración de conversión en cualquier lugar en el nivel superior (entre de las etiquetas <xml-java-binding-schema version="1.0ea">) de nuestro esquema de unión, quizás después de la declaración de unión del elemento check:
    <conversion name=BigDecimal type=java.math.BigDecimal />
    

    Cualquier declaración de unión de elemento o de atributo que utilice esta conversión se refiere a ella por el nombre BigDecimal, según lo especificado por el nombre del atributo. El valor del tipo de atributo es el tipo real al cual se convierte una propiedad.

  2. Para instruir al compilador de esquema para que genere una propiedad amount con un tipo BigDecimal:
    • Añadimos una declaración de unión de elemento para el elemento amount en la parte superior de nuestro esquema de unión:
      <element name=amount type=value />
      
    • Añadimos un atributo convert a la unión del elemento amount y seleccionamos su valor a BigDecimal:
      <element name=amount type=value convert=BigDecimal />
      

    Estas declaraciones de unión producen estás firmas de métodos:

    public java.math.BigDecimal getAmount(); 
    public void setAmount(java.math.BigDecimal amount);
    

    Declarar por separado la conversión de una unión de elemento nos permite reutilizar una conversión con otras uniones de elemento. Podemos hacer esto con el elemento balance, que también necesita estar unido a una propiedad BigDecimal.

    Para instruir al compilador de esquema para que genere una propiedad balance con un tipo BigDecimal:

    1. >Añadimos una declaración de unión de elemento para el elemento amount en la parte superior de nuestro esquema de unión, y asignamos BigDecimal a su atributo convert:
      <element name=balance type=value convert=BigDecimal />
      

    También podemos convertir el tipo de la propiedad del elemento date a un java.util.Date. Puesto que una fecha se puede escribir de muchas formas distintas, necesitamos especificar cómo se debe analizar la fecha cuando se desempaquete y cómo debe imprimirse cuando se empaquete. La declaración de conversión incluye los atributos parse y print para este propósito.

    Para convertir los elementos date a un java.util.Date:

    1. Añadimos esta declaración de conversión al esquema de unión:
      <conversion name=TransDate type=java.util.Date
          parse=TransDate.parseDate print=TransDate.printDate/>
      

      El nombre TransDate se refiere a una clase Java que necesitamos proporcionar. Esta clase contiene un método estático parseDate que especifica cómo analizar la fecha y un método estático printDate que especifica cómo imprimir la fecha.

  3. Para instruir al compilador de esquema para que genere una propiedad date con la clase TransDate, añadimos un atributo convert a la declaración de unión del elemento date y seleccionamos su valor a TransDate:
    <element name=date type=value convert=TransDate />
    

    Estas declaraciones de unión producen estas firmas de métodos:

    public java.util.Date getDate(); 
    public void setDate(java.util.Date x);
    

En el ejemplo de conversión BigDecimal, no necesitamos especificar un método de análisis o de impresión porque la clase java.math.BigDecimal especifica un constructor que acepta un String y devuelve un BigDecimal y un método toString que acepta un BigDecimal y devuelve un String. Para aplicar la conversión, el compilador de esquema genera código para invocar al constructor y al método toString.

Especificar Tipos Primitivos

Cuando especificaamos tipos primitivos, tales como int, no necesitamos proporcionar una declaración de unión de conversión separada; simplemente añadimos el atributo convert a la declaración de unión del elemento o del atributo y asignamos el valor al tipo primitivo.

Dentro de la declaración de unión del elemento check, añadimos una declaración de unión de atributo para el atributo number y especificamos un tipo int para su propiedad:

<element name=check type=class > 
<attribute name=number convert=int /> 
</element>

. Crear Tipos Enumerados

El atributo convert también se puede utilizar para especificar un tipo enumerado. En el lenguaje de programación Java, representamos tipos enumerados con un tipo seguro enum, que es una clase cuyos ejemplares representan un conjunto fijo de valores.

Un atributo cuyo valor sólo se pueda fijar a uno de un conjunto fijo de valores es un buen candidato para un tipo enumerado. El DTD checkbook tiene dos de estos atributos: el atributo category del elemento deposit y el atributo category del elemento check:

<!ATTLIST deposit ... category ( salary | interest-income | other ) #IMPLIED >

<!ATTLIST check ... category ( rent | groceries | other ) #IMPLIED >

Para generar los tipos para estos atributos:

  1. Introducimos dos etiqueutas enumeration para cada conversión en el nivel superior del esquema de unión, quizás detrás de la declaración de conversión de TransDate:
    <conversion name=TransDate ...
    <enumeration
    <enumeration
    
  2. Para el atributo name de las etiquetas enumeration, necesitamos un nombre único para cada enumeración porque ámbas enumeraciones están al mismo nivel en el esquema de unión. Introducimos DepCategory para la categoria de deposit y CheckCategory para la categoría de check.
    <enumeration name="DepCategory"
    <enumeration name=CheckCategory
    

    Estos nombres serán los nombres de las clases para representar los tipos seguros enums.

  3. Añadimos un atributo members a cada declaración de enumeración, y les asignamos los posibles valores de cada atributo:
    <enumeration name="DepCategory" members=salary interest-income other />
    <enumeration name=CheckCategory members=rent groceries other />
    
  4. Añadimos las declaraciones de unión de attribute dentro de las declaraciones de unión de los elementos deposit y check y asignamos el nombre de la declaración de enumeración apropiada a cada atrributo convert de la declaración de unión de attribute:
    <element name=deposit ...
        <attribute name=category convert=DepCategory />
    ...
    <element name=check ...
        <attribute name=category convert=CheckCategory />
    

    El compilador de esquema generará esta clase desde la unión del atributo category de check:

    public final class CheckCategory { 
        public final static CheckCategory RENT; 
        public final static CheckCategory GROCERIES; 
        public final static CheckCategory OTHER; 
        public static CheckCategory parse(String x); 
        public String toString(); 
    }
    

    Veremos como trabajar con esta clase en el capítulo Construir Representaciones de Datos.

. Personalizar el Modelo de Declaración de Unión de Contenido

Los modelos de contenido pueden ser muy complejos, y por eso el lenguaje de unión define muchas declaraciones diferentes de uniones para manejar diversos tipos de modelos de contenido. Definimos estas uniones con la declaración de unión de contenido.

Podemos utilizar la declaración de unión de contenido para definir dos tipos de declaraciones de modelo de contenido: la propiedad general-content y la propiedad de cotenido basado en modelos. Una propiedad general-content se utiliza para unir un modelo de contenido completoo a una propiedad. Esta declaración no se utiliza para unir cualquier cosa en el DTD checkbook, pero es útil para definir uniones más flexibles si nos anticipamos a que el DTD cambiará en el futuro. Para más información sobre esta declaración, puedes ir a la sección Manejar la Evolución del Esquema.

La declaración de propiedad de contenido basado en modelo puede contener cuatro tipos de declaraciones para especificar diferentes tipos de uniones de grupos de modelo:

  • element-ref, que especifica la unión de un ejemplar element dentro de otros elementos content. El constructor del elemento especifica la unión del propio elemento.
  • choice, que especifica la unión de un grupo de modelo de elección anidado a una propiedad.
  • sequence, que especifica la unión de un grupo de modelo de elección anidado a una propiedad.
  • rest, que es una declaración de unión más flexible que podemos utilizar para especificar cualquier tipo de contenido. Puedes econtrar más información en la sección Manejar la Evolución del Esquema.

Recordamos de la sección Entender las Declaraciones de Unión por Defecto que el compilador de esquema asume una declaración de unión de la propiedad general-content para los contenidos de los elemento check y transactions porque estos elementos no tienen modelos de contenido simples, con secuencias no repetitivas. En su lugar, estos elementos tienen grupos de modelos de elección en sus declaraciones de contenido:

<!ELEMENT transactions ( deposit | check | withdrawal )* > 
... 
<!ELEMENT check ( date, name, amount, ( pending | void | cleared ), memo? ) > 
...

Esta sección muestra cómo utilizar la declaración de unión de elección para hacer que el compilador de esquema genere propiedads más útiles para el contenido de estos elementos. En el caso del contenido del elemento transactions, deseamos asignar el grupo entero a una propiedad de elección. Para especificar la unión del contenido del elemento transactions:

  1. Dentro de la declaración de unión del elemento transactions, introducimos una etiqueta content y una declaración de unión choice, y asignamos el valor entries al atributo property:
    <element name=transactions type=class root=true >
        <content>
            <choice property=entries
    

    El compilador de esquema generará una propiedad llamada Entries. Por ejemplo, el método get se llamará getEntries. La razón por la que utilizamos el nombre entries, es porque el nombre se refiere a un declaración de interface, que la sección Crear Interfaces nos enseñará como crear.

  2. Como el modelo de contenido tiene un indicador de ocurrencia *, necesitamos unir este contenidos a una propiedad collection. Introducimos el atributo collection y le damos el valor list:
    <choice property=entries collection=list />
    

    Una propiedad collection puede representar un array o una List. En este caso, deberíamos unir el contenido a una List, porque ésta, al contrario que el array, nos permite añadir más entradas durante la ejecución.

  3. Introducimos la etiqueta final para la declaración de unión content:
    </content>
    

Estas declaraciones de unión producirán está propiedad en la clase Transactions:

public List getEntries(); 
public void deleteEntries(); 
public void emptyEntries();

El método getEntry devuelve la lista de entradas completa. Una vez que consigamos la lista, podemos iterar a través de la lista como lo haríamos con cualquier lista para llegar a una entrada determinada. La lista devuelta por getEntries es modificable: si modificamos una entrada en esta lista, cambiará la entrada en el árbol de contenido. El método emptyEntries desecha los valores de la lista y crea una nueva lista, vacía. El método deleteEntries borra la lista.

Para especificar la unión del grupo del modelo de alección anidado en el modelo de elección del elemento check, dentro de la declaración de unión del elemento check:

  1. Introducimos las declaraciones de unión element-ref para los elementos date, name, y amount, e insertamos la declaración de unión de elección como se muestra en negrita en la declaración de unión del elemento check:
    <element name=check type=class >
        <attribute name=number convert=int />
        <attribute name=category convert=CheckCategory />
        <content>
            <element-ref name=date />
            <element-ref name=name />
            <element-ref name=amount />
            <choice property=pend-void-clrd />
        </content>
    </element>
    

El valor del atributo property es el nombre de la propiedad generada. Por ejemplo, el método get se llamará getPendVoidClrd. Si el contenido también contiene una opción, una secuencia, o un declaración de unión de resto de contenido, necesitamos especificar las declaraciones de unión por defecto de element-ref para los elementos que preceden a este contenido; si no, el compilador de esquema no sabe qué elementos se han pensado para dichas declaraciones de unión.

. Crear Interfaces

Puede ser que hayas notado que los elementos deposit, check, withdrawal tienen cierto contenido común. Puede que también hayas notado que cada uno representa una entrada en una lista de transacciones. Cuando tenemos un grupo de clases que proporcionan funciones similares y tienen algún comportamiento y propiedades comunes, podemos utilizar un interface para capturar las semejanzas entre las clases. En el caso de deposit, check, y withdrawal, todas tienen los elementos date y amount. Estos elementos serán propiedads comunes en el interface.

Para hacer que el compilador de esquema genere un inteface, con Deposit, Check, y Withdrawal implementaremos:

  1. En cualquier lugar del nivel más alto de nuestro esquema de unión, quizás después de las declaraciones de enumeración, introducimos esta declaración de interface:
    <interface name=Entry members=Deposit Check Withdrawal
    	properties=date amount />
    

    El atributo members representa todas las clases que implementan el interface. El atributo properties representa el contenido común compartido por los miembros del interface.

Como los elementos deposit, check, withdrawal ocurren en el modelo contenido de las transacciones como un grupo de elección, necesitamos referenciar Entry desde la unión del grupo de elección. Previamente asignamos el nombre del interface al atributo de la propiedad y al valor list del atributo collection. Asignamos el nombre del interface al atributo supertype:

<element name=transactions type=class class=Transactions > 
<content> 
    <choice property=entries collection=list supertype=Entry />
</content> 
</element>

El atributo supertype indica una clase o interface declarada en el esquema de unión que cada clase de elemento inclúida en la propiedad choice implementa.

Estas declaraciones de unión producirán un interface llamado Entry, que incluirá las propiedades date y amount:

public interface Entry { 
    ... 
    public int getAmount(); 
    public void setAmount(int x); 
    public Date getDate(); 
    public void setDate(Date d);

. Manejar la Evolución del Esquema

Como con cualquier recurso que genere código, un desarrollador que escriba aplicaciones básadas en el código necesita asegurarse de que el código recientemente generado no rompa las aplicaciones. Si nos anticipamos a que el DTD cambiará, podemos utilizar las declaraciones de unión más flexibles en el lenguaje de unión para proteger la integridad de las aplicaciones.

La manera más fácil de manejar la evolución del esquema es aceptar los uniones por defecto que el compilador de esquema produce. Estas uniones son definiciones muy flojas de las declaraciones del DTD, y así son más flexibles a los cambios del DTD. Por ejemplo, cualquier grupo de modelo que no consista en elementos distintos en una secuencia no-repetitiva se unirá usando una declaración de propiedad de contenido general, que une todo el contenido a una propiedad. Si agregaramos elementos a este grupo de modelo, estos elementos todavía serían representados por la propiedad, y las clases no cambiarían.

Podemos usar la declaración de propiedad de contenido general para cualquier grupo de modelo. Para unir content usando esta declaración, usamos la construcción content:

<content property=mygroup />

Esta unión generá esta propiedad:

public List getMygroup(); 
public void deleteMygroup(); 
public void emptyMygroup();

Otra declaración de unión que podemos utilizar para manejar la evolución del esquema es la declaración de unión rest. Esta declaración de unión es similar a la declaración de la propiedad de contenido general en que puede representar cualquier tipo de contenido. Añadiendo una declaración rest sobre una declaración de unión de elemento content, podemos añadir otros elementos y grupos al modelo de contenido del DTD en el futuro sin afectar a las clases generadas. Por ejemplo, podemos añadir una construcción rest a la unión de contenido del elemento withdrawal:

<content> 
    ... 
<rest property=rest />

</content>

como ya tenemos la propiedad rest, podemos añadir otro contenido al elemento withdrawal sin romper la aplicación. Además, todavía podemos tener acceso individualmente al viejo contenido con las propiedads generadas por las otras declaraciones dentro de la declaración de contenido. Solamente el nuevo contenido, definido por la propiedad rest, será representado por una propiedad List.

. Generar las Clases Java

Ahora que hemos terminado el esquema de unión, podemos ejecutar el compilador de esquema para generar las clases Java. Esta guía asume que hemos seguido las instrucciones en las notas de liberación, situadas en el directorio doc de la instalación y hemos configura los classpaths correctamente.

Para generar las clases Java:

  1. Ejecutamos el compilador de esquema con checkook.dtd y checkook.xjs, el esquema de unión que hemos creado:
    xjc checkook.dtd checkook.xjs
    

    Ahora deberíamos ver los ficheros Checkbook.java y Entry.java en nuestro directorio actual.

  2. Compilamos los ficheros fuentes en clases Java:
    javac *.java
    

    El resto de esta sección explica el código generado en estos ficheros. Si no necesitas una explicación del código, puedes ir al siguiente capítulo Construir Representaciones de Datos para construir árboles de contenido usando las clases.

. Los Ficheros Fuente Java Generados

Esta sección explica abreviadamente algunos de los métodos y de las clases públicas generadas por el compilador de esquema basadas en el DTD checkbook y el esquema de unión que creamos en la sección anterior. Puesto que las clases Deposit, Check, y Withdrawal son tan similares, entre estas clases, esta sección explica solamente la clase Check. Asimismo, esta sección explica solamente la clase enumerada CheckCategory, y entre las clases Pending, void, Cleared, esta sección explica solamente la clase pending.

. El fichero Checkbook.java

El elemento checkbook del DTD transactions.dtd está unido a la clase Checkbook cuya firma es:

public class Checkbook extends MarshallableRootElement implements RootElement

Como checkbook es un elemento raíz, la clase Checkbook extiende MarshallableRootElement, que es la clase que representa objetos elemento raíz que pueden ser empaquetados y desempaquetados, e implementa RootElement.

Como cada clase generada, la clase Checkbook contiene un constructor sin argumentos:

public Checkbook();

Recuerda que el elemento checkbook contiene un elemento transactions y un elemento balance:

<!ELEMENT checkbook ( transactions, balance ) >

Estos elementos están unidos a éstas propiedades en el clase Checkbook:

// the transactions property 
public void setTransactions(Transactions x); 
public Transactions getTransactions();

// the balance property 
public void setBalance(java.math.BigDecimal x); 
public java.math.BigDecimal getBalance();

La propiedad transactions acepta y devuelve un objeto transactions porque el elemento transactions también está representado por una clase. La propiedad balance acepta y devuelve un java.math.BigDecimal debido a éstas declaraciones de unión que especificamos en la sección Especificar Tipos:

<element name="balance" type="value" convert="BigDecimal"/> 
... 
<conversion name="BigDecimal" type="java.math.BigDecimal" />

Aunque MarshallableRootElement define métodos marshal, que la clase Checkbook usa por extensión, no define ningún método unmarshal. Así, el compilador de esquema genera estos métodos unmarshal estáticos en la clase Checkbook:

public static Checkbook unmarshal(InputStream in)
public static Checkbook unmarshal(XMLScanner xs)
public static Checkbook unmarshal(XMLScanner xs, Dispatcher d)

Cuando despempaquetamos un documento XML, podemos invocar a unmarshal(InputStream) o a unmarshal(XMLScanner) en los que InputStream o XMLScanner representan nuestro documento XML.

Aunque el desempaquetamiento realiza la validación por nosotros, necesitamos realizar validación después de editar el árbol de contenido y antes de empaquetarlo en un documento XML. Para este propósito, el compilador de esquema genera estos métodos:

public void validateThis(); 
public void validate();

Después de editar una parte del árbol de contenido, podemos usar validateThis para validar el objeto editado. Antes de empaquetar el arbol de contenido a un documento XML, debemos usar validate para validar todos el árbol de contenido.

. El Fichero Transactions.java

El elemento transactions de checkokk.dtd está unido a la clase Transactions cuya firma es:

public class Transactions extends MarshallableRootElement implements RootElement

Al igual que checkbook , transactions es un elemento raíz, y por eso la clase Transactions extiende MarshallableRootElement, e implementa RootElement. La clase Transactions contiene un constructor sin argumentos:

public Transactions();

El elemento transactions contiene cero o más elementos deposit, check, o withdrawal:

<!ELEMENT transactions (deposit | check | withdrawal)* >

Cuando seguimos las instrucciones de Personalizar las Declaraciones de Unión del Modelo de Contenido, especificamos que el compilador de esquema uniera el contenido del elemento transactions a una propiedad de colección List llamada entries. En la sección Crear Interfaces, especificamos que el supertype de la propiedad entries es el interface Entry:

<element name=transactions type=class class=Transactions > 
<content> 
    <choice property=entries collection=list supertype=Entry />
</content> 
</element>

El declaración de unión del interface hizo que el compilador de esquema uniera el contenido de transactions a una lista de deposit, check, y withdrawals. La declaración de unión del interface hizo que el compilador de esquema generara un interface Entry, que implementaban las clases Deposit, Check, y Withdrawal. El interface Entry se explica en la siguiente sección.

La propiedad entries consta de tres métodos que usamos para aceder al contenido del elemento transactions:

public List getEntries(); 
public void deleteEntries(); 
public void emptyEntries();

El método getEntry devuelve la lista de entradas completa. Una vez que consigamos la lista, podemos iterar a través de la lista como lo haríamos con cualquier lista para obtener una entrada determinada. La lista devuelta por getEntries es modificable: Si cambiamos una entrada de esta lista, cambiará la entrada en el árbol de contenido. El método deleteEntries suprime la lista de entradas actual. El método emptyEntries suprime los valores de la lista.

Cualquier contenido de transactions (tanto si son dos depósitos o un depósito y cinco reintegros) implementa Entry. Por lo tanto, no necesitamos realizar pruebas de instanceof o forzados de tipo en los items de lista que representan entradas, a menos que estemos trabajando con un elemento contenido en deposit, check, o withdrawal que no sea un miembro del interface Entry.

Al igual que la clase Checkbook, la clase Transactions contiene los métodos marshal, unmarshal y validate que un usuario debería invocar.

. El Fichero Entry.java

El supertype del contenido del elemento transactions es el inteface Entry, como se especifica en esta declaración de unión de inteface:

<element name=transactions type=class class=Transactions > 
    <content> 
        <choice property=entries collection=list supertype=Entry />
    </content> 
</element>

La firma del inteface Entry es:

public interface Entry {

El único contenido de elementos comunes entre los elementos deposit, check, y withdrawal son date y amount, y por eso asignamos estos elementos al atributo properties de la construcción del interface en la sección Crear Interfaces

<interface name=Entry members=Deposit Check Withdrawal properties=date amount />

Por lo tanto, el interface Entry, incluye las propiedades para los elementos date y amount:

public int getAmount(); 
public void setAmount(int x); 
public Date getDate(); 
public void setDate(Date d);

La propiedad amount devuelve y acepta un int porque escribimos una declaración de conversión de unión para convertir desde String a BigDecimal y utilizamos el atributo convert en la declaración de unión del elemento amount y le asignamos el valor BigDecimal:

<element name=amount type=value convert=BigDecimal /> 
<conversion name="BigDecimal" type="java.math.BigDecimal" />

. El Fichero Check.java

El elemento check está unido a la clase Check, que tiene la firma:

public interface Check extends MarshallableObject implements Element, Entry{

La clase Check extiende MarshallableObject, que es la clase abstracta que representa cualquier objeto que se pueda empaquetar o despempaquetar pero no sea necesariamente un elemento raíz. La clase Check debe implementar Element porque, al contrario que MarshallableRootElement, un MarshallableObject no tiene que ser un objeto derivado de un elemento y por lo tanto no implementa Element en si mismo. Finalmente, Check implementa Entry porque especificamos Entry como el supertype del contenido del elemento transaction en la sección Crear Interfaces. El elemento check tiene dos atributos y contiene seis elementos:

<!ELEMENT check ( date, name, (pending | void | cleared), memo? ) >
<!ATTLIST check
        number CDATA #IMPLIED
        category ( rent | groceries | other ) #IMPLIED >

El elemento date está unido a una propiedad y devuelve un objeto Date:

public Date getDate(); 
public void setDate(Date x);

Los elementos name, y memo, que sólo contienen texto están unidos a estas propiedades:

public String getName(); 
public void setName(String x); 
public String getMemo(); 
public void setMemo(String x);

En la sección Especificar Tipos, especificamos un tipo int para el atributo number, produciendo esta propiedad:

public int getNumber(); 
public void setNumber(int x);

En la sección Personalizar Declaraciones de Unión de Modelos de Contenido especificamos que el modelo de grupo de elección, (pending | void | cleared), está unido a una propiedad, produciendo estos métodos:

public MarshallableObject getPendVoidClrd(); 
public void setPendVoidClrd(MarshallableObject x);

La propiedad pend-void-clrd devuelve y acepta un MarshallableObject, que representa objetos que pueden ser empaquetados y desempaquetado. La razón por la que este tipo de propiedad no es un String es porque no podemos determinar si un String se supone que es un elmento pend, void, cleared; con MarshallableObject, si podemos porque el MarshallableObject será un objeto Pend, Void, o Cleared. La clase Check también contiene una propiedad para la enumeración checkCategory, que especificamos en la sección Crear Tipos Enumerados:

<enumeration name=CheckCategory members=rent groceries other />
... 
<attribute name=category convert=CheckCategory />

La propiedad que genera está declaración de unión es:

public CheckCategory getCheckCategory();
public void setCheckCategory(CheckCategory x);

Esta propiedad acepta y devuelve un objeto CheckCategory, que es un interface de la clase CheckCategory. Esta clase se explica en la siguiente sección.

. El Fichero CheckCategory.java

El atributo category toma un valor de un conjunto fijo de valores representado por un grupo de elección en la definición de atributo:

<!ATTLIST check ... category ( rent | groceries | other ) #IMPLIED >

Cuando seguimos las instrucciones de la sección Crear Tipos Enumerados, especificamos una unidón de este atributo a un tipo seguro enum:

<enumeration name=CheckCategory members=rent groceries other />
... 
<attribute name=category convert=CheckCategory />

Un tipo seguro enum es una clase que representa una enumeración que consta de un elemento o un atributo y la lista de los posibles valores, sólo uno de los cuales puede asignarse a la clase. El nombre de la clase corresponde al del elemento o del atributo, y los campos estáticos representan los valores. Las clases de tipo seguro enum tienen muchas ventajas, incluyendo controlar el tipo en tiempo de compilación. El tipo seguro enum generado desde el atributo category es:

public final class CheckCategory { 
    public final static CheckCategory RENT; 
    public final static CheckCategory GROCERIES; 
    public final static CheckCategory OTHER; 
    public static CheckCategory parse(String x); 
    public String toString(); 
}

El método parse intenta mapear un argumento String a uno de los valores aceptados. El método toString devuelve el valor actual de un CheckCategory como un String.

. El Fichero Pending.java

La clase Pending representa uno de los miembros del modelo de grupo de elección contenido en la declaración del elemento check:

<!ELEMENT check ( date, name, ( pending | void | cleared), memo? ) >

Este elemento, así como los elementos void o cleared, están unidos a sus propias clases porque son parte de este grupo modelo de elección. Según lo explicado en el sección del fichero Check.java, la propiedad a la que está unido este grupo debe devolver un MarshallableObject, que debe también ser un objeto Pend, Void o Cleared, de modo que la aplicación sepa qué elemento encontrará durante el empaquetamiento o despempaquetamiento. Las clases Pend, Void y Clearedtienen un constructor sin argumentos.

El siguiente capítulo nos mostrará como trabajar con estas clases para construir representaciones de datos.

 
Patrocinados
 

Copyright © 1999-2007 Programación en castellano. Todos los derechos reservados.
Formulario de Contacto - Datos legales - Publicidad

Hospedaje web y servidores dedicados linux por Ferca Network

red internet: musica mp3 | logos y melodias | hospedaje web linux | registro de dominios | servidores dedicados
más internet: comprar | recursos gratis | posicionamiento en buscadores | tienda virtual | gifs animados