Posts

 

Logo startupweb
Página del proyecto en google code.
 
Más de una vez he querido desarrollar una página web para distintas cosas, y normalmente como son cosas raras no puedo echar manos de un CRM así que tengo que iniciarlas de 0 (cosa que también gusta para que nos vamos a engañar).
 
El caso es que siempre me topaba con el mismo rollo: a ver voy a hacer una página y me tengo que enfrentar al problema de registros de usuarios. Lo que me gustaría tener es lo siguiente:
- Que esté hecho en php/mysql. Y ya que estamos que use un framework, pero ligerito.
- Quitarme de encima la gestión de altas/validación/recuperación de cuentas. Otra vez no por favor.
- Pasar esa gestión a otros sitios en los que el usuario va a confiar mucho más que en un web rara.
 
¡No problemo! Existe un proyecto llamado hybridauth que nos ofrece todo lo necesario para validarnos contra los grandes sitios como Google, Facebook, yahoo, live, twitter, etc... Y lo que es mejor, es compatible con Codeigniter, el  framework php of (my) choice. 
 
Codeigniter + Hybridauth... ummm el caso es que ya existe algo así: hybridigniter. Pero en mi caso quería meter algunas cosas más de serie: la librería jquery y ya que estamos el bootstrap. Al final, startupweb contiene todo eso más el controlador+modelo que se ocupa del login. Vamos, que si quieres no montar, más bien iniciar un site que ya tenga solucionado el problema de los usuarios, con una plantilla web sencilla y que funciona en todas partes, ya la tienes. En concreto tiene:
  • codeigniter 2.1.3
  • hybridauth 2.1.2
  • bootstrap 2.3.2
  • jquery-1.10.1
  • jquery-ui-1.10.3
 
Si te descargas startupweb te quedaría lo siguiente:
1.editar la configuración de la BB: application/config/database.php
2.editar la configuración de HybridAuth: application/config/hybridauthlib.php
 
Esa es la parte más engorrosa, pero por el tema administrativo: por cada sitio en el que vas a permitir que los usuarios se validen tendrás que dar de alta tu web. En cada uno es distinto, pero los datos que te acaban facilitando son similares: una url y una serie de claves.
 
Una vez hecho eso, ya tienes el site iniciado y ya puedes programar tu web sobre Codeigniter, un framework potente y sobre todo muy ligero. Se basa en convenciones, así que basicamente tienes que preocuparte de colocar los ficheros en determinados directorios y nombrarlos de determinadas maneras. Nada de ficheros xml como el Spring.
 
Para predicar con el ejemplo, una de las tareas pendientes del estío es pasar pello.info a startupweb. Así que cualquier día  de estos cambiará el aspecto de la página.

 

Vale, en los post anteriores hemos visto cómo se crean tanto los tipos simples como los tipos complejos de un documento XML Schema. Por lo que se ve es una herramienta que permite hilar mucho más fino para crear documentos XML mucho más precisos y restrictivos de lo que se podía hacer con el DTD.
 
Pero ahora bien, ¿cómo se crea un documento completo? ¿Se ponen primero los elementos simples y luego los complejos? ¿Se pone todo del tirón?
 
Tenemos más de una opción y elegir una u otro puede ir según gustos o percepción  de claridad. Yo particularmente recomendaría, en caso de documentos don mucho elemento al menos, iniciar el documento con la declaración de tipos simples, atributos, etc... y luego meter los tipos complejos. En cualquier caso, con IDEs como eclipse podemos crear el fichero XSD de una forma algo más visual para evitar problemas. Si lo hacemos a mano nos indicará errores y tendremos que ir corrigiendo y haciendo botón derecho sobre el fichero XSD y seleccionar Validate. A veces aparece en rojo, ejecutas eso y el error desaparece... eclipse no es perfecto eh amigos del netbeans?
 
Vamos a ver un ejemplo. Supongamos que tenemos un documento XML que define las características de un personaje de un juego:
 
<?xml version="1.0" encoding="UTF-8"?>
<character 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="personaje.xsd">
  <name>Iker Nieve</name>
  <race>Human</race>
  <guild>Smith</guild>
  <weapons>
  <weapon type="axe" damage="5" defence="3" />
  <weapon type="shield" damage="2" defence="10" />
  </weapons>
  <magic>
  <spell cost="5">
  <name>Hellfire</name>
  <type>Elementalist</type>
  </spell>
  <spell cost="15">
  <name>Reanimate</name>
  <type>Necromancy</type>
  </spell>
</character>
 
Vamos a ver formas de crear un XSD para este XML:
 
 

1. Todo del tirón:

Trabajo de chinos para habernos matao:
 
<?xml version="1.0" encoding="ISO-8859-1" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
 
<xs:element name="character">
  <xs:complexType>
    <xs:sequence>
<xs:element name="name">
  <xs:simpleType>
    <xs:restriction base="xs:string">
      <xs:whiteSpace value="collapse"/>
      <xs:minLength value="8"/>
      <xs:maxLength value="15"/>
    </xs:restriction>
  </xs:simpleType>
</xs:element>
<xs:element name="race">
 <xs:simpleType>
   <xs:restriction base="xs:string">
     <xs:enumeration value="Human"/>
     <xs:enumeration value="Orc"/>
     <xs:enumeration value="Elf"/>
     <xs:enumeration value="Dwarf"/>
   </xs:restriction>
 </xs:simpleType>
</xs:element>
<xs:element name="guild">
 <xs:simpleType>
   <xs:restriction base="xs:string">
     <xs:enumeration value="Smith"/>
     <xs:enumeration value="Cook"/>
     <xs:enumeration value="Warrior"/>
     <xs:enumeration value="Peasant"/>
   </xs:restriction>
 </xs:simpleType>
</xs:element>
<xs:element name="weapons">
<xs:complexType>
<xs:sequence>
<xs:element name="weapon" minOccurs="1" maxOccurs="10">
  <xs:complexType>
      <xs:attribute name="type" type="xs:string"/>
      <xs:attribute name="damage">
  <xs:simpleType>
    <xs:restriction base="xs:int">
      <xs:minInclusive value="1"/>
      <xs:maxInclusive value="15"/>
    </xs:restriction>
  </xs:simpleType>
</xs:attribute>
      <xs:attribute name="defence">
  <xs:simpleType>
    <xs:restriction base="xs:int">
      <xs:minInclusive value="1"/>
      <xs:maxInclusive value="8"/>
    </xs:restriction>
  </xs:simpleType>
</xs:attribute>
  </xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="magic">
 <xs:complexType>
  <xs:sequence>
<xs:element name="spell"  minOccurs="0" maxOccurs="10">
 <xs:complexType>
     <xs:sequence>
       <xs:element name="name" type="xs:string"/>
<xs:element name="type">
  <xs:simpleType>
  <xs:restriction base="xs:string">
    <xs:enumeration value="Elementalist"/>
    <xs:enumeration value="Necromancy"/>
    <xs:enumeration value="Cider Remains"/>
    <xs:enumeration value="Coffe Remains"/>
    </xs:restriction>
  </xs:simpleType>
</xs:element>
      </xs:sequence>
      <xs:attribute name="cost">
  <xs:simpleType>
    <xs:restriction base="xs:int">
      <xs:minInclusive value="1"/>
      <xs:maxInclusive value="15"/>
    </xs:restriction>
  </xs:simpleType>
</xs:attribute>       
     </xs:complexType>
 
</xs:element>
</xs:sequence>
 </xs:complexType>
</xs:element>
</xs:sequence>
  </xs:complexType>
</xs:element>
 
</xs:schema>
 
 

2. La versión separada, mucho más manejable

 
<?xml version="1.0" encoding="UTF-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
 
<!-- Definition of elements -->
<xs:element name="name">
  <xs:simpleType>
    <xs:restriction base="xs:string">
      <xs:whiteSpace value="collapse"/>
      <xs:minLength value="8"/>
      <xs:maxLength value="15"/>
    </xs:restriction>
  </xs:simpleType>
</xs:element>
 
<xs:element name="race">
 <xs:simpleType>
   <xs:restriction base="xs:string">
     <xs:enumeration value="Human"/>
     <xs:enumeration value="Orc"/>
     <xs:enumeration value="Elf"/>
     <xs:enumeration value="Dwarf"/>
   </xs:restriction>
 </xs:simpleType>
</xs:element>
 
<xs:element name="guild">
 <xs:simpleType>
   <xs:restriction base="xs:string">
     <xs:enumeration value="Smith"/>
     <xs:enumeration value="Cook"/>
     <xs:enumeration value="Warrior"/>
     <xs:enumeration value="Peasant"/>
   </xs:restriction>
 </xs:simpleType>
</xs:element>
 
<xs:element name="weapon">
  <xs:complexType>
      <xs:attribute name="type" type="xs:string"/>
      <xs:attribute ref="damage" />
      <xs:attribute ref="defence" />
  </xs:complexType>
</xs:element>
 
<xs:element name="weapons">
<xs:complexType>
<xs:sequence>
<xs:element ref="weapon"  minOccurs="1" maxOccurs="10"  />
</xs:sequence>
</xs:complexType>
</xs:element>
 
<xs:element name="type">
<xs:simpleType>
  <xs:restriction base="xs:string">
    <xs:enumeration value="Elementalist"/>
    <xs:enumeration value="Necromancy"/>
    <xs:enumeration value="Cider Remains"/>
    <xs:enumeration value="Coffe Remains"/>
    </xs:restriction>
  </xs:simpleType>
