Archivo de Etiquetas de 'java'

Java: cambiar el valor de un atributo privado de un objeto

En Java a veces es necesario cambiar el valor de un atributo privado de un objeto. Por suerte, la reflexión nos permite hace esto de forma sencilla. El código, como vereis, no puede ser más simple:

public static void setField (Object o, String fieldName, Object newValue)
	throws IllegalArgumentException, IllegalAccessException {
	// Get all the object class fields
	final Field fields[] = o.getClass().getDeclaredFields();
	// Search the requested field
	for (int i = 0; i < fields.length; ++i)
		// If found, set its new value
		if (fieldName.equals(fields[i].getName())) {
			boolean accesible = fields[i].isAccessible();
			fields[i].setAccessible(true);
			fields[i].set(o, newValue);
			fields[i].setAccessible(accesible);
		}
}

Un sencillo código para hacer la prueba puede ser el siguiente, que juega con el atributo privado hash de la clase String:

public static void main(String[] args) throws IllegalArgumentException, IllegalAccessException {
	String s = new String("abc");
	System.out.println("hashcode: " + s.hashCode());
	setField(s, "hash", 30);
	System.out.println("hashcode: " + s.hashCode());
}

Java Fixed Hashtable (tabla hash de longitud fija en Java)

Recientemente he tenido la necesidad de implementar una tabla hash de longitud fija (es decir, cada elemento de la tabla es una lista enlazada con los distintos valores cuya clave tiene la misma hash) en lenguaje Java por motivos que no vienen al caso :razz: . Busqué (escasamente, todo hay que decirlo) una que se adaptase a mis necesidades en Google pero entre mi pereza a la hora de buscar y no tener clara la licencia de las que encontré más o menos a mi gusto decidí partir de cero. Así que aquí dejo mi versión con licencia GPL. Consta de dos clases. La primera de ellas representa cada elemento contenido dentro de cada casilla de la tabla:

/**
 * Hashtable element (list of nodes with key and value)
 * @author Diego Toharia deigote ALGARROBA gmail PUNTO com
 * Licence GNU GPL
 */
public class HashtableElement {

	/**
	 * Hashtable table element nodes
	 * @author Diego Toharia deigote ALGARROBA gmail PUNTO com
	 */
	public class Hashnode {

		// Key and value
		public Object value, key;
		// Links to previous and next nodes
		public Hashnode next, prev;
		// Constructor
		public Hashnode(Object key, Object value, Hashnode next) {
			this.key = key;
			this.value = value;
			this.next = next;
			// New nodes are always in list first position
			if (this.next != null) this.next.prev = this;
			this.prev = null;
		}
		// To string
		public String toString() {
			return key + " . " + value;
		}
		// Clone
	    public Hashnode clone() {
	    	return new Hashnode(key, value, next);
	    }
	}

	// The first element of the linked list
	public Hashnode first;

	// Constructors
	public HashtableElement() {
		first = null;
	}
	public HashtableElement(Hashnode first) {
		this.first = first;
	}

	// Add a node
	public Object add(Object key, Object value) {
		// Try to find the key
		for (Hashnode it = first; it != null; it = it.next)
			if (it.key.equals(key)) {
				Object ret = it.value;
				it.value = value;
				return ret;
			}
		// If not found, add a new node at list start and return null
		first = new Hashnode(key, value, first);
		return null;
	}

	// Remove a node
	public Object remove(Object key) {
		// Try to find the key
		for (Hashnode it = first; it != null; it = it.next)
			if (it.key.equals(key)) {
				Object ret = it.value;
				if (it.prev != null) it.prev.next = it.next;
				else first = it.next;
				if (it.next != null) it.next.prev = it.prev;
				return ret;
			}
		// If not found, return null
		return null;
	}

	// Get a node
	public Object get(Object key) {
		// Try to find the key
		for (Hashnode it = first; it != null; it = it.next)
			if (it.key.equals(key)) return it.value;
		return null;
	}

	// Clone operation
	public HashtableElement clone() {
		// If there is no list, return an empty table element
		if (first == null) return new HashtableElement();
		Hashnode newListIterator = null, oldListIterator = first;
		// Actual list iterator must be at the end
		while (oldListIterator.next != null)
			oldListIterator = oldListIterator.next;
		// Create a new list
		while (oldListIterator != null) {
			newListIterator = new Hashnode(oldListIterator.key, oldListIterator.value, newListIterator);
			oldListIterator = oldListIterator.prev;
		}
		return new HashtableElement(newListIterator);
	}
	// To string
	public String toString() {
		Hashnode iterator = first;
		String ret = "";
		while (iterator != null) {
			ret += iterator.toString();
			iterator = iterator.next;
			ret += " || ";
		}
		return ret;
	}
}

