package es.ejie.frmk.infraestructura.poolCache;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectOutputStream;
import java.util.Iterator;
import java.util.Set;

import org.apache.commons.collections.LRUMap;

import es.ejie.frmk.Q70Factoria;
import es.ejie.frmk.infraestructura.traza.Q70TraceLevel;
import es.ejie.frmk.infraestructura.traza.Q70TrazaInterface;

/**
 * Clase que representa un almacén de elementos Q70ElementoCache con algorimo
 * LRU para controlar su tamaño. También tiene posibilidad de controlar el
 * tamaño en memoria del alamcén.
 * 
 * @version 2.0 Fecha: 21-feb-06
 */
public class Q70LRUMap extends LRUMap {
	
	/**
	 * @param tamano
	 */
	public Q70LRUMap (int tamano) {
		super(tamano);
	}
	
	/**
	 * Añade un objeto al almacén controlando el tamaño del almacén en memoria. 
	 * @param clave Key del objeto
	 * @param obj Objeto a almacenar
	 * @param memoriaMax Tamaño de memoria en bytes que puede opcupar el alamacén.
	 */
	public void putConRestriccionMemoriaMax(Object clave, Q70ElementoCache obj, long memoriaMax) {
		// Si memoria == 0 no se controla el tamaño del alamacén en memoria
		if (memoriaMax > 0) {
			Q70TrazaInterface traza = Q70Factoria.getTraza();
			try {
				int memoriaObj = calcularTamanoBytes(obj);
				
				if (memoriaObj < memoriaMax) {
					// Eliminar elementos almacenados hasta que el total de memoria resultante no supere el máximo permitido.
					while((calcularTamanoBytes(this)+memoriaObj) > memoriaMax) {
						this.removeLRU();
					}
					this.put(clave, obj);
					if (traza.isTraceActive()){
						traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.DEBUG,"El elemento con clave: "+clave+" ha sido añadido a la cache.");
					}
				} else {
					if (traza.isTraceActive()){
						traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.WARN,"El elemento con clave: "+clave+" no ha sido añadido a la cache ya que su tamaño en memoria es superior al máximo permitido.");
					}
				}
			} catch (NotSerializableException nse) {
				if (traza.isTraceActive()){
					traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.WARN,"El elemento con clave: "+clave+" no ha sido añadido a la cache ya que no se ha podido calcular su tamaño en memoria ya que no es serializable.");
				}
			} catch (IOException ioe) {
				if (traza.isTraceActive()){
					traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.ERROR,"El elemento con clave: "+clave+" no ha sido añadido a la cache ya que se ha producido un error al intentar serializarlo.");
				}
			}
		} else {
			putSinRestriccionMemoriaMax(clave, obj);
		}		
	}
	
	/**
	 * Añade un objeto al almacén. 
	 * @param clave Key del objeto
	 * @param obj Objeto a almacenar
	 */
	public void putSinRestriccionMemoriaMax(Object clave, Q70ElementoCache obj) {
		this.put(clave, obj);
		Q70TrazaInterface traza = Q70Factoria.getTraza();
		if (traza.isTraceActive()){
			traza.trace(Q70Constantes.ID_COMPONENTE, Q70TraceLevel.DEBUG,"El elemento con clave: "+clave+" ha sido añadido a la cache.");
		}
	}
	
	/**
	 * Método que calcula el tamaño en bytes de un objeto serializable.
	 * @param obj Objeto a calcular su tamaño.
	 * @return int Tamaño en bytes.
	 * @throws IOException Si el objeto no es serializable.
	 */
	private int calcularTamanoBytes (Object obj) throws IOException {
		int size=-1;
		
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		ObjectOutputStream oos = new ObjectOutputStream(baos);
		oos.writeObject(obj);
		oos.flush();
		size = baos.size();
		
		return size;
	}
	
	/**
	 * La clase LRUMap de la que hereda este método necesita que esté sincronizado.
	 * @see org.apache.commons.collections.LRUMap#get(java.lang.Object)
	 */
	public synchronized Object get(Object arg0) {
		return super.get(arg0);
	}

	/**
	 * @see org.apache.commons.collections.LRUMap#put(java.lang.Object, java.lang.Object)
	 */
	public synchronized Object put(Object arg0, Object arg1) {
		return super.put(arg0, arg1);
	}

	/**
	 * @see org.apache.commons.collections.LRUMap#removeLRU()
	 */
	protected synchronized void removeLRU() {
		super.removeLRU();
	}

	/**
	 * @see org.apache.commons.collections.SequencedHashMap#remove(java.lang.Object)
	 */
	public synchronized Object remove(Object arg0) {
		return super.remove(arg0);
	}

	/**
	 * @see org.apache.commons.collections.SequencedHashMap#keySet()
	 */
	public synchronized Set keySet() {
		return super.keySet();
	}

	/**
	 * @see org.apache.commons.collections.SequencedHashMap#clear()
	 */
	public synchronized void clear() {
		super.clear();
	}

	/**
	 * @see org.apache.commons.collections.SequencedHashMap#entrySet()
	 */
	public synchronized Set entrySet() {
		return super.entrySet();
	}

	/**
	 * @see org.apache.commons.collections.SequencedHashMap#iterator()
	 */
	public synchronized Iterator iterator() {
		return super.iterator();
	}
}