</xs:element>
 
<xs:element name="spell" >
 <xs:complexType>
     <xs:sequence>
       <xs:element name="name" type="xs:string"/>
       <xs:element ref="type" />
      </xs:sequence>
     <xs:attribute ref="cost" />       
 </xs:complexType>
</xs:element>
 
<xs:element name="magic">
 <xs:complexType>
  <xs:sequence>
<xs:element ref="spell"  minOccurs="0" maxOccurs="10" />
</xs:sequence>
 </xs:complexType>
</xs:element>
 
 
<!-- Attributes -->
      <xs:attribute name="damage">
      <xs:simpleType>
    <xs:restriction base="xs:int">
      <xs:minInclusive value="1"/>
      <xs:maxInclusive value="15"/>
    </xs:restriction>
  </xs:simpleType>
</xs:attribute>
 
    <xs:attribute name="defence">
  <xs:simpleType>
    <xs:restriction base="xs:int">
      <xs:minInclusive value="1"/>
      <xs:maxInclusive value="8"/>
    </xs:restriction>
  </xs:simpleType>
</xs:attribute>
 
<xs:attribute name="cost">
  <xs:simpleType>
    <xs:restriction base="xs:int">
    <xs:minInclusive value="1"/>
    <xs:maxInclusive value="15"/>
    </xs:restriction>
  </xs:simpleType>
</xs:attribute>
 
 
<!-- And finally, character -->
<xs:element name="character">
  <xs:complexType>
    <xs:sequence>
<xs:element ref="name" />
<xs:element ref="race" />
<xs:element ref="guild" />
<xs:element ref="weapons" />
<xs:element ref="magic" />
</xs:sequence>
  </xs:complexType>
</xs:element>
 
</xs:schema>
 

 

En el post anterior se trataba de ver de forma más o menos exhaustiva los elementos simples y ante todo los tipos que se pueden aplicar o crear y las restricciones que se pueden añadir. Partiendo de esa base ya podemos centrarnos en los elementos complejos y con eso ya podremos crear XSD completos.
 
Hay cuatro tipos de elementos complejos:
- Elementos que contienen otros elementos
- Elementos que solamente contienen texto
- Elementos que pueden tener tanto otros elementos como texto
- Elementos vacíos.
 

Elementos que contienen elementos

 
Era lógico poder tener algo así.
Un ejemplo:
<xs:element name="system">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="name" type="xs:string"/>
      <xs:element name="version" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
</xs:element> 
 
En xml podría ser:
<system>
<name>Windows</name>
<version>3.11 para trabajo en grupo</version>
</system>
 
Al escribirlo de esa manera solo podrá existir un elemento system con esos atributos.
Si quisieramos que hubiera otros elementos con LAS MISMAS características lo tendríamos que
definir así, si el xs:element:
 
<xs:complexType name="system">
    <xs:sequence>
      <xs:element name="name" type="xs:string"/>
      <xs:element name="version" type="xs:string"/>
    </xs:sequence>
 </xs:complexType>
 
 
Y así sí, podemos crear otros elementos del mismo tipo complejo:
<xs:element name="mobile" type="system" />
<xs:element name="tablet" type="system" />
 
Lo primero que llama la atención en el elemento system es el indicador <xs:sequence>. Eso se explica más adelante en los indicadores
 
Por cierto podemos hacer que un elemento extienda a otro, es decir, que represente
lo mismo pero con elementos añadidos. Ojo a la etiqueta.
 
 
<xs:complexType name="mage">
  <xs:sequence>
    <xs:element name="name" type="xs:string"/>
    <xs:element name="type" type="xs:string"/>
  </xs:sequence>
</xs:complexType>
 
<xs:complexType name="archmage">
  <xs:complexContent>
    <xs:extension base="mage">
      <xs:sequence>
        <xs:element name="level" type="xs:int"/>
        <xs:element name="order" type="xs:string"/>
      </xs:sequence>
    </xs:extension>
  </xs:complexContent>
</xs:complexType>
 
en XML:
 
<archmage>
<name>Skeleton</name>
<type>Necromancer</type>
<level>10</level>
<order>Order of the Leech</order>
</archmage>
 

Elementos que solamente contienen texto

Estos elementos tienen como contenido un simple texto y pueden tener atributos.
Por ejemplo
<personaje id="666">Demonio</personaje>
 
 
Se pueden definir de dos formas, como una extensión o con restricciones. Lo único que hay que indicar es que el texto que contienen tiene que ser de algún tipo. Puede ser de algún tipo básico u otro definido por nosotros. Y luego aparte podemos meter atributos:
 
<xs:element name="personaje">
  <xs:complexType>
    <xs:simpleContent>
      <xs:extension base="tipo_en_el_que_basa">
      <!-- podemos meter atributos aquí -->
<xs:attribute name="xx" type="tipo_atributo">
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>
</xs:element>
 
Con restricciones:
 
<xs:element name="personaje">
  <xs:complexType>
    <xs:simpleContent>
      <xs:restriction base="tipo_en_el_que_basa">
        <!-- podemos meter atributos aquí -->
<xs:attribute name="xx" type="tipo_atributo">
      </xs:restriction>
    </xs:simpleContent>
  </xs:complexType>
</xs:element>
 
Por ejemplo:
 
<xs:element name="personaje">
  <xs:complexType>
    <xs:simpleContent>
      <xs:extension base="xs:string">
      <!-- podemos meter atributos aquí -->
<xs:attribute name="id" type="xs:int">
      </xs:extension>
    </xs:simpleContent>
  </xs:complexType>
</xs:element>
 
<personaje id="666">Demonio</personaje>
 
 
 

Elementos que pueden tener tanto otros elementos como texto

Este sería una mezcla de los dos anteriores, debemos añadir el atributo mixed="true"
 
<xs:element name="paragraph">
  <xs:complexType mixed="true">
    <xs:all>
      <xs:element name="url" type="xs:anyURI"/>
      <xs:element name="number" type="xs:int"/>
    </xs:sequence>
  </xs:complexType>
</xs:element>
 
En XML sería:
<paragraph>
Bla bla bla <url>http://pello.io</url> con un total
de <number>666</number> visitas al día bla bla bla..
</paragraph>
 

Elementos vacíos

Sí, puede ser, son elementos que lo único que tienen son atributos. Si haces un poco de memoria en el propio xhtml existen este tipo de elementos, como <img>, <input> ...
 
Por ejemplo:
<xs:element name="person">
  <xs:complexType>
    <xs:attribute name="name" type="xs:string"/>
    <xs:attribute name="age" type="xs:int"/>
  </xs:complexType>
</xs:element>
 
en xml:
<person name="Ignacio" age="-41" />
 
Si queremos que los atributos tengan alguna restricción:
<xs:element name="person">
  <xs:complexType>
      <xs:attribute name="name" type="xs:string"/>
    <xs:complexContent>
      <xs:restriction base="xs:integer">
        <xs:attribute name="age" type="xs:positiveInteger"/>
      </xs:restriction>
    </xs:complexContent>
  </xs:complexType>
</xs:element>
 
Ahora ya no admitiría edades negativas
 

Indicadores

Antes nos llamaba la atención en indicador sequence. 
<xs:element name="sistema">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="nombre" type="xs:string"/>
      <xs:element name="version" type="xs:string"/>
    </xs:sequence>
  </xs:complexType>
</xs:element> 
 
Existen varios tipos de indicadores:
 
- sequence: dentro irán dos elementos seguidos y que en el documento XML deben ir en el mismo orden!!.
-all: en el XML salen todos los elementos o no, en cualquier orden. 
-choice: no pueden salir todos los elementos internos ¡solo uno de ellos!
 
<xs:element name="tipo">
  <xs:complexType>
    <xs:choice>
      <xs:element name="guerrero" type="guerrero"/>
      <xs:element name="mago" type="mago"/>
    </xs:choice>
  </xs:complexType>
</xs:element>
 
en xml:
<tipo>
<mago>Gandalf</mago>
</tipo>
 
También podemos añadir indicadores de ocurrencia para poder decir cuántes veces puede aparecer un elemento.
 
-maxOccurs/minOccurs: indica el número máximo/mínimo de veces que puede apacer un elemento. Téngase en cuenta
que en indicador de elementos all el maxOccurs solo podrás ser 1 y el minOccurs 0 o 1.
Si queremos que sea infinito pondríamos maxOccurs="unbounded"
 
Por ejemplo, podemos tener un elemento warrior que tiene un nombre y puede llevar entre 1 y 10 armas:
 
<xs:element name="weapon">
<xs:simpleType>
    <xs:restriction base='string'>
    <xs:restriction base="xs:string">
      <xs:whiteSpace value="collapse"/>
    </xs:restriction>
    </xs:restriction>
</xs:simpleType>
</xs:element>
 
<xs:element name="warrior">
  <xs:complexType>
    <xs:sequence>
      <xs:element name="name" type="xs:string"/>
      <xs:element name="weapon" type="weapon" minOccurs="1" maxOccurs="10" />
    </xs:sequence>
  </xs:complexType>
</xs:element>
 
En XML:
 
<warrior>
<name>Tronak the Barbarian</name>
<weapon>Sword of Steel</weapon>
<weapon>Battle Axe</weapon>
<weapon>Spiked Club</weapon>
<weapon>Hammer of War</weapon>
</warrior>
 
 
El elemento grupo:
 