La segunda representa a la tabla en sí:

import java.io.PrintStream;

/**
 * Fixed-sized linked list based hashtable
 * @author Diego Toharia deigote ALGARROBA gmail PUNTO com
 * Licence GNU GPL
 */
class HashtableFixed {

    // Table size
    private int size;
    // Array of node lists
    private HashtableElement[]  table;
    // Objects in the table
    private int length;
    // Lock for synchronized if required
    private ReentrantLock lock;

    /**
     * Constructor (table size and if the table is coarse-grained synchronized)
     */
    public HashtableFixedCoarse(int size, boolean sync) {
        this.size = size;
        table = new HashtableElement[size];
        for (int i = 0; i < size; i++)
        	table[i] = new HashtableElement();
        length = 0;
        if (sync) lock = new ReentrantLock();
        else lock = null;
    }

    /**
     * Gets the table size
     * @return
     */
    public int size() {
        return size;
    }

    /**
     * Gets the number of elements in the table
     * @return
     */
    public int dataSize() {
        return length;
    }

    /**
     * Get an element using its key (null if element doesn't exist)
     * @param key
     * @return
     */
    public Object get(Object key) {
    	Object ret = null;
    	if (lock != null) lock.lock();
    	// Null key or value is not allowed
        if (key != null) {
        	// Get the key hash code
        	int index = Math.abs(key.hashCode()) % size;
        	// Get the node from the table element with same hash
        	ret = table[index].get(key);
        }
        if (lock != null) lock.unlock();
        return ret;
    }

   /**
    * Sets the value for the given key
    * @param key The key (can't be null)
    * @param value The value (can't be null)
    * @return The last value corresponding to this key
    */
    public Object put(Object key, Object value) {
    	Object ret = null;
    	if (lock != null) lock.lock();
    	// Null key or value are not allowed
        if (key != null && value != null) {
        	// Get the key hash code
        	int index = Math.abs(key.hashCode()) % size;
        	// Put the node in the table element with same hash
        	ret = table[index].add(key, value);
        	// If it's a new key, increment number of elements
        	if (ret == null) length++;
        }
        if (lock != null) lock.unlock();
        return ret;
    }

    /**
     * Remove the given key entry
     * @param key
     * @return The key value before remove it
     */
    public Object remove(Object key) {
    	Object ret = null;
    	if (lock != null) lock.lock();
    	// Null key is not allowed
        if (key != null) {
        	// Get the key hash code
        	int index = Math.abs(key.hashCode()) % size;
        	// Remove the node in the table element with same hash
        	ret = table[index].remove(key);
        }
        // If the key was found, decrement number of elements
        length--;
        if (lock != null) lock.unlock();
        return ret;
    }

    /**
     * Clear the table
     */
    public void clear() {
    	if (lock != null) lock.lock();
        for (int i=0; i < size; i++) table[i].first = null;
        if (lock != null) lock.unlock();
    }

    public void print(PrintStream out) {
    	if (lock != null) lock.lock();
    	String msg = "";
    	for (int i = 0; i < table.length; i++)
    		msg += i + ": " + table[i].toString() + "\n";
    	out.println(msg);
    	if (lock != null) lock.unlock();
    }

    /**
     * Test program
     * @param args
     */
    public static void main (String args[]) {
        HashtableFixedCoarse t = new HashtableFixedCoarse(10, true);
        final int NUMS = 20;
        long t1 = System.currentTimeMillis(), t2 = 0;

        for(int i = 0; i < NUMS; i++)
            System.out.println("Put " + -i + "." + i + " (replace " +
            		t.put(new Integer(-i), new Integer(i)) + ")");

        System.out.println("\nlength " + t.dataSize());
        t.print(System.out);

        for(int i = 0; i < NUMS; i += 3)
            System.out.println("Removed " + t.remove(new Integer(-i)));

        System.out.println("\nlength " + t.dataSize());
        t.print(System.out);

        for(int i = 0; i < NUMS; i += 2)
        	System.out.println("Put " + -i + "." + (i*10) + " (replace " +
            		t.put(new Integer(-i), new Integer(i*10)) + ")");

        System.out.println("\nlength " + t.dataSize());
        t.print(System.out);

        for(int i = 0; i < NUMS; i += 2)
            System.out.println("Removed " + t.remove(new Integer(-i)));

        System.out.println("\nlength " + t.dataSize());
        t.print(System.out);

        t2 = System.currentTimeMillis();
        System.out.println(t2-t1);
    }
}

Sólo un par de cosas a tener en cuenta: que mi objetivo no ha sido buscar la máxima eficiencia posible, así que puede que haya cosas mejorables (aparte de posibles bugs, que creo que no), y que el sincronismo es de grano grueso y por lo tanto se usa un único cerrojo que engloba todo el código de cada método (en caso de usar sincronismo claro) en lugar de cerrojos de lectura-escritura con exclusión mútua lo más corta posible.
Espero que a alguien le sea de utilidad o… algo.

