====== Java Beans ======
===== Introducción =====
Los Java Beans -en castellano granos de café java- no son otra cosa que un modelo de componentes para Java. Se estandariza la forma de definir las propiedades (métodos get y set), cómo deben notificarse cambios en los estados del componente (propiedades ligadas, constraints) y cómo acceder a colecciones de elementos (propiedades índice).
Estas notas están sacadas del [[http://java.sun.com/docs/books/tutorial/javabeans/index.html|Tutorial de Java sobre java beans]] y he extraido aquello que se refiere sólamente al código fuente, dejando de lado los aspectos de diseño y visuales.
===== Propiedades sencilla =====
Un java bean sencillo:
public class MyBean {
/** Creates a new instance of MyBean */
public MyBean() {
}
/**
* Holds value of property yourName.
*/
private String yourName;
/**
* Getter for property yourName.
* @return Value of property yourName.
*/
public String getYourName() {
return this.yourName;
}
/**
* Setter for property yourName.
* @param yourName New value of property yourName.
*/
public void setYourName(String yourName) {
this.yourName = yourName;
}
}
Las propiedades exponen dos métodos públicos: getNombrePropiedad y setNombrePropiedad. El "set" siempre retorna void, el get siempre retorna el tipo de la propiedad.
===== Propiedades Indexadas =====
Cuando una propiedad debe exponer una colección de elementos (el acceso a una lista de valores), los métodos a programar son cuatro:
* String getNombrePropiedad( int index ) - devolverá el elemento de la enésima posición de la lista
* String[] getNombrePropiedad() - devolverá una copia de la colección completa
* void setNombrePropiedad( int index, String valor ) - establece el enésimo elemento de la lista
* void setNombrePropiedad( String[] valores ) - establece la colección completa
/**
* Holds value of property lines.
*/
private String[] lines;
/**
* Indexed getter for property lines.
* @param index Index of the property.
* @return Value of the property at index.
*/
public String getLines(int index) {
return this.lines[index];
}
/**
* Getter for property lines.
* @return Value of property lines.
*/
public String[] getLines() {
return this.lines;
}
/**
* Indexed setter for property lines.
* @param index Index of the property.
* @param lines New value of the property at index.
*/
public void setLines(int index, String lines) {
this.lines[index] = lines;
}
/**
* Setter for property lines.
* @param lines New value of property lines.
*/
public void setLines(String[] lines) {
this.lines = lines;
}
===== Propiedades Ligadas =====
Lo que haremos en este caso es utilizar un objeto [[http://java.sun.com/javase/6/docs/api/java/beans/PropertyChangeListener.html|PropertyChangeListener]] para que cada vez que una de las propiedades cambie, se mande un mensaje a aquellos objetos que se hayan registrado como "listeners ((Oyentes))" de nuestro bean.
import java.awt.Graphics;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.Serializable;
import javax.swing.JComponent;
/**
* Bean with bound properties.
*/
public class MyBean
extends JComponent
implements Serializable
{
private String title;
private String[] lines = new String[10];
private final PropertyChangeSupport pcs = new PropertyChangeSupport( this );
public String getTitle()
{
return this.title;
}
public void setTitle( String title )
{
String old = this.title;
this.title = title;
this.pcs.firePropertyChange( "title", old, title );
}
public String[] getLines()
{
return this.lines.clone();
}
public String getLines( int index )
{
return this.lines[index];
}
public void setLines( String[] lines )
{
String[] old = this.lines;
this.lines = lines;
this.pcs.firePropertyChange( "lines", old, lines );
}
public void setLines( int index, String line )
{
String old = this.lines[index];
this.lines[index] = line;
this.pcs.fireIndexedPropertyChange( "lines", index, old, lines );
}
public void addPropertyChangeListener( PropertyChangeListener listener )
{
this.pcs.addPropertyChangeListener( listener );
}
public void removePropertyChangeListener( PropertyChangeListener listener )
{
this.pcs.removePropertyChangeListener( listener );
}
}
===== Propiedades ligadas y limitadas =====
Existe también la posibilidad de que los listeners puedan "vetar" un cambio, que son lo que se llama "constrained properties" ((propiedades limitadas)).
import java.io.Serializable;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.beans.PropertyVetoException;
import java.beans.VetoableChangeListener;
import java.beans.VetoableChangeSupport;
import java.awt.Graphics;
import javax.swing.JComponent;
/**
* Bean with constrained properties.
*/
public class MyBean
extends JComponent
implements Serializable
{
private String title;
private String[] lines = new String[10];
private final PropertyChangeSupport pcs = new PropertyChangeSupport( this );
private final VetoableChangeSupport vcs = new VetoableChangeSupport( this );
public String getTitle()
{
return this.title;
}
/**
* This method was modified to throw the PropertyVetoException
* if some vetoable listeners reject the new title value
*/
public void setTitle( String title )
throws PropertyVetoException
{
String old = this.title;
this.vcs.fireVetoableChange( "title", old, title );
this.title = title;
this.pcs.firePropertyChange( "title", old, title );
}
public String[] getLines()
{
return this.lines.clone();
}
public String getLines( int index )
{
return this.lines[index];
}
/**
* This method throws the PropertyVetoException
* if some vetoable listeners reject the new lines value
*/
public void setLines( String[] lines )
throws PropertyVetoException
{
String[] old = this.lines;
this.vcs.fireVetoableChange( "lines", old, lines );
this.lines = lines;
this.pcs.firePropertyChange( "lines", old, lines );
}
public void setLines( int index, String line )
throws PropertyVetoException
{
String old = this.lines[index];
this.vcs.fireVetoableChange( "lines", old, line );
this.lines[index] = line;
this.pcs.fireIndexedPropertyChange( "lines", index, old, line );
}
public void addPropertyChangeListener( PropertyChangeListener listener )
{
this.pcs.addPropertyChangeListener( listener );
}
public void removePropertyChangeListener( PropertyChangeListener listener )
{
this.pcs.removePropertyChangeListener( listener );
}
/**
* Registration of the VetoableChangeListener
*/
public void addVetoableChangeListener( VetoableChangeListener listener )
{
this.vcs.addVetoableChangeListener( listener );
}
public void removeVetoableChangeListener( VetoableChangeListener listener )
{
this.vcs.removeVetoableChangeListener( listener );
}
protected void paintComponent( Graphics g )
{
g.setColor( getForeground() );
int height = g.getFontMetrics().getHeight();
paintString( g, this.title, height );
if ( this.lines != null )
{
int step = height;
for ( String line : this.lines )
paintString( g, line, height += step );
}
}
private void paintString( Graphics g, String str, int height )
{
if ( str != null )
g.drawString( str, 0, height );
}
}
===== Persistencia: la vida más allá de la cpu =====
Hay dos modos de que un java bean pueda alcanzar la persistencia: serialización y externalización.
La **serialización** se consigue implentando el interfaz Serializable, que podemos entender como un mero "marcador" que indica que nuestro componente es puede serializarse y por lo tanto ser almacenado en disco --o transferido por la red--.
Si la clase padre ya implementa Serializable, entonces la nuestra es también serializable.
Si queremos que una clase o método no se guarde en el proceso de serialización, usaremos el marcador "transient".
Podemos hacer que la serialización no funcione por defecto implementando estos dos métodos en nuestra clase:
private void writeObject( java.io.ObjectOutputStream out) throws IOException;
private void readObject( java.io.ObjectInputStream out) throws IOException, ClassNotFoundException;
La **externalización** exige de un mayor trabajo por nuestra parte, y está pensada para que el contenido del java bean se escriba en disco.
Se hace básicamente heredando el interfaz Externalizable e implementando a continuación los métodos readExternal y writeExternal.
La clase que implemente Externalizable tiene que tener un constructor sin argumentos.
===== Codificando y descodificando un objeto en XML =====
La clase [[http://java.sun.com/j2se/1.4.2/docs/api/java/beans/XMLEncoder.html|XMLEncoder]] permite hacer esto. Ahí van dos ejemplos:
XMLEncoder e = new XMLEncoder(
new BufferedOutputStream(
new FileOutputStream("Test.xml")));
e.writeObject(object);
e.close()
XMLDecoder decoder = new XMLDecoder(
new BufferedInputStream(
new FileInputStream( "Test.xml" ) ) );
Object object = decoder.readObject();
decoder.close();