Con este elemento podemos agrupar elementos o atributos y luego indicar ese grupo dentro de tipos complejos sin necesidad de reescribir todos los elementos. En caso de que más de un elemento complejo comparta un conjunto de elementos, si los agrupamos podemos reutilizarlos. Y lo mismo con los atributos. Si más de un elemento complejo comparte los atributos podemos agruparlos.
 
<xs:group name="characterdata">
  <xs:sequence>
    <xs:element name="name" type="xs:string"/>
    <xs:element name="race" type="xs:string"/>
    <xs:element name="guild" type="xs:string"/>
  </xs:sequence>
</xs:group>
 
Ahora en player en lugar de indicar name, race,... metemos el grupo:
<xs:element name="player">
  <xs:complexType>
    <xs:sequence>
<xs:group ref="characterdata"/>
      <xs:element name="weapon" type="weapon" minOccurs="1" maxOccurs="10" />
    </xs:sequence>
  </xs:complexType>
</xs:element>
 
en el XML:
<player>
<name>Radagast</name>
<race>Human</race>  <!-- I know I know... don't make me go to check Silmarilion again.. -->
<guild>Wizard</guild>  
<weapon>Mocus</weapon>
<weapon>Cannabis Leaf</weapon>
</player>
 
Y con los atributos los mismo:
<xs:attributeGroup name="playerinfo">
  <xs:attribute name="name" type="xs:string"/>
  <xs:attribute name="strength" type="xs:int"/>
  <xs:attribute name="intelligence" type="xs:int"/>
</xs:attributeGroup>
 
<xs:element name="player">
  <xs:complexType>
  <xs:complexType>
    <xs:attributeGroup ref="playerinfo" />
  </xs:complexType>
  </xs:complexType>
</xs:element>
 

 

XML Schema, elementos simples

 
Los ficheros XSD pueden estar formados por elementos simples o complejos. Los elementos simples son aquellos que solamente pueden contener un valor concreto (un texto, un número) y nada más. Si necesitamos un elemento que a su vez contenga otros elementos o atributos eso sería un elemento complejo.
 
Para definir un elemento simple basta con poner algo así:
 
<xs:element name="nombrecliente" type="xs:string" />
 
Está claro que ese elemento contendrá un texto.
 

Los tipos

Los tipos se pueden aplicar tanto a elementos como a atributos. Los atributos personalizados
se aplican a elementos complejos. Pero ahora lo que nos ocupa es ver qué tipos tenemos.
 
 
Los tipos de los que disponemos son los habituales: string, integer, decimal, date, time, boolean,... cada uno
con sus peculiaridades y opciones.
 

El tipo boolean

Es de los más simples, puede contener cuatro posibles valores: true, false, 1, 0
 

El tipo string y sus derivados

Es un tipo bastante simple del que se pueden crear tipos simples con ciertas restricciones
como whitespace, length,minLength,maxLength,pattern,enumeration,assertions
Por ejemplo el DNI:
 
<xs:element name="dni">
<xs:simpleType>
    <xs:restriction base='string'>
      <xs:length value='9' fixed='true' />
    </xs:restriction>
</xs:simpleType>
<xs:element>
 
O una enumeración:
<xs:element name="sistema">
  <xs:simpleType>
    <xs:restriction base="xs:string">
      <xs:enumeration value="Windows"/>
      <xs:enumeration value="Linux"/>
      <xs:enumeration value="OS2"/>
      <xs:enumeration value="MSDOS"/>
    </xs:restriction>
  </xs:simpleType>
</xs:element>
 
Los patrones permiten expresiones regulares. El dni se puede hacer así también:
<xs:element name="dni" type="tipoDni" />
 
<xs:simpleType name="tipoDni">
   <xs:restriction base="xs:string">
     <xs:pattern value="[0-9]{8}[A-Z]{1}"/>
   </xs:restriction>
</xs:simpleType>
 
Con el whitespace podemos indicar cómo queremos que se traten los espacios en blanco.
Podemos indicar estos valores:
  • preserve: mantiene los espacios blancos o caracteres no visibles.
  • collapse: quita cualquier caracter no visible (espacios, saltos, etc..)
  • replace: sustituye saltos de línea etc por espacios en blanco.
 
<xs:element name="nombreLimpio">
  <xs:simpleType>
    <xs:restriction base="xs:string">
      <xs:whiteSpace value="collapse"/>
    </xs:restriction>
  </xs:simpleType>
</xs:element>
 
Las longitudes también dan juego:
<xs:element name="contrasena">
  <xs:simpleType>
    <xs:restriction base="xs:string">
      <xs:minLength value="8"/>
      <xs:maxLength value="15"/>
    </xs:restriction>
  </xs:simpleType>
</xs:element>
 

Tipos numéricos

Aparte de los clásicos interger y decimal, disponemos de los siguientes:
  • byte, unsignedByte
  • decimal, 
  • int, unsignedInt, 
  • integer, negativeInteger (-1,-2,-3,..), nonPositiveInteger (0,-1,-2,...), nonNegativeInteger (0,1,2,...), positiveInteger (1,2,3,...)
  • long,unsignedLong 
  • short,unsignedShort
 
 Si cremos tipos propios como con los Strings podemos aplicar estas resitrcciones:
  •  enumeration: un conjunto de números
  •  fractionDigits: total de decimales a mostrar
  •  maxExclusive: valor máximo EXcluyendo el indicado
  •  maxInclusive: valor máximo incluyendo el indicado
  •  minExclusive: valor mínimo EXcluyendo el indicado
  •  minInclusive:  valor mínimo incluyendo el indicado
  •  pattern: aplicar patrón regex
  •  totalDigits: total de dígitos a mostrar
  •  whiteSpace: tratamiendo de espacios en blanco

Las fechas.

Hay varios tipos. 

Date

El date de toda la vida
<xs:element name="nacimiento" type="xs:date" />
 
Por ejemplo (es Año-Mes-Día)
<nacimiento>1983-02-28</nacimiento>
 
Podemos indicar la zona horaria en la fecha. Añadir una Z para zona UTC o bien
añadir diferencia de zona en positivo o negativo.
 
<nacimiento>1985-12-23-03:00</nacimiento>
<nacimiento>1985-12-23+03:00</nacimiento>
 
¡Ojo!! no es la hora, es la diferencia horaria.
 
Luego está el time para indicar una hora en formato hh:mm:ss
<xs:element name="inicio" type="xs:time" />
 
Por ejemplo:
 
<inicio>09:15:00</inicio>  
 
También podemos indicar la zona horaria.
<inicio>09:15:00+01:00</inicio>  
 

 El dateTime

 Une fecha y hora con formato: YYYY-MM-DDThh:mm:ss
 Ojo con la T.
 <xs:element name="instalacion" type="xs:dateTime" />
Por ejemplo:
<instalacion>2012-10-23T09:34:23</instalacion>
 
Y se puede añadir la zona horaria también. 
<instalacion>2012-10-23T09:34:23-07:00</instalacion>
 

Duration, la duración en tiempo

Este tipo nos permite indicar la duración del tiempo.
El formato es el siguiente: PnYnMnDTnHnMnS
La P hay que ponerla sí o sí el resto:
  • nY indica n años
  • nM n meses
  • nD n días
  • T En el caso de que vayamos indicar un tiempo ponemos la T primero
  • nH n horas
  • nM n minutos
  • nS n segundos
 
Por ejemplo
<xs:element name="condena" type="duration" />
 
y un valor:
<condena>P5Y2M1D</condena>
 
Sería una condena de cinco años dos meses y un día.
 
Luego tenemos los tipos más básicos
  • type="xs:gDay" para representar un día en formato DD
  • type="xs:gMonth" para representar un mes en formato MM
  • type="xs:gMonthDay" para representar un mes-día en formato MM-DD
  • type="xs:gYear" para representar un año en formato YYYY 
  • type="xs:gYearMonth" para representar un año-mes en formato YYYY-MM
 
 Si cremos tipos propios como con los Strings y números podemos aplicar estas resitrcciones:
  • enumeration
  • maxExclusive
  • maxInclusive
  • minExclusive
  • minInclusive
  • pattern
  • whiteSpace
 

Otros tipos raros

El binario:

puede ser en formato base64 o hexadecimal. Esto nos puede servir para meter un churro
en alguno de esos formatos dentro del document XML.
 
<xs:element name="binario" type="xs:hexBinary"/>
Luego
<binario>DEAD0663245345ABDDEAD2346562</binario>
 
Otro:
<xs:element name="adjunto" type="xs:base64Binary"/>
 

AnyURI

Es un tipo muy interesante para indicar una URL
<xs:element name="web" type="xs:anyURI" />
 
Luego
<web>http://pello.io</web> 
 
---
 

Aplicable para todos 

 Aparte del tipo podemos especificar un valor por defecto:
 
<xs:element name="bote" type="xs:int" default="20" />
 
O incluso un valor fijo con el atributo fixed:
 
<xs:element name="pi" type="xs:decimal" fixed="3.1415" />
 
 

 

Plugins, Goals, Coordenadas...
Tenemos varias maneras de crear un proyecto con Maven. La que no falla es la de la consola.
 
mvn archetype:create -DgroupId=info.pello.maven.xsd -DartifactId=validadorXsd -DpackageName=info.pello.maven
 
El parámetros archetype:create es lo que Maven llama goal; algo similar a los targets de ant.  Un arquetipo para entendernos
es un modelo, o por decirlo más simple es un tipo. Para ser más precisos, archetype es un plugin de maven y create el goal.
Un plugin puede tener distintos goal.
 