Servicios web (2): Desplegando un servicio JWS

Una vez que ya tenemos Tomcat y Axis instalados y funcionando, el siguiente paso es desplegar un servicio web. Existen dos maneras de hacerlo, de las cuales yo voy a abordar la primera en este post. También deberemos escribir un cliente. Para ello, existen tres posibilidades, yo sólo voy a tratar con dos de ellas.

La primera manera de despleguar un servicio web, JWS, es muy sencilla y sólo está indicada para hacer pruebas o para empezar. Consiste en esribir una clase en Java con una serie de métodos, de tal manera que esta se convierta por si sóla en un servicio web, sin tener que hacer nada más. La conversión, creo entender, se hace al vuelo, por eso no está recomendado usarlo: incrementaría mucho la carga del servidor y además no podemos modificar el fichero WSDL.

Escribiendo el servidor

En mi caso he escrito el programa más sencillo del mundo, el clasico Hola, aunque he quitado el Mundo porque me parece muy pretencioso ;-). El código fuente es como se ve a continuación:


/*
 Hola.java - El ejemplo Java mas simple
 */

public class Hola {
  public String decirHola(String persona) {
      return "Hola " + persona;
  }
}

Desplegando el servicio web

Para desplegar este servicio web, basta con copiar el fichero al directorio webapps de axis, pero cambiando la extensión a jws. Esto es:

cp Hola.java $CATALINA_HOME/webapps/axis/Hola.jws

Una vez hecho esto, podemos visitar nuestro servicio web. Evidentemente, no es una página web, pero Axis nos responde avisándonos de que es un servicio web, y nos permite ver el WSDL asociado, lo que para hacer pruebas o aprender es bastante útil.

Escribiendo un cliente

Existen varios métodos para escribir un cliente, como ya dije al principio. Voy a usar el que mejor corresponde al método para desplegar el servicio: el más sencillo. Éste método consiste en crear una DDI o interfaz de invocación dinámica. Su ventaja es la sencillez, pero tiene como desventaja el que no obtendremos una referencia a un objeto remoto, sino a un método, lo cual es menos cómodo.
Primero deberemos importar una serie de librerías de Axis y una de Java:

import org.apache.axis.client.Call;
import org.apache.axis.client.Service;
import org.apache.axis.encoding.XMLType;
import javax.xml.rpc.ParameterMode;

A continuación, declararemos una clase de las que se ejecutan, es decir, con un método estático main. Lo primero que hay que hacer es tener claro cuál es nuestro endpoint, que es la dirección del servicio web. En mi ejemplo:

public class HolaCliente {
   public static void main(String [] args) throws Exception {

       String endpoint = "http://localhost:8080/axis/Hola.jws";

A continuación, declararemos un servicio y una llamada para el mismo. A esta llamada le indicaremos cual es su endpoint, la operación a realizar y los parámetros de la misma, así como el tipo de retorno. Por último la invocaremos:


       Service  service = new Service();
       Call call = (Call)service.createCall();

       call.setTargetEndpointAddress(new java.net.URL(endpoint));
       call.setOperationName("decirHola");
       call.addParameter("persona", XMLType.XSD_STRING,
                         ParameterMode.IN);
       call.setReturnType(XMLType.XSD_STRING);

       String resultado = (String)call.invoke(new Object [] { " cliente"});

       System.out.println(resultado);
   }
}

Compilando y ejecutando el cliente

Para compilar nuestro cliente, debemos tener en cuenta el classpath del compilador de Java, ya que debe incluir el de Axis. Puedes hacerlo usando variables de entorno, aunque yo prefiero pasárselo como un argumento de la llamada al compilador, tal que así:

$ javac -classpath $AXISCLASSPATH HolaCliente.java

Si todo ha ido bien, ya podemos lanzar nuestro cliente. Hay que tener en cuenta ahora que en el classpath debe figurar el directorio donde está el resultado de la compilación anterior. En mi caso, hago:

$ java -cp .:$AXISCLASSPATH HolaCliente

Lo que hace que obtenga el siguiente resultado:
log4j:WARN No appenders could be found for logger (org.apache.axis.i18n.ProjectResourceBundle).
log4j:WARN Please initialize the log4j system properly.
Hola cliente

Que, a pesar de los warnings que sigo ignorando, da un resultado safistactorio :-). Los ficheros con el código fuente son Hola.java y HolaCliente.java

Precede: Servicios web (1): Instalando Tomcat y Axis
Continua: Servicios web (3): Desplegando un servicio WDSS

Technorati Tags: