====== 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();