La orden anterior nos crea un toda la estructura de directorios de maven, descrita en un post anterior. Como se puede
adivinar en los parámetros (proporcionados con -D al estilo java), coinciden con los que se ponen en un fichero pom.xml
básico. El directorio del proyecto coincidirá con el nombre de artifactId.
 
Si queremos compilar, testear, crear un jar, etc.. ejecutariamos 
mvn install
 
Ojo! en ese caso install NO ES un plugin sino una fase del ciclo de vida (lifecycle) de Maven. La gestión un proyecto
con Maven es un ciclo de vida completo, y en este caso el install no es más que una fase concreta de ese ciclo. Hay una fase
llamada package que se encarga de empaquetar todo el software. Las fases lo que llevan consigo son determinadas tareas
y así por ejemplo la fase package utilizará entre otras el goal/tarea jar:jar. (plugin jar, goal jar). Vamos, que la fase es algo más general
y un goal una tarea concreta que puede formar parte de una fase. 
 
En el caso de Mvn install
 
Al fichero pom.xml podemos ir añadiendo más cosas como las dependencias para que ejecute test unitarios. Aparte del
fichero pom.xml del proyecto Maven también llevará a cabo las tareas ficheros POM superiores si los hay o un fichero POM
general que trae la distribución de Maven.
 
Si utilizamos un IDE como netbeans o eclipse (desde la versión Kepler) podremos crear directamente proyectos Maven.
 
Espacio Maven
Algo curioso en Maven es que se considera una especie de lugar multidimensional lleno de artefactos. Y para localizar
algo en un sistema así ¿qué utilizamos? Pues está claro, unas coordenadas.. Los identificadores nos proveen de esas
coordenadas y son cruciales ya que puede que reutilicemos artefactos de un proyecto a otro y para eso necesitamos
localizarlos. Las coordenadas serían:
 
groupId:artifactId:packaging:version
 
por ejemplo: si en el pom tenemos:
  <groupId>info.pello.maven</groupId>
  <artifactId>xsdvalidator</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>info.pello.maven.xsd</name>
  
Las coordenadas serían:
info.pello.maven:xsdvalidator:jar:0.0.1-SNAPSHOT
 
Los repositorios
Maven se va descargando lo que necesita según las necesidades del proyecto. Una vez bajado no necesitará volver
a bajarse algo, pero las primeras veces veremos que se baja ciento y la madre de paquetes ya estemos en la consola
o con el eclipse. Existe un repositorio central y oficial de Maven, más el repositorio local que se va creando conforme
nos bajamos cosas o desarrollamos proyectos.
 
La gestión de la dependencia
Esto es la crema. Uno de los quebraderos de cabeza al desarrollar con java, aunque uses un IDE estupendo es que
necesitas bajarte la librería j_la_madre_que_te_trajo.jar y el paquete j_tu_padre_que_tal_mea.jar. Ese trabajo sucio
es algo que maven hace por ti, y lo que es mejor, si existen dependencias extra (transitivas) te las resuelve.
Si en el fichero pom.xml hemos indicado alguna dependencia como junit, Maven se encargará.
 
Un validador de XML Schema
Como ejemplo he creado un proyecto muy simple, un validador de ficheros XSD. Lo del validador no es más que un churro
con un main. Se puede hacer mucho más bonito y tal pero la cosa ahora era ver todo dentro de un proyecto Maven.
En este caso he utilizado eclipse, y la verdad es que se ha portado bien. Al crear un proyecto Maven efectivamente es 
un proyecto Maven y se puede hacer install, build, etc... y por supuesto ejecutarlo.
Puedes verlo/bajártelo en :
http://code.google.com/p/erps-2dam-4vientos/source/browse/#svn%2Ftrunk%2Fxsdvalidator
 
¿Qué es XML Schema? Es otro tema que lo iniciaré en otro post, básicamente te permite definir tipos de documento
XML, vamos algo así como un DTD pero muchísimo mejor.
En este caso el Schema es el siguiente:
<?xml version="1.0"?>
<!--
hello.xsd a very simple XSD sample with just one elements
-->
 
<xs:schema version="1.0"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           elementFormDefault="qualified">
    <xs:element name="greet" type="xs:string" />
</xs:schema>
 
Y el XML correspondiente:
<?xml version="1.0" encoding="UTF-8"?>
<!--
To change this template, choose Tools | Templates
and open the template in the editor.
-->
<greet
    xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
    xsi:noNamespaceSchemaLocation='hello.xsd'>
    Hello XSD
</greet>
 
Y por último la clase java que toma los dos ficheros y nos dice si el XML es válido:
 
 
package info.pello.mvn.xsd;
 
import javax.xml.XMLConstants;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.*;
 
import org.xml.sax.SAXException;
import java.io.File;
 
/**
 * XSD file validator sample in a project using Maven:
 * To execute goto maven dir and:
 * java -cp target/xsdvalidator-0.0.1-SNAPSHOT.jar info.pello.mvn.xsd.XSDValidator
 * @author Pello Altadill
 * @greetz you, for reading this.
 */
public class XSDValidator {
 
/**
* @param args
*/
public static void main(String [] args)  {
// First we load both the schema and the xml to validate
// sorry for the hardcoded file locations...
       Source schemaFile = new StreamSource(new File("src/main/resources/hello.xsd"));
       Source xmlFile = new StreamSource(new File("src/main/resources/hello.xml"));
       
       try{
        SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema = schemaFactory.newSchema(schemaFile);
        Validator validator = schema.newValidator();
           validator.validate(xmlFile);
           System.out.println(xmlFile.getSystemId() + " file is VALID XML");
       } catch (SAXException saxe) {
           System.out.println(xmlFile.getSystemId() + " file is NOT VALID");
           System.out.println("Message: " + saxe.getLocalizedMessage());
       } catch (Exception e) {
           System.out.println("Message: " + e.getMessage());
       }
   }
 
 
}
 

 

Intro a la DI
Una de las mayores ventajas de las pruebas unitarias, más incluso que la posibilidad de testear las aplicaciones es sin duda el hecho de que desarrollar las pruebas unitarias te lleva de manera irremediable a desarrollar un mejor código: como poco te obliga a crear clases con métodos que hacen una tarea muy concreta. También está la posibilidad de desarrollar partiendo de las pruebas (TDD) con un par.
 
Muchas veces cuando tratas de aprender algo una cosa lleva a la otra. Si tratas de profundizar en frameworks como mockito  para falsear objetos en los tests unitarios (mock objects) cuando buscas ejemplos te empiezas a encontrar que necesitas ir más allá en el desacoplamiento de clases y aparece la DI (Dependency Injection). Y te encontrarás con el clásico artículo de Fowler
http://martinfowler.com/articles/injection.html que bueno en fin, es un gurú pero digamos que no es para todos los públicos. Fowler pone como ejemplo el framework Spring, una herramienta que ahora hace mil cosas pero cuya función primigenia es facilitar la inyección de dependencias. 
 
 
Los orígenes de Spring se introducen por Rod Johnson en el libro Expert One-to-One: J2EE Design and Development, en especial en el capítulo 11 si mal no recuerdo. Ahí habla de los BeanFactories, el Application Context y de ficheros XML con los que generamos instancias y asignamos propiedades.
Bueno, yo lo que sugiero es echar un ojo a este ejemplo que es más simple:
 
/**
* Representa a un mensajero de una empresa
* de mensajería.
* @author Pello Altadill
* @greetz Taladros Friend's
*/
public class Mensajero  {
 
 private Furgoneta vehiculo = new Furgoneta();
 
 public Mensajero () {
 }
 
 public void desplazarse () {
  vehiculo.moverse();
 }
 
}
 
El mensajero necesita obviamente un vehículo, y por tanto tiene un atributo que él mismo
se encarga de instanciar. Hay un problema claro, y es que el Mensajero se limita a
desplazarse con una furgoneta y no podrá usar ningún otro vehículo. Encima no podremos
testear que realmente se llama al método moverse() del atributo vehículo que es privado.
A pesar de todo necesitamos que las clases hablen cooperen entre ellas pero debemos procurar
no acoplarlas tanto.
 
Para conseguir ese equilibrio usamos la DI que básicamente consiste en que esas dependencias se asignarán
desde fuera de la clase Mensajero. Hay dos formas básicas de hacerlo, a través de un parámetro en el constructor
o a través de métodos set. Además no vamos a asignar una clase concreta sino un interfaz lo cual nos va a facilitar
dos cosas: poder meter distintos tipos de vehículo (que deben implementar ese interfaz) y simplificar
el testeo del método desplazarse.
 
Este sería el interfaz Vehículo:
/**
* Interfaz para vehículos
* @author Pello Altadill
* @greetz Central
*/
public interface Vehiculo {
 
 public void moverse();
}
 
Y esta una implementación de ese interfaz:
 
/**
* Implementamos un vehículo con la clase furgoneta.
* @author Pello Altadill
* @greetz Tejedor
*/
public class Furgoneta implements Vehiculo {
 private int deposito;
 
 public void moverse() {
  if (deposito > 0)
   deposito--;
 }
}
 
Ahora cambiamos al mensajero por esto:
/**
* Representa a un mensajero de una empresa
* de mensajería. Version desacoplada
* @author Pello Altadill
* @greetz Bar Taladros crew
*/
public class Mensajero  {
 
private Vehiculo vehiculo;
 
 public Mensajero (Vehiculo vehiculo) {
  this.vehiculo = vehiculo;
 }
 
 public void desplazarse () {
  vehiculo.moverse();
 }
 
}
 
Ok, es muy simple como se puede apreciar. El vehículo se asignara desde fuera. Y como se puede ver hasta ahora
no hemos creado clases complejas, todo tiende a POJOs, (Plain Old Java Objects). Con Spring, usando un fichero XML
podremos instanciar Vehículo (un objeto de la clase Furgoneta) e inyectarla en una nueva instancia de Mensajero.
 
Aparte de conseguir desacoplar las clases, facilitaremos que se pueda testear el método desplazarse, ya que ahora
tenemos acceso al Vehículo que se utiliza, es un interfaz y podemos mockearlo o falsearlo en el test. Si lo que queremos
es comprobar que realmente se llama al método moverse() hariamos algo así:
 
import static org.mockito.Mockito.*;
import org.junit.Test;
/**
* Testeando el Método desplazarse de Mensajero
* @author Pello Altadill
* @greetz UUuuuOOooo
*/
public class MensajeroTest {
 @Test
 public void desplazarseLlamaAMoverse () {
  // Creamos el mock para las pruebas
  Vehiculo vehiculoFalso = mock(Vehiculo.class);
 
  // Inyectamos esa instancia para la prueba
  Mensajero mensajero = new Mensajero(vehiculoFalso);
  mensajero.desplazarse();
 
  // AHORA SÍ, podemos verificar si se llama a moverse una vez
  verify(vehiculoFalso, times(1)).moverse();
 }
}
 
Bueno, "Talk!, Talk is for lovers Merlin!! I need a Spring sample to be King!!"
Esto, ejem.. quiero decir que este post pretendía introducir de alguna forma la DI o inyección de dependencia
y tratar de vender los muchos beneficios de Spring que así a botepronto se me ocurren tres:
 - Desacoplar las clases y facilita que todo sean POJOs y te centres en el negocio
 - Facilitar los tests unitarios
 - Permite introducir los aspectos (AOP), cosa que es crema máxima. Otro día.

There's two ways to go on this job: my way or the highway.
Ese podría ser el lema de Maven, o como decía la versión traducida de Reservoir Dogs
"Hay dos formas de hacer el trabajo, la mía o la puta calle."

Comienzo con esa cita porque, para qué nos vamos a engañar, esa escena es genial y esa es la típica frase que a veces nos vemos tentados a soltar pero no hacemos por ser correctos. Maven es un poco así, te obliga a hacer las cosas a su manera, pero todo es por tu bien.


Si bien se puede personalizar su forma de funcionar la gente que lo usa te sugiere que mejor no empeñarse en retorcerlo a nuestro gusto.
Deja que Maven conduzca...

La estructura de directorios de un proyecto Maven sería así, no hay que hacerla a mano te la puede generar el propio
Maven o un IDE.

Proyecto/pom.xml
Proyecto/src
Proyecto/src/main
Proyecto/src/main/java/Hola.java
Proyecto/src/main/resources
Proyecto/src/test


Tras ejecutar mvn install, se ejecutarían las siguientes tareas por defecto: procesar recursos si los hubiera, compilar código, compilar y pasar test unitarios si las hubiero, generar un fichero jar e instalarlo en el repositorio local para que otros proyectos lo usen.

En el proyecto se generaría la carpeta Target con el resultado:

Proyecto/target
Proyecto/target/Hola-1.0.jar
Proyecto/target/classes/Hola.class


El contenido del fichero pom.xml es el siguiente, muy mínimo:

4.0.0
info.pello.maven.hellomaven
00HelloMaven
1.0


Maven es mucho más que una herramienta para automatizar la compilación de código. Se considera como una herramiento para gestionar proyectos de software. Aunque en un primer vistazo nos encontramos con un fichero xml y un comando que pone todo en marcha (como ant), Maven va mucho más allá. Partiendo
de un interfaz único Maven es capaz de resolver las dependencias  de los componentes del proyecto y reutiliza infinidad de plugins para resolver cualquier tarea.

La base de Maven es muy pequeña pero sin embargo según las tareas que se le requieran de forma automática se descarga todos los plugins necesario de un repositorio central a uno local. La mayor parte de las tareas de Maven la realizan esos plugins.

Los IDEs tienen soporte para crear proyectos basados en Maven. Netbeans desde hace más tiempo que Eclipse, que por fin
en la versión Kepler parece traer proyectos Maven de serie. Y si es una forma unificada de configurar un poryecto, solamente por eso merece la pena usarlo.


¿Qué es XML?
XML: eXtensible Markup Language
es un subconjunto del SGML (un lenguaje de marcas de propósito general) y es el lenguaje que la w3c recomienda para crear lenguajes de marcas orientados a determinadas funciones. Es una especie de lenguaje como HTML pero en el que las etiquetas las inventamos nosotros.
 
Dicho de otra forma:

XML es un lenguaje de marcas con el que tenemos libertad total para decidir que etiquetas y atributos metemos. Su principal aplicación es la de intercambio de datos entre sistemas y aplicaciones.
Este es el aspecto de un fichero XML
<?xml version="1.0" encoding="UTF-8"?>
<libro>
    <titulo>Neuromante</titulo>
    <autor>William Gibson</autor>
    <isbn>84-450-7405-9</isbn>
    <fecha edicion=”2”>1985-12-4</fecha>
    <paginas>450</paginas>
</libro>

¿Y para que necesito esto?

  • Es el lenguaje ideal para el intercambio de información ya que tiene estas ventajas
  • Es texto plano
  • Es legible por humanos y máquinas
  • Es muy simple
  • Es muy útil para representar tipos de datos (registros, arboles, listas)
  • Es un estándar abierto
  • La mayoría de lenguajes tienen librerías para tratar XML


Cómo hacer que XML siga un esquema determinado: DTD

(Igual esquema no es la palabra apropiada, pero en fin)
Si nos interesa podemos hacer que un documento XML cumpla un determinado esquema de etiquetas y atributos. Ese esquema esta definido por un DTD. Esa definición puede ir dentro del XML o estar referenciado así:
<!DOCTYPE libro SYSTEM "libros.dtd">

El DTD es un Document Type Definition, en definitiva un fichero que indica el formato que debe tener un contenido XML, indicando las etiquetas, atributos, jerarquía etc..

Este sería un ejemplo de DTD para el XML anterior:

<!ELEMENT libro (titulo,autor,isbn,fecha,paginas)>
    <!ELEMENT titulo (#PCDATA) >
    <!ELEMENT autor (#PCDATA) >
        <!ELEMENT isbn (#PCDATA) >
    <!ELEMENT fecha (#PCDATA) >
        <!ATTLIST fecha edicion CDATA "1">
    <!ELEMENT paginas (#PCDATA) >

Requisitos que debe cumplir un fichero XML
Estas son las propiedades que debe tener un fichero XML considerarse como tal:

1.-Debe estar BIEN FORMADO (well formed)
 -Siempre debe tener un elemento raíz
 -Todas las etiquetas deben cerrarse
 -Todos los atributos llevan comillas ' o “.
 -Las etiquetas se anidan pero nunca se solapan
 -Las etiquetas son case-sensitive, no hay que mezclar mayúsculas con minúsculas

2.-En caso de depender de un DTD, debe ser VÁLIDO

Una ventaja muy  obvia de XML es que podemos crear un fichero de propiedades, de configuración, de lo que sea y no necesitamos crear desde 0 código para verificar y validar que ese fichero es correcto. Todo ese trabajo nos lo dan las librerías de las que disponemos para gestionar XML. Por ejemplo en java si abrimos un fichero xml y esté no está bien formado o no es válido nos los dirá de forma inmediata.

Los DTD se quedan un poco cortos si necesitamos un mayor control de los tipos de datos que metemos en el fichero XML. Con un DTD podemos decir que vale, en tal etiqueta hay un texto, pero ese texto puede ser cualquier cosa. ¿Podemos controlar que sea número, texto o que siga un patrón predeterminado?

La respuesta es XSD, pero eso amigos, es otra historia para otro día.

xml sigue vigente, y de qué manera. Basta con ver android y sus inifinitos ficheros XML para configurar hasta el último texto de la etiqueta más remota. También en muchos frameworks son frecuentes los ficheros de configuración aunque hay cierta lucha con la opción XML o las anotaciones. En un principio cualquier cosa configurable se hacía en un fichero XML aparte, y de tanto exceso hay quien tratade volver atrás.



Recientemente me disponía a poner orden en la sección de descargas y software de este sitio donde no hay más que vaporware, pruebas de concepto y algunas ideas disparatadas. Casi todas ellas están dejadas ahí por si a alguien le servía algo como idea o por si algún trozo de código podía ser útil para uno mismo: cómo leer un fichero properties, cómo hacer el tema de locales en php, cómo hacer un bot de irc en perl para tomar el pelo a los salidos, en fin un cajón desastre.

Uno de las tonterías que tenía por ahí era una vieja idea de montar un motor de inferencia propio. La inteligencia artificial fue de las últimas asignaturas de la carrera que se me atravesó un poco con uno de los exámenes más jodidos de todos al menos en la facultad de Donosti: mezclaba programación funcional en lisp, programación lógica con prolog y programar una especie de sistemas expertos sencillos en clisp, y no recuerdo qué cosas más -oscuras o mejor dicho difusas ;)-. Un horror. Es una asignatura que bien dada puede ser fascinante pero tal cual te la dan lo que acabas haciendo es procurar preparar el examen, más que aprender algo útil.

El caso es que el tema de los motores de inferencia, los sistemas expertos me parecía algo más interesante para aplicar a problemas reales. Por ejemplo tenía la idea de crear un administrador de sistemas virtual que en función del estado del sistema fuera capaz de tomar decisiones y aplicar los  comandos oportunos.Esto quizá vino inspirado por una peli de serie B llamada Webmaster donde sale algo parecido: un sysadmin que transmite sus ordenes a un ente virtual llamado EGO que va aprendiendo de su amo y mantiene y supervisa los servidores.

Comencé desarrollando una especie de motor de inferencia en perl cuyo código debe andar por sourceforge desde al año 2000 (madre del amor hermoso) y que lo mismo ya está desaparecido. Si lo viera sería incapaz de reconocer la paternidad del mismo ya que perl es un lenguaje que por mucho que te esfuerces en ponerlo claro queda inevitablemente sucio (demasiados caracteres especiales).

En el 2001 la humanidad todavía no había llegado a Jupiter como predijo Clarke, así que me dediqué a esa misma idea y la traté de pasar a Java manteniendo en mismo nombre, inferenczy,evidente para mí pero sin duda rebuscado para cualquiera que no haya leído la saga del Necroscopio de Brian Lumley, o sea la gran mayoría de la gente normal informática o no. Justo cuando estaba revisando ese código me encontré que inferenczy estaba montado sobre otro proyecto que pensaba que estaba perdido sin remedio: Ozone.

OOO = O3 = Ozone
Era por las tres O, de Object Oriented Operative System. Tras ese nombre tan pretendidamente molón lo que trataba de hacer es un sistema operativo hecho enteramente en java, todo de consola claro. En su día ya existía alguna cosa así JavaOS se me ocurre y seguramente ahora habrá miles de ellos. Me ha sorprendido un poco cómo compliqué el desarrollo de ese sistema seguramente inspirado en el entorno de trabajo que me rodeaba en esa época: en Madrid trabajando para una una de las más célebres cárnicas (AXPE) que a su vez me tenía en la mítica picadora de carne llamada Andersen (¡¡toma combo breaker!!). Ese era un proyecto en el que los pérfidos gerentes de Andersen le habían vendido a Safei un proyecto monstruoso con un framework del mal donde TODO era XML y donde se metía lo típico de la época: Java, Oracle, EJB, Unix (no linux) y ante todo XML everywhere: los formularios web estaban definidos en xml. Las consultas a BBDD estaban definidas en xml, etc... una época en la que, te lo creas o no, todavía se fumaba en la oficina.

Así que Ozone e inferenczy iban a usar XML. Y por supuesto JDBC, que siempre queda más profesional. Como digo hace unas semanas me bajé el código y eché un ojo a los ficheros de log que generaba el sistema y esto fue lo que me encontré:
Wed Sep 05 11:30:06 CEST 2001> <Kernel> Kernel loaded.
Wed Sep 05 11:30:06 CEST 2001> <Kernel> starting init...
Wed Sep 05 11:30:06 CEST 2001> <Init> Init started. [pid: 1]
Wed Sep 05 11:30:06 CEST 2001> <Init> Init completed succesfully.
Wed Sep 05 11:30:06 CEST 2001> <DBConnection> starting...
Wed Sep 05 11:30:06 CEST 2001> <DBConnection> Connected to DB
Wed Sep 05 11:30:06 CEST 2001> <DBConnection> DB Operations interface set to: SQL
Wed Sep 05 11:30:06 CEST 2001> <DBOperationsSQL> SQL SELECT successful: select * from personas
Wed Sep 05 11:30:06 CEST 2001> <DBConnection> started. [pid: 2]

Madre mía, 5 de septiembre de 2001. Y al poco se ve esto:

Thu Sep 06 08:19:06 CEST 2001> <Kernel> Kernel loaded.
Thu Sep 06 08:19:06 CEST 2001> <Kernel> starting init...
Thu Sep 06 08:19:06 CEST 2001> <Init> Init started. [pid: 1]
Thu Sep 06 08:19:06 CEST 2001> <Init> Init completed succesfully.
Thu Sep 06 08:19:06 CEST 2001> <DBConnection> starting...
Thu Sep 06 08:19:07 CEST 2001> <DBConnection> Connected to DB
Thu Sep 06 08:19:07 CEST 2001> <DBConnection> DB Operations interface set to: SQL
Thu Sep 06 08:19:07 CEST 2001> <DBConnection> started. [pid: 2]
Wed Sep 12 11:35:06 CEST 2001> <Kernel> Kernel loaded.
Wed Sep 12 11:35:07 CEST 2001> <Kernel> starting init...
Wed Sep 12 11:35:07 CEST 2001> <Init> Init started. [pid: 1]
Wed Sep 12 11:35:07 CEST 2001> <Init> Init completed succesfully.
Wed Sep 12 11:35:07 CEST 2001> <DBConnection> starting...
Wed Sep 12 11:35:07 CEST 2001> <DBConnection> Can't connect to DB
Wed Sep 12 11:35:07 CEST 2001> <DBConnection> DB Operations interface set to: SQL
Wed Sep 12 11:35:07 CEST 2001> <DBConnection> started. [pid: 2]

Parece que tras el 11 de septiembre el mundo se derrumbaba pero yo seguía a mi rollo. ¡¡Oh cielos no! ¡¡No me conectaba a la BBDD!!

El caso es que, años después, lo volvía a ejecutar tirando de un script BAT que había para tal efecto. Y esto sucedió:
Tue Sep 18 09:26:00 CEST 2001> <Kernel> Kernel loaded.
Tue Sep 18 09:26:00 CEST 2001> <Kernel> starting init...
Tue Sep 18 09:26:00 CEST 2001> <Init> Init started. [pid: 1]
Tue Sep 18 09:26:00 CEST 2001> <SystemXMLReader> Init Data/etc/init.xml parsing.
Tue Sep 18 09:26:00 CEST 2001> <SystemXMLReader> parsed.
Tue Sep 18 09:26:00 CEST 2001> <Init> Loading com.javamercenary.ai.inferenczy.core.DBConnection...
Tue Sep 18 09:26:00 CEST 2001> <Init> Init completed succesfully.
Tue Sep 18 09:26:00 CEST 2001> <DBConnection> starting...
Tue Sep 18 09:26:00 CEST 2001> <DBConnection> Can't connect to DB
Tue Sep 18 09:26:00 CEST 2001> <DBConnection> DB Operations interface set to: SQL
Tue Sep 18 09:26:00 CEST 2001> <DBConnection> started. [pid: 2]
<LOG><DATE>Tue Sep 18 09:28:26 CEST 2001</DATE><MESSAGE><Kernel> Kernel loaded.</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:26 CEST 2001</DATE><MESSAGE><Kernel> starting init...</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:26 CEST 2001</DATE><MESSAGE><Init> Init started. [pid: 1]</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:26 CEST 2001</DATE><MESSAGE><SystemXMLReader> Init Data/etc/init.xml parsing.</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:27 CEST 2001</DATE><MESSAGE><SystemXMLReader> parsed.</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:27 CEST 2001</DATE><MESSAGE><Init> Loading com.javamercenary.ai.inferenczy.core.DBConnection... </MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:27 CEST 2001</DATE><MESSAGE><Init> Init completed succesfully.</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:27 CEST 2001</DATE><MESSAGE><DBConnection> starting...</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:27 CEST 2001</DATE><MESSAGE><DBConnection> Can't connect to DB</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:27 CEST 2001</DATE><MESSAGE><DBConnection> DB Operations interface set to: SQL</MESSAGE></LOG>
<LOG><DATE>Tue Sep 18 09:28:27 CEST 2001</DATE><MESSAGE><DBConnection> started. [pid: 2]</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:48 CEST 2013</DATE><MESSAGE><Kernel> Kernel loaded.</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:48 CEST 2013</DATE><MESSAGE><Kernel> starting init...</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:48 CEST 2013</DATE><MESSAGE><Init> Init started. [pid: 1]</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:48 CEST 2013</DATE><MESSAGE><SystemXMLReader> Init Data/etc/init.xml parsing.</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:49 CEST 2013</DATE><MESSAGE><SystemXMLReader> parsed.</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:49 CEST 2013</DATE><MESSAGE><Init> Loading com.javamercenary.ai.inferenczy.core.DBConnection... </MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:49 CEST 2013</DATE><MESSAGE><DBConnection> starting...</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:49 CEST 2013</DATE><MESSAGE><Init> Init completed succesfully.</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:49 CEST 2013</DATE><MESSAGE><DBConnection> Can't connect to DB</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:49 CEST 2013</DATE><MESSAGE><DBConnection> DB Operations interface set to: SQL</MESSAGE></LOG>
<LOG><DATE>Fri May 31 23:47:49 CEST 2013</DATE><MESSAGE><DBConnection> started. [pid: 2]</MESSAGE></LOG>

Hay dos cosas: se ve que el 18 de septiembre, inmerso en la locura de XML cambié el formato del fichero de log usando tags (MAL, en mayúsculas) con la vaga idea de que quizá en algún momento podría interesar parsear los ficheros de log para facilitar su análisis. Cosa que sí es útil. La revisión y procesamient de logs puede servir para sacar estadísticas por ejemplo como hacen algunos analizadores web como webalizer.

La otra cosa, algo más anecdótica es el salto temporal en el fichero de log desde:
Tue Sep 18 09:28:27 CEST 2001
Hasta
Fri May 31 23:47:48 CEST 2013

Madre mía, 12 años nos contemplan. Y el código seguía funcionando en una máquina virtual mucho más moderna.

Bueno, en lugar de empezar poniendo un diagrama de clases, voy a ir a haciendo algo de ingeniería inversa para poder entender cómo narices funcionaba esto. Y todo parte de dos ficheros *.bat, uno para compilar y el otro para ejecutar. Efectivamente, ignoraba la existencia de herramientas como ANT, si es que existían entonces que no lo sé. El caso es que el fichero de compilación hacía lo siguiente:
@echo off
@REM Copyright (c) 2000 Pedro Alcazar, Inc. All Rights Reserved.

@REM Adjust these variables to match your environment
set CLASSES=src/core/*.java src/api/*.java src/ie/*.java

set PATH=%PATH%;c:\jbuilder4\jdk1.3\bin
if "" == "%JAVA_HOME%" set JAVA_HOME=c:\jbuilder4\jdk1.3

set MYCLASSPATH=%JAVA_HOME%\lib\classes.zip;.;c:\weblogic\lib\parser.jar

echo  Build %CLASSES% ...
javac -d .  -classpath %MYCLASSPATH% %CLASSES%
echo  [ DONE ]
pause


Madreeee! ¡El Jbuilder! ¡El jdk1.3! ¡El weblogic!  ¿Y quién era Pedro Alcazar? ¡Era yo! Si quieres saber por qué... pues en fin.

A ver el de ejecución:

@echo off
@REM Copyright (c) 2000 Pedro Alcazar, Inc. All Rights Reserved.

@REM Adjust these variables to match your environment
set STARTCLASS=com.javamercenary.ai.inferenczy.core.Boot

set PATH=%PATH%;c:\jbuilder4\jdk1.3\bin
if "" == "%JAVA_HOME%" set JAVA_HOME=c:\jbuilder4\jdk1.3

set MYCLASSPATH=%JAVA_HOME%\lib\classes.zip;./lib/parser.jar;.

echo running %STARTCLASS%
java -classpath %MYCLASSPATH% %STARTCLASS%

pause

Bueno vale, parecido pero ya vemos un inicio:
com.javamercenary.ai.inferenczy.core.Boot

Cómo mola lo de javamercenary, y lo de core, jajaja, qué pretencioso es todo. Bueno, a ver qué narices hacía la clase Boot:

package com.javamercenary.ai.inferenczy.core;
 
/**
 * Título:       Inference Engine
 * Descripcion:
 * Copyright:    Copyright (c) 2001
 * Empresa:
 * @author P. Al.
 * @version 1.0
 */
 
 public class Boot  {

     
     /**
     * method
     */
    Boot () {
    }
        
    
    public static void main (String args[]) {
        System.out.println("INFeReNCZy (c) P. Al. Madrid September 2001");
        System.out.println("INFeReNCZy Bootstrap...");
        new Kernel().start();
    }

 }

Los comentarios javadoc son lo que te metía el jbuilder. Vale, se ve hay un Kernel quese inicia, y tiene pinta de ser un hilo. Vamos a verlo:

package com.javamercenary.ai.inferenczy.core;

/**
 * Título:       Inference Engine
 * Descripcion:
 * Copyright:    Copyright (c) 2001
 * Empresa:
 * @author P. Al.
 * @version 1.0
 */
 import java.util.Hashtable;
 import java.util.Vector;
 import java.sql.Connection;
 
 public class Kernel extends Thread {
     
     /**
     * KERNEL ATTRIBUTES
     */
     // process table
    private final static Hashtable _PROCESSES = new Hashtable();
    // system properties: loglevel, multiuser,...
    private InferenczyProperties _SYSTEM_PROPERTIES = new InferenczyProperties();
    // system interface
    private SystemInterface _SI = null;
    // log service
    private static final Log _LOG = new Log();
    // console output
    private static final Printer _PRINTER = new Printer();
    // XML Reader
    private static final SystemXMLReader _XML_READER = new SystemXMLReader();
    // database connection
    private Connection _DBCONNECTION = null;
    // database operations interface
    private DBOperationsInterface _DBOPERATIONS_INTERFACE = null;
     
     /**
     * method
     */
    Kernel () {
    }
        
    /**
    * run the Kernel
    */
    public void run () {
        // first of all, load system properties.
        if (!this.loadProperties()) {
            System.err.println("Failed to load Properties file.");    
        }
        _SI = new SystemInterface(this);
        //setting properties.
        _LOG.setProperties(_SI);
        _PRINTER.setProperties(_SI);    
        _XML_READER.setProperties(_SI);    
        _LOG.log(1,"<Kernel> Kernel loaded.");
        // now run first process: init (pid:1)
        _LOG.log(1,"<Kernel> starting init...");
        startProcess("com.javamercenary.ai.inferenczy.core.Init",1,1,1,1,1,"Init program");
    }
    
    /**
    * loadProperties
    */
    private boolean loadProperties () {
        return _SYSTEM_PROPERTIES.loadProperties();
    }
    
    /**
    * getProperties
    */
    public InferenczyProperties getProperties () {
        return  _SYSTEM_PROPERTIES;
    }
    
    /**
    * startProcess
    * return 0 if everything works well
    */
    public int startProcess (String name,int PID,int UID,int GID,int STATE,int PRIORITY, String P_INFO) {
        Object o = null;
        try {
        o = Class.forName(name).newInstance();    
        } catch (ClassNotFoundException cnfe) {
            _LOG.log(1,"Error on instantiation. Class not Found."+cnfe.getMessage());
            return -1;
        } catch (IllegalAccessException iae) {
            _LOG.log(1,"Error on instantiation.Illegal Access"+iae.getMessage());
            return -2;
        } catch (InstantiationException ie) {
            _LOG.log(1,"Error on instantiation."+ie.getMessage());
            return -3;
        }
        try {
        if (((Process) o).setAttributes(PID,UID,GID,STATE, PRIORITY,P_INFO,this._SI)) {
            addProcess(((Process)o));
            ((Process) o).start();            
        }
        return 0;
        } catch (Exception e) {
            removeProcess(new Integer(((Process)o).getPID()));
            _LOG.log(1,"Error on instantiation."+e.getMessage());
            return -4;
        }
        
    }

/**
* addProcess
*/
 private boolean addProcess (Process p) {
     try {
    _PROCESSES.put(new Integer(p.getPID()),p);     
        return true;
    } catch (Exception e) {
        _PRINTER.print(-1,"Error starting process.\n");
        return false;
    }
}

/**
* removeProcess
*/
 public boolean removeProcess (Integer pid) {
     try {
    _PROCESSES.remove(pid);     
        return true;
    } catch (Exception e) {
        _PRINTER.print(-1,"Error deleting process.\n");
        return false;
    }
}

/**
* getProcessTable
*/
 public Hashtable getProcessTable () {     
    return _PROCESSES;     
 }

/**
* log
*/
public boolean log (int loglevel, String message) {
    return _LOG.log(loglevel,message);
}

/**
* print
*/
public void print (int flag, String message) {
     _PRINTER.print(flag,message);
}


/**
* setDBConnection
*/
public void setDBConnection (Connection conn) {
    this._DBCONNECTION = conn;    
}

/**
* getDBConnection
*/
public Connection getDBConnection () {
    return this._DBCONNECTION;    
}

/**
* setDBOperations
*/
public void setDBOperationsInterface (DBOperationsInterface dbo) {
    this._DBOPERATIONS_INTERFACE = dbo;    
}

/**
* getDBOperations
*/
public DBOperationsInterface getDBOperationsInterface () {
    return this._DBOPERATIONS_INTERFACE;    
}

/**
* readData
* @param String filename
* @param String[] elements
* @return Hashtable
*/
public Hashtable readData (String filename, String[] elements) {
    return this._XML_READER.initParse(filename,elements);
}

/**
* readData
* @param String filename
* @param String field
* @return Hashtable
*/
public Vector readData (String filename,String field) {
    return this._XML_READER.initParse(filename,field);
}
}//end kernel


Vaya vaya, si es una especie de sistema operativo.
Tiene properties, un sistema de logs, un printer que será para sacar mensajes por algún lado, una tabla de procesos, y un método para crear nuevos procesos que hace uso del Reflection. Y aparte de getters y setters tiene como peculiar un interfaz para una BBDD y un lector de XML.

Básicamente el Kernel pone en marcha nuevas instancias de programas que heredan de la clase Process, la cual tiene esta pinta:
package com.javamercenary.ai.inferenczy.core;

/**
 * Título:       Inference Engine
 * Descripcion:
 * Copyright:    Copyright (c) 2001
 * Empresa:
 * @author P. Al.
 * @version 1.0
 */
 
 public abstract class Process extends Thread {
 
     /**
     * attributes
     */
     private int PID = -1 ;
     private int UID = 0;
     private int GID = 0;
     private int STATE = 0;
     private int PRIORITY = 0;
     private String P_INFO = "";
     private long START_DATE;
     private SystemInterface _SI = null;
     
     /**
     * constructors
     */
     public Process () {
         this.START_DATE = System.currentTimeMillis();
    }

     public Process (int PID,int UID,int GID,int STATE,int PRIORITY, String P_INFO, SystemInterface _SI) {
         this.PID = PID;
         this.UID = UID;
         this.GID = GID;
         this.PID = PID;
         this.PRIORITY = PRIORITY;
         this.P_INFO = P_INFO;
         this._SI = _SI;
    }

     public boolean setAttributes (int PID,int UID,int GID,int STATE,int PRIORITY, String P_INFO, SystemInterface _SI) {
         if (PID == -1) return false;
         
         this.PID = PID;
         this.UID = UID;
         this.GID = GID;
         this.PID = PID;
         this.PRIORITY = PRIORITY;
         this.P_INFO = P_INFO;
         this._SI = _SI;
         return true;
    }

     public  int getPID (){
         return PID;
     }
          
     public  int getUID () {
         return UID;
     }
     
     public  int getGID () {
         return GID;
     }
     
     public  int getSTATE () {
          return STATE;
    }
    
     public  int setSTATE (int state) {
         return STATE = state;
     }
     
    public  int getPRIORITY () {
         return PRIORITY;
    }
    
    public  int setPRIORITY(int priority) {
        return PRIORITY = priority;
    }
    
    public  long getSTART_DATE() {
        return START_DATE;
    }
    
    public  String getP_INFO () {
        return P_INFO;
    }
    
    public  SystemInterface get_SI () {
        return _SI;
    }
        
    public  String setP_INFO(String p_info) {
        return P_INFO = p_info;    
    }
 }

Un Thread.

El primer proceso que inicia el kernel es, como no podía ser menos Init. Y este hereda de process y lo que hace básicamente es cargar en el kernel una serie de procesos que tiene indicado dentro de init.d:

package com.javamercenary.ai.inferenczy.core;

/**
 * Título:       Inference Engine
 * Descripcion:
 * Copyright:    Copyright (c) 2001
 * Empresa:
 * @author P. Al.
 * @version 1.0
 */
 import java.util.Vector;

 public class Init extends Process {

     
     /**
     * method
     */
     public Init () {
         super();
     }

    public Init (int PID,int UID,int GID,int STATE,int PRIORITY, String P_INFO, SystemInterface _SI) {
        super(PID,UID,GID,STATE,PRIORITY,P_INFO, _SI);
    }
    
    /**
    * run  method
    */
    public void run () {
        get_SI().log(1,"<Init> Init started. [pid: "+this.getPID()+"]");
        loadProcesses();
        while (true) {
        }
    }

    /**
    * loadProcesses
    * load processes from init.d
    */
    private boolean loadProcesses () {
        String line = "";
        int cont = 2;
        boolean errors = false;
        Vector vclasses = null;
        
        try {
        vclasses = get_SI().readData(get_SI().getProperty("init.d"),"CLASSNAME");
        for (int i = 0;i<vclasses.size();i++) {
            get_SI().log(1,"<Init> Loading "+vclasses.elementAt(i)+"... ");
            if (get_SI().startProcess((String)vclasses.elementAt(i),cont++,1,1,1,1,line) < 0)
                errors = true;
        }
        if (errors)
            get_SI().log(1,"<Init> Init finished with errors. Check init.d file.");
        else
            get_SI().log(1,"<Init> Init completed succesfully.");
        return true;    
        } catch (Exception e) {
                get_SI().log(1,"Failed loadin properties .Error: "+e.getMessage());
            return false;
        }
    }
 }
El sistema gira en torno al kernel. Y en resumen los procesos como el init y los recursos del sistema se comunican con el kernel a través de un intermediario llamado SystemInterface. En el diagrama UML puedes hacerte una vaga idea de cómo está montado, y verás que las clases se acoplan y que todo es un poco infernal. Pero como digo... era joven e incosciente. Ahora ya soy simplemente joven y  plenamente inconsciente. ¿o era simplemente inconsciente?

Diagrama

Delirio total

Ojo que lo del sistema operativo no era más que una base para montar el motor de inferencia. Y lo pretencioso del tema es que quería crear programas escritos en XML. Es decir, era una especie de lenguaje de programación genérico, al que por ejemplo se le podría hipotéticamente aplicar un XSLT y convertir en un lenguaje concreto. Por ejemplo de un programa.xml podriamos tener un Programa.java y un Programa.cpp.

Todo el proyecto está ahora subido a google code. Echa un ojo a la carpeta Data donde encontrarás el DTD: http://code.google.com/p/ozone/

Para descargar por git:
git clone https://code.google.com/p/ozone/

O puedes descargar el zip original:

http://pello.info/filez/inf.zip

aunque existe otro original en alguna parte con otros procesos como el login y un shell... pero quién sabe dónde.


Java i18n Internationalization

¿Qué por qué se llama i18n? búscalo y verás, qué cosas.

A diferencia de otros lenguajes la internacionalización de programas en java, al menos la forma básica que trae la plataforma por defecto te permite hacer de todo (fechas, monedas, traducción de texto con parámetros) pero la forma es cuando menos engorrosa.

Lo que he puesto aquí es el típico churro que muestra cómo hacer varias cosas: detectar el locale del sistema, aplicar el que nosotros queramos, mostrar números y monedas según el locale, las fechas, etc. y por supuesto, mostrar mensajes en distintos idiomas, con o sin parámetros.

si ahondamos un poco en la documentación oficial veremos que hay dos tipos de internacionalizaciones, una es la que veremos aquí pero hay otra (ListResourceBundle) en la que hay que crear ficheros *.class para la internacionalización.

Otra cosa curiosa es que los ficheros properties con los mensajes deben estar en el classpath como si fueran clases. Por cada idioma que queremos meter debemos crear un fichero con mensajes, incluyendo uno que será el que se aplique en caso de no encontrar el locale elegido en el programa.

Messages.properties

Este sería el fichero por defecto:

# this is a comment
start = Starting program
end = Program ended
hello = Hello
name = My name is {0}
name2 = My name is {0} and Im {1,number,integer} years old \
and I was born in {2,date,long} at {2,time,short} \
and I weighed {3,number}

Message_en_US.properties

Este sería la versión inglesa-USA:

# this is a comment
start = Starting program
end = Program ended
hello = Hello
name = My name is {0}
name2 = My name is {0} and Im {1,number,integer} years old \
and I was born in {2,date,long} at {2,time,short} \
and I weighed {3,number}

Message_es_ES.properties

Y la versión española, ole ole y ole qué arte :)

# this is a comment
start = Iniciando programa
end = Programa terminado
hello = Hola
name = Mi nombre es {0}
name2 = Mi nombre es {0} y tengo {1,number,integer} años\
y nací el {2,date,long} a las {2,time,short}\
y pesé {3,number}

El programa

Un churro con todas las pruebas que se me ocurrían, aunque sé que faltan algunas.

import java.text.DateFormat;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.util.Currency;
import java.util.Date;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.Scanner;


/**
 * Shows how to write a program with messages in different languages
 * NOTE: message files (*.properties) must be located in the classpath
 * like a *.class file
 * @author Pello Altadill
 * @greetz fat-free milk drinker
 * http://butterbeliever.com/fat-free-dairy-skim-milk-secrets/
 */
public class InternationalizationSample {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        Locale locale = Locale.getDefault();
        String language = locale.getDisplayLanguage();
        String country = locale.getDisplayCountry();
        Scanner reader = new Scanner(System.in);
        Integer integerNumber = new Integer(160666);
        Double doubleNumber = new Double(1666.42);
        NumberFormat numberFormatter;
        DateFormat dateFormatter;
        Currency currency;
        
        String languageCode = System.getProperty("user.language");
        String countryCode = System.getProperty("user.country");
        System.out.println("Lang: " + language + ", country: " + country);
        System.out.println("Lang: " + languageCode + ", country: " + countryCode);
        
        System.out.println("Give me lang code (en, es, eu): ");
        languageCode = reader.nextLine();
        System.out.println("Give me country code (EN, ES, EU): ");
        countryCode = reader.nextLine();
        
        // Let's show messages in selected language
        ResourceBundle messages;

        System.out.println("Setting locales to: " + languageCode +"_"+countryCode);
        locale = new Locale(languageCode, countryCode);
        messages = ResourceBundle.getBundle("Messages", locale);

        System.out.println(messages.getString("start"));
        System.out.println(messages.getString("hello"));
        System.out.println(messages.getString("end"));
        
        numberFormatter = NumberFormat.getNumberInstance(locale);
        dateFormatter = DateFormat.getDateInstance(DateFormat.LONG, locale);
       // currency = Currency.getInstance(locale);
        
        System.out.println("Numbers: " + numberFormatter.format(integerNumber));
        System.out.println("Number Double: " + numberFormatter.format(doubleNumber));

        //numberFormatter = NumberFormat.getCurrencyInstance(locale);
        //System.out.println("Currency: " + currency);
        //System.out.println("Money: " + numberFormatter.format(doubleNumber));
        
        // PARAMETERS!!!
        // For messages with parameters we use MesssageFormat
        // This will be the param to fill de message
        Object[] messageParams = {
                "Prince"
            };
        MessageFormat formatter = new MessageFormat("");
        formatter.setLocale(locale);
        formatter.applyPattern(messages.getString("name"));
        System.out.println(formatter.format(messageParams));
        // Format samples: http://docs.oracle.com/javase/7/docs/api/java/text/MessageFormat.html
        Object[] messageParams2 = {
                "Prince",
                new Integer(45),
                new Date(),
                new Double(3.4)
            };
        formatter = new MessageFormat("");
        formatter.setLocale(locale);
        formatter.applyPattern(messages.getString("name2"));
        System.out.println(formatter.format(messageParams2));
    }

}
 

subscribe via RSS