/*
* JBoss, Home of Professional Open Source
* Copyright 2005, JBoss Inc., and individual contributors as indicated
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/

package org.jboss.serial.objectmetamodel;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.ArrayList;
import java.util.Iterator;

import org.jboss.serial.classmetamodel.ClassMetamodelFactory;
import org.jboss.serial.classmetamodel.ClassResolver;
import org.jboss.serial.exception.SerializationException;
import org.jboss.serial.finalcontainers.BooleanContainer;
import org.jboss.serial.finalcontainers.ByteContainer;
import org.jboss.serial.finalcontainers.CharacterContainer;
import org.jboss.serial.finalcontainers.DoubleContainer;
import org.jboss.serial.finalcontainers.FloatContainer;
import org.jboss.serial.finalcontainers.IntegerContainer;
import org.jboss.serial.finalcontainers.LongContainer;
import org.jboss.serial.finalcontainers.ShortContainer;
import org.jboss.serial.objectmetamodel.safecloning.SafeCloningRepository;
import org.jboss.serial.util.StringUtil;
import org.jboss.serial.util.StringUtilBuffer;

/**
 * DataContainer is a Container of Immutables and Object References.
 * It emmulates the repository as it would be a DataOutputStream and DataInputStream
 *
 * $Id: DataContainer.java,v 1.44 2006/08/28 18:35:34 csuconic Exp $
 *
 * @author clebert suconic
 */
public class DataContainer extends DataExport implements DataContainerConstants, Externalizable
{
    /** These are the bytes used during streaming of Datacontainer. They are used to control and they are mixed within content.
     *  So, they should be read in the same order they were written */
    byte[] controlStreaming;

    DataContainerOutput currentOutput = null;

    ArrayList content = new ArrayList();

    /** I used the transient tag as a documentation feature.
     *  this contains the root objectCache being used.
     *  It would be possible to use ThreadLocals for this.
     *  But I didn't want to take the risk of having problems when the application was running
     *  in ThreadPools generating leaks.*/
    transient ObjectsCache cache;
    
    public DataContainer cloneContainer()
    {
    	DataContainer newContainer = new DataContainer();
    	newContainer.content = this.content;
    	newContainer.controlStreaming = this.controlStreaming;
    	newContainer.cache = this.cache.cloneCache();
    	return newContainer;
    }

    private DataContainer()
    {
    }

    public DataContainer(boolean checkSerializable)
    {
        this ((ClassLoader)null, checkSerializable,null);
    }
    
    public DataContainer(boolean checkSerializable, StringUtilBuffer buffer)
    {
        this ((ClassLoader)null, checkSerializable,buffer);
    }

    public DataContainer(ClassLoader loader, boolean checkSerializable, StringUtilBuffer buffer)
    {
        this(loader,null,checkSerializable,buffer);
    }

    public DataContainer(ClassLoader loader, boolean checkSerializable, StringUtilBuffer buffer,ClassResolver resolver)
    {
        this(loader,null,checkSerializable,buffer);
    }

    public DataContainer(ClassLoader loader, ObjectSubstitutionInterface substitution, boolean checkSerializable)
    {
        this(loader,substitution,null,checkSerializable,null);
    }

    public DataContainer(ClassLoader loader, ObjectSubstitutionInterface substitution, boolean checkSerializable, StringUtilBuffer buffer)
    {
        this(loader,substitution,null,checkSerializable,buffer);
    }

    public DataContainer(ClassLoader loader, ObjectSubstitutionInterface substitution, SafeCloningRepository safeToReuse, boolean checkSerializable)
    {
    	this();
        this.cache=new ObjectsCache(substitution,loader,safeToReuse,checkSerializable,null);
    }

    public DataContainer(ClassLoader loader, ObjectSubstitutionInterface substitution, SafeCloningRepository safeToReuse, boolean checkSerializable, StringUtilBuffer buffer)
    {
    	this();
        this.cache=new ObjectsCache(substitution,loader,safeToReuse,checkSerializable,buffer);
    }


    public DataContainer(ObjectsCache cache)
    {
        this.cache=cache;
    }

    public int getSize()
    {
        return content.size();
    }


    public ObjectInput getInput()
    {
        return new DataContainerInput();
    }

    public ObjectOutput getOutput()
    {
        if (currentOutput==null)
        {
            currentOutput = new DataContainerOutput();
        }
        return currentOutput;
    }

    public ObjectOutput getDirectOutput(DataOutputStream dataOut)
    {
        return new DataContainerDirectOutput(dataOut);
    }

    public ObjectInput getDirectInput(DataInputStream dataInput)
    {
        return new DataContainerDirectInput(dataInput);
    }

    public void flush() throws IOException
    {
        if (currentOutput!=null)
        {
            currentOutput.flushByteArray();
        }
    }

    class DataContainerDirectOutput implements ObjectsCache.JBossSeralizationOutputInterface
    {
        DataOutputStream dataOut;

        public void addObjectReference(int reference) throws IOException {
        	this.writeInt(reference);
        }

        public void openObjectDefinition() throws IOException {
        }

        public void closeObjectDefinition() throws IOException {
        }

        public void writeByteDirectly(byte parameter) throws IOException {
            this.write(parameter);
        }

        public boolean isCheckSerializableClass() {
            return DataContainer.this.getCache().isCheckSerializableClass();
        }

        public void writeObject(Object obj) throws IOException {
            DataContainer.this.cache.setOutput(this);
        	if (cache.getSubstitution()!=null)
        	{
        		obj = cache.getSubstitution().replaceObject(obj);
        	}
            ObjectDescriptorFactory.describeObject(DataContainer.this.cache,obj);
        }

        public void write(byte b[]) throws IOException {
            dataOut.write(b);
        }

        public void close() throws IOException {
            dataOut.close();
        }

        public DataContainerDirectOutput(DataOutputStream dataOut) {
            this.dataOut = dataOut;
        }

        public void write(int b) throws IOException {
            dataOut.write(b);
        }

        public void write(byte[] b, int off, int len) throws IOException {
            dataOut.write(b, off, len);
        }

        public void flush() throws IOException {
            dataOut.flush();
        }

        public void writeBoolean(boolean v) throws IOException {
            dataOut.writeBoolean(v);
        }

        public void writeByte(int v) throws IOException {
            dataOut.writeByte(v);
        }

        public void writeShort(int v) throws IOException {
            dataOut.writeShort(v);
        }

        public void writeChar(int v) throws IOException {
            dataOut.writeChar(v);
        }

        public void writeInt(int v) throws IOException {
            dataOut.writeInt(v);
        }

        public void writeLong(long v) throws IOException {
            dataOut.writeLong(v);
        }

        public void writeFloat(float v) throws IOException {
            dataOut.writeFloat(v);
        }

        public void writeDouble(double v) throws IOException {
            dataOut.writeDouble(v);
        }

        public void writeBytes(String s) throws IOException {
            dataOut.writeBytes(s);
        }

        public void writeChars(String s) throws IOException {
            dataOut.writeChars(s);
        }

        public void writeUTF(String str) throws IOException {
            StringUtil.saveString(dataOut,str,cache.getStringBuffer());
        }

        public int size() {
            return dataOut.size();
        }

        public void saveImmutable(ObjectsCache cache, Object obj) throws IOException {
        	
        	int id=cache.findIdInCacheWrite(obj);
        	if (id!=0)
        	{
        		this.writeByte(DataContainerConstants.IMMUTABLE_OBJREF);
        		this.addObjectReference(id);
        		return;
        	}
        	id=cache.putObjectInCacheWrite(obj);
            if (obj instanceof String)
            {
            	this.writeByte(DataContainerConstants.STRING);
            	this.addObjectReference(id);
            	StringUtil.saveString(this,(String)obj,cache.getStringBuffer());
            } else
            if (obj instanceof Byte)
            {
            	this.writeByte(DataContainerConstants.BYTE);
            	this.addObjectReference(id);
            	this.writeByte(((Byte)obj).byteValue());
            } else
            if (obj instanceof Character)
            {
            	this.writeByte(DataContainerConstants.CHARACTER);
            	this.addObjectReference(id);
            	this.writeChar(((Character)obj).charValue());
            } else
            if (obj instanceof Short)
            {
            	this.writeByte(DataContainerConstants.SHORT);
            	this.addObjectReference(id);
            	this.writeShort(((Short)obj).shortValue());
            } else
            if (obj instanceof Integer)
            {
            	this.writeByte(DataContainerConstants.INTEGER);
            	this.addObjectReference(id);
            	this.writeInt(((Integer)obj).intValue());
            } else
            if (obj instanceof Long)
            {
            	this.writeByte(DataContainerConstants.LONG);
            	this.addObjectReference(id);
            	this.writeLong(((Long)obj).longValue());
            } else
            if (obj instanceof Double)
            {
            	this.writeByte(DataContainerConstants.DOUBLE);
            	this.addObjectReference(id);
            	this.writeDouble(((Double)obj).doubleValue());
            } else
            if (obj instanceof Float)
            {
            	this.writeByte(DataContainerConstants.FLOAT);
            	this.addObjectReference(id);
            	this.writeFloat(((Float)obj).floatValue());
            } else
            if (obj instanceof BooleanContainer || obj instanceof Boolean)
            {
            	this.writeByte(DataContainerConstants.BOOLEAN);
            	this.addObjectReference(id);
            	this.writeBoolean(((Boolean)obj).booleanValue());
            } else
            {
                throw new SerializationException("I don't know how to write type " + obj.getClass().getName() + " yet");
            }
        	
    		
    	}

    }

    class DataContainerOutput implements ObjectsCache.JBossSeralizationOutputInterface
    {

        /** outByte is used to hold writeByte operations until we start doing a different operation */
        ByteArrayOutputStream outByte=new ByteArrayOutputStream();
        //DataOutputStream dataOutput = null;

        private void flushByteArray()
        {
            if (outByte!=null)
            {
                byte controlStreaming[] = outByte.toByteArray();
                DataContainer.this.setControlStreaming(controlStreaming);
            }
        }

        /* (non-Javadoc)
         * @see java.io.ObjectOutput#writeObject(java.lang.Object)
         */
        public void writeObject(Object obj) throws IOException
        {

            if (obj==null)
            {
                this.writeByte(DataContainerConstants.NULLREF);
            }
            else
            {
                if (ClassMetamodelFactory.isImmutable(obj.getClass()))
                {
                	if (cache.getSubstitution()!=null)
                	{
                		obj = cache.getSubstitution().replaceObject(obj);
                	}
                }
                DataContainer.this.cache.setOutput(this);
                ObjectDescriptorFactory.describeObject(DataContainer.this.cache,obj);
            }
        }

        public void addObjectReference(int reference) throws IOException
        {
        	this.writeInt(reference);
        }

        public void saveImmutable(ObjectsCache cache, Object obj) throws IOException {
            if (obj instanceof String)
            {
            	this.writeByte(DataContainerConstants.STRING);
            	DataContainer.this.content.add(obj);
            } else
            if (obj instanceof Byte)
            {
            	this.writeByte(DataContainerConstants.BYTE);
            	DataContainer.this.content.add(obj);
            } else
            if (obj instanceof Character)
            {
            	this.writeByte(DataContainerConstants.CHARACTER);
            	DataContainer.this.content.add(obj);
            } else
            if (obj instanceof Short)
            {
            	this.writeByte(DataContainerConstants.SHORT);
            	DataContainer.this.content.add(obj);
            } else
            if (obj instanceof Integer)
            {
            	this.writeByte(DataContainerConstants.INTEGER);
            	DataContainer.this.content.add(obj);
            } else
            if (obj instanceof Long)
            {
            	this.writeByte(DataContainerConstants.LONG);
            	DataContainer.this.content.add(obj);
            } else
            if (obj instanceof Double)
            {
            	this.writeByte(DataContainerConstants.DOUBLE);
            	DataContainer.this.content.add(obj);
            } else
            if (obj instanceof Float)
            {
            	this.writeByte(DataContainerConstants.FLOAT);
            	DataContainer.this.content.add(obj);
            } else
            if (obj instanceof BooleanContainer || obj instanceof Boolean)
            {
            	this.writeByte(DataContainerConstants.BOOLEAN);
            	DataContainer.this.content.add(obj);
            } else
            {
                throw new SerializationException("I don't know how to write type " + obj.getClass().getName() + " yet");
            }
        }



        public void writeByteDirectly(byte parameter) throws IOException {
            writeByte(parameter);
        }

        public void openObjectDefinition() throws IOException {
            //TODO: -TME Implement
        }

        public void closeObjectDefinition() throws IOException {
            //TODO: -TME Implement
        }


        /* (non-Javadoc)
        * @see java.io.ObjectOutput#write(int)
        */
        public void write(int b) throws IOException
        {
            outByte.write(b);
        }

        /* (non-Javadoc)
         * @see java.io.ObjectOutput#write(byte[])
         */
        public void write(byte[] b) throws IOException
        {
            outByte.write(b);
        }

        /* (non-Javadoc)
         * @see java.io.ObjectOutput#write(byte[], int, int)
         */
        public void write(byte[] b, int off, int len) throws IOException
        {
            outByte.write(b,off,len);

        }

        /* (non-Javadoc)
         * @see java.io.ObjectOutput#flush()
         */
        public void flush() throws IOException
        {
            flushByteArray();
        }

        /* (non-Javadoc)
         * @see java.io.ObjectOutput#close()
         */
        public void close() throws IOException
        {
            flush();

        }

        /* (non-Javadoc)
         * @see java.io.DataOutput#writeBoolean(boolean)
         */
        public void writeBoolean(boolean v) throws IOException
        {
            content.add(BooleanContainer.valueOf(v));
        }

        /* (non-Javadoc)
         * @see java.io.DataOutput#writeByte(int)
         */
        public void writeByte(int v) throws IOException
        {
            //createByteArray();
            outByte.write(v);
        }

        /* (non-Javadoc)
         * @see java.io.DataOutput#writeShort(int)
         */
        public void writeShort(int v) throws IOException
        {
            //DataContainer.this.content.add(Short.valueOf((short)v));
            DataContainer.this.content.add(new ShortContainer((short)v));
        }

        /* (non-Javadoc)
         * @see java.io.DataOutput#writeChar(int)
         */
        public void writeChar(int v) throws IOException
        {
            //flush();
            DataContainer.this.content.add(new CharacterContainer((char)v));
        }

        /* (non-Javadoc)
         * @see java.io.DataOutput#writeInt(int)
         */
        public void writeInt(int v) throws IOException
        {
            //flush();
            //DataContainer.this.content.add(Integer.valueOf(v));
            DataContainer.this.content.add(new IntegerContainer(v));
        }

        /* (non-Javadoc)
         * @see java.io.DataOutput#writeLong(long)
         */
        public void writeLong(long v) throws IOException
        {
            //flush();
            //DataContainer.this.content.add(Long.valueOf(v));
            DataContainer.this.content.add(new LongContainer(v));
        }

        /* (non-Javadoc)
         * @see java.io.DataOutput#writeFloat(float)
         */
        public void writeFloat(float v) throws IOException
        {
            //flush();
            //DataContainer.this.content.add(Float.valueOf(v));
            DataContainer.this.content.add(new FloatContainer(v));
        }

        /* (non-Javadoc)
         * @see java.io.DataOutput#writeDouble(double)
         */
        public void writeDouble(double v) throws IOException
        {
            //flush();
            //DataContainer.this.content.add(Double.valueOf(v));
            DataContainer.this.content.add(new DoubleContainer(v));
        }

        /* (non-Javadoc)
         * @see java.io.DataOutput#writeBytes(java.lang.String)
         */
        public void writeBytes(String s) throws IOException
        {
            //createByteArray();

            char[] chars = s.toCharArray();
            for (int i=0;i<chars.length;i++)
            {
                this.write(chars[i]);
            }
        }

        /* (non-Javadoc)
         * @see java.io.DataOutput#writeChars(java.lang.String)
         */
        public void writeChars(String s) throws IOException
        {
            //createByteArray();
            char[] chars = s.toCharArray();
            for (int i=0;i<chars.length;i++)
            {
                this.writeChar(chars[i]);
            }
        }

        /* (non-Javadoc)
         * @see java.io.DataOutput#writeUTF(java.lang.String)
         */
        public void writeUTF(String str) throws IOException
        {
            //flush();
            DataContainer.this.content.add(str);
        }

        public boolean isCheckSerializableClass() {
            return DataContainer.this.cache.isCheckSerializableClass();
        }

    }


    class DataContainerDirectInput implements ObjectsCache.JBossSeralizationInputInterface
    {
        DataInputStream dataInp;

        public DataContainerDirectInput(DataInputStream dataInp) {
            this.dataInp = dataInp;
        }

        public int readObjectReference() throws IOException {
        	return this.readInt();
        }

        public byte readByteDirectly() throws IOException {
            return (byte)this.read();
        }

        public Object readObject() throws ClassNotFoundException, IOException {
            DataContainer.this.cache.setInput(this);
            return ObjectDescriptorFactory.objectFromDescription(DataContainer.this.cache,this);
        }

        /**
         * Reads a byte of data. This method will block if no input is
         * available.
         *
         * @return the byte read, or -1 if the end of the
         *         stream is reached.
         * @throws java.io.IOException If an I/O error has occurred.
         */
        public int read() throws IOException {
            return dataInp.read();
        }

        /**
         * Skips n bytes of input.
         *
         * @param n the number of bytes to be skipped
         * @throws java.io.IOException If an I/O error has occurred.
         * @return the actual number of bytes skipped.
         */
        public long skip(long n) throws IOException {
            return dataInp.skip(n);
        }

        /**
         * Returns the number of bytes that can be read
         * without blocking.
         *
         * @return the number of available bytes.
         * @throws java.io.IOException If an I/O error has occurred.
         */
        public int available() throws IOException {
            return dataInp.available();
        }

        /**
         * Closes the input stream. Must be called
         * to release any resources associated with
         * the stream.
         *
         * @throws java.io.IOException If an I/O error has occurred.
         */
        public void close() throws IOException {
            dataInp.close();
        }

        public int read(byte[] b) throws IOException {
            return dataInp.read(b);
        }

        public int read(byte[] b, int off, int len) throws IOException {
            return dataInp.read(b, off, len);
        }

        public void readFully(byte[] b) throws IOException {
            dataInp.readFully(b);
        }

        public void readFully(byte[] b, int off, int len) throws IOException {
            dataInp.readFully(b, off, len);
        }

        public int skipBytes(int n) throws IOException {
            return dataInp.skipBytes(n);
        }

        public boolean readBoolean() throws IOException {
            return dataInp.readBoolean();
        }

        public byte readByte() throws IOException {
            return dataInp.readByte();
        }

        public int readUnsignedByte() throws IOException {
            return dataInp.readUnsignedByte();
        }

        public short readShort() throws IOException {
            return dataInp.readShort();
        }

        public int readUnsignedShort() throws IOException {
            return dataInp.readUnsignedShort();
        }

        public char readChar() throws IOException {
            return dataInp.readChar();
        }

        public int readInt() throws IOException {
            return dataInp.readInt();
        }

        public long readLong() throws IOException {
            return dataInp.readLong();
        }

        public float readFloat() throws IOException {
            return dataInp.readFloat();
        }

        public double readDouble() throws IOException {
            return dataInp.readDouble();
        }

        public String readLine() throws IOException {
            return dataInp.readLine();
        }

        public String readUTF() throws IOException {
            //return dataInp.readUTF();
            return StringUtil.readString(dataInp,cache.getStringBuffer());
        }

        public String readUTF(DataInput in) throws IOException {
            //return dataInp.readUTF(in);
            return StringUtil.readString(in,cache.getStringBuffer());
        }

        public Object readImmutable(byte byteDescription,ObjectsCache cache) throws IOException {
    		
        	Object retObject=null;
        	int reference = this.readObjectReference();
        	switch (byteDescription)
        	{
        	case DataContainerConstants.IMMUTABLE_OBJREF:
        	     retObject = cache.findObjectInCacheRead(reference);
        	     if (retObject==null)
        	     {
        	    	 throw new IOException("reference " + reference + " not found no readImmutable");
        	     }
        	     break;
        	case DataContainerConstants.STRING:
        		retObject = StringUtil.readString(this,cache.getStringBuffer());break;
        	case DataContainerConstants.BYTE:
        		retObject = new Byte(this.readByte());break;
        	case DataContainerConstants.CHARACTER:
        		retObject = new Character(this.readChar());break;
        	case DataContainerConstants.SHORT:
        		retObject = new Short(this.readShort());break;
        	case DataContainerConstants.INTEGER:
        		retObject = new Integer(this.readInt());break;
        	case DataContainerConstants.LONG:
        		retObject = new Long(this.readLong());break;
        	case DataContainerConstants.DOUBLE:
        		retObject = new Double(this.readDouble());break;
        	case DataContainerConstants.FLOAT:
        		retObject = new Float(this.readFloat());break;
        	case DataContainerConstants.BOOLEAN:
        		retObject = new Boolean(this.readBoolean());break;
        	}
        	
        	if (byteDescription!=DataContainerConstants.IMMUTABLE_OBJREF)
        	{
        		cache.putObjectInCacheRead(reference,retObject);
        	}
        	
        	return retObject;
    	}

    }

    class DataContainerInput implements ObjectsCache.JBossSeralizationInputInterface
    {
        int position=-1;
        Object currentObject=null;

        ByteArrayInputStream byteStreamInput;

        public DataContainerInput() {
            byteStreamInput = new ByteArrayInputStream(DataContainer.this.getControlStreaming());
        }

        public void reset()
        {
            position=-1;
            byteStreamInput=null;
        }

        boolean moveNext() throws EOFException
        {
            position++;

            if (position>=content.size())
            {
                throw new EOFException("Unexpected end of repository");
            }

            currentObject = content.get(position);

            return position<content.size();
        }

        /* (non-Javadoc)
           * @see java.io.ObjectInput#readObject()
           */
        public Object readObject() throws ClassNotFoundException, IOException
        {
                DataContainer.this.cache.setInput(this);
                return ObjectDescriptorFactory.objectFromDescription(DataContainer.this.cache,this);
        }


        public int readObjectReference() throws IOException {
             moveNext();
             return ((IntegerContainer)currentObject).getValue();
         }

        public byte readByteDirectly() throws IOException {
            return readByte();
        }


        /*void testNextBytes() throws IOException
        {
            if(byteStreamInput==null)
            {
                moveNext();
            }

            if (this.byteStreamInput==null)
            {
                throw new SerializationException("Excepted a byteBuffer in the sequence");
            }
        }*/

        /* (non-Javadoc)
           * @see java.io.ObjectInput#read()
           */
        public int read() throws IOException
        {
            //testNextBytes();
            return byteStreamInput.read();
        }

        /* (non-Javadoc)
           * @see java.io.ObjectInput#read(byte[])
           */
        public int read(byte[] b) throws IOException
        {
            //testNextBytes();
            return byteStreamInput.read(b);
        }

        /* (non-Javadoc)
           * @see java.io.ObjectInput#read(byte[], int, int)
           */
        public int read(final byte[] b, final int off, final int len) throws IOException
        {
            //testNextBytes();
            return byteStreamInput.read(b,off,len);
        }

        /* (non-Javadoc)
           * @see java.io.ObjectInput#skip(long)
           */
        public long skip(long n) throws IOException
        {
            //testNextBytes();
            return byteStreamInput.skip(n);
        }

        /* (non-Javadoc)
           * @see java.io.ObjectInput#available()
           */
        public int available() throws IOException
        {
            //testNextBytes();
            return byteStreamInput.available();
        }

        /* (non-Javadoc)
           * @see java.io.ObjectInput#close()
           */
        public void close() throws IOException
        {
        }

        /* (non-Javadoc)
           * @see java.io.DataInput#readFully(byte[])
           */
        public void readFully(byte[] b) throws IOException
        {
            //testNextBytes();
            byteStreamInput.read(b);
        }

        /* (non-Javadoc)
           * @see java.io.DataInput#readFully(byte[], int, int)
           */
        public void readFully(byte[] b, int off, int len) throws IOException
        {
            //testNextBytes();
            byteStreamInput.read(b,off,len);

        }

        /* (non-Javadoc)
           * @see java.io.DataInput#skipBytes(int)
           */
        public int skipBytes(int n) throws IOException
        {
            //testNextBytes();
            return (int)byteStreamInput.skip(n);
        }

        /* (non-Javadoc)
           * @see java.io.DataInput#readBoolean()
           */
        public boolean readBoolean() throws IOException
        {
            moveNext();

            try {
                return ((BooleanContainer)currentObject).getValue();
            }
            catch (ClassCastException e)
            {
                throw new SerializationException("Excepted to be boolean",e);
            }
        }

        /* (non-Javadoc)
           * @see java.io.DataInput#readByte()
           */
        public byte readByte() throws IOException
        {
            //testNextBytes();

            return (byte)byteStreamInput.read();
        }

        /* (non-Javadoc)
           * @see java.io.DataInput#readUnsignedByte()
           */
        public int readUnsignedByte() throws IOException
        {
            //testNextBytes();

            return byteStreamInput.read();
        }

        /* (non-Javadoc)
           * @see java.io.DataInput#readShort()
           */
        public short readShort() throws IOException
        {
            moveNext();

            try {
                return ((ShortContainer)currentObject).getValue();
            }
            catch (ClassCastException e)
            {
                throw new SerializationException("Excepted to be short",e);
            }
        }

        /* (non-Javadoc)
           * @see java.io.DataInput#readUnsignedShort()
           */
        public int readUnsignedShort() throws IOException
        {
            moveNext();

            try {
                return ((ShortContainer)currentObject).getValue();
            }
            catch (ClassCastException e)
            {
                throw new SerializationException("Excepted to be short",e);
            }
        }

        /* (non-Javadoc)
           * @see java.io.DataInput#readChar()
           */
        public char readChar() throws IOException
        {
            moveNext();

            try {
                return ((CharacterContainer)currentObject).getValue();
            }
            catch (ClassCastException e)
            {
                throw new SerializationException("Excepted to be char",e);
            }
        }

        /* (non-Javadoc)
           * @see java.io.DataInput#readInt()
           */
        public int readInt() throws IOException
        {
            moveNext();

            try {
                return ((IntegerContainer)currentObject).getValue();
            }
            catch (ClassCastException e)
            {
                throw new SerializationException("Excepted to be int",e);
            }
        }

        /* (non-Javadoc)
           * @see java.io.DataInput#readLong()
           */
        public long readLong() throws IOException
        {
            moveNext();

            try {
                return ((LongContainer)currentObject).getValue();
            }
            catch (ClassCastException e)
            {
                throw new SerializationException("Excepted to be long",e);
            }
        }

        /* (non-Javadoc)
           * @see java.io.DataInput#readFloat()
           */
        public float readFloat() throws IOException
        {
            moveNext();

            try {
                return ((FloatContainer)currentObject).getValue();
            }
            catch (ClassCastException e)
            {
                throw new SerializationException("Excepted to be float",e);
            }
        }

        /* (non-Javadoc)
           * @see java.io.DataInput#readDouble()
           */
        public double readDouble() throws IOException
        {
            moveNext();

            try {
                return ((DoubleContainer)currentObject).getValue();
            }
            catch (ClassCastException e)
            {
                throw new SerializationException("Excepted to be double",e);
            }
        }

        /* (non-Javadoc)
           * @see java.io.DataInput#readLine()
           */
        public String readLine() throws IOException
        {
            return readLine();
        }

        /* (non-Javadoc)
           * @see java.io.DataInput#readUTF()
           */
        public String readUTF() throws IOException
        {
            moveNext();

            try {
                return (String)currentObject;
            }
            catch (ClassCastException e)
            {
                throw new SerializationException("Excepted to be String",e);
            }
        }

        public Object readImmutable(byte byteDescription,ObjectsCache cache) throws IOException {
    		
        	moveNext();
        	return currentObject;
    	}

    }

    /**
     * @return Returns the cache.
     */
    public ObjectsCache getCache()
    {
        return cache;
    }

    /** Sends this data over the wire to a streaming. */
    public void saveData(DataOutput output) throws IOException
    {
        // This only flushes internal buffers from sub models, it doesn't flush to the external Streams.
        this.flush();
        output.writeInt(getControlStreaming().length);
        output.write(getControlStreaming());
        writeMyself(output);
        //this.cache.writeMyself(output);
        /*if (output instanceof DataOutputStream)
        {
            ((DataOutputStream)output).flush();
        }*/
        output.write(DataContainerConstants.closeSign);
    }

    private void writeInteger(DataOutput output, Object obj) throws IOException
    {
        if (obj instanceof IntegerContainer)
        {
            output.writeByte(INTEGER);
            output.writeInt(((IntegerContainer)obj).getValue());
        } else
        {
            output.writeByte(INTEGEROBJ);
            output.writeInt(((Integer)obj).intValue());
        }
    }

    /**
     * @param output
     * @param obj
     */
    private void writeDouble(DataOutput output, Object obj) throws IOException
    {
        if (obj instanceof DoubleContainer)
        {
            output.writeByte(DOUBLE);
            output.writeDouble(((DoubleContainer)obj).getValue());
        }
        else
        {
            output.writeByte(DOUBLEOBJ);
            output.writeDouble(((Double)obj).doubleValue());
        }
    }



    private void saveString(DataOutput output, Object obj) throws IOException
    {
        output.writeByte(STRING);
        StringUtil.saveString(output,(String)obj,cache.getStringBuffer());
    }


    /** this reads data from a streaming. */
     public void loadData(DataInput input) throws IOException
    {
        int size = input.readInt();
        byte byteControl[] = new byte[size];
        input.readFully(byteControl);
        this.setControlStreaming(byteControl);


        readMyself(input);
        //this.cache.readMyself(input);

    }

    private boolean compareBuffer(byte[] buffer1, byte[] buffer2)
    {
        for (int i=0;i<buffer1.length;i++)
        {
            if (buffer1[i]!=buffer2[i])
            {
                return false;
            }
        }
        return true;

    }

    /* (non-Javadoc)
    * @see org.jboss.serial.objectmetamodel.DataExport#writeMyself(java.io.DataOutput)
    */
    public void writeMyself(DataOutput output) throws IOException
    {
        if (currentOutput!=null && currentOutput.outByte!=null)
        {
            currentOutput.flushByteArray();
        }

        output.writeInt(content.size());
        Iterator iter = content.iterator();
        while (iter.hasNext())
        {
            Object obj = iter.next();
            if (obj == null)
            {
               output.writeByte(NULLREF); // read is implemented
            } else
            if (obj instanceof String)
            {
                saveString(output, obj); // read is implemented
            } else
            if (obj instanceof ByteContainer || obj instanceof Byte)
            {
               // Although this was not supposed to happen as we will use array of bytes, we still want to keep it just in case
               writeByte(output, obj); // read is implemented
            } else
            if (obj instanceof CharacterContainer || obj instanceof Character)
            {
               writeCharacter(output, obj); // read is implemented
            } else
            if (obj instanceof ShortContainer || obj instanceof Short)
            {
               writeShort(output, obj); // read is implemented
            } else
            if (obj instanceof IntegerContainer || obj instanceof Integer)
            {
                writeInteger(output, obj); // read is implemented
            } else
            if (obj instanceof LongContainer || obj instanceof Long)
            {
                writeLong(output,obj); // read is implemented
            } else
            if (obj instanceof DoubleContainer || obj instanceof Double)
            {
                writeDouble(output,obj); // read is implemented
            } else
            if (obj instanceof FloatContainer || obj instanceof Float)
            {
                writeFloat(output,obj); // read is implemented
            } else
            if (obj instanceof BooleanContainer || obj instanceof Boolean)
            {
                writeBoolean(output,obj);
            } else
            if (obj instanceof byte[])
            {
                writeByteArray(output,obj);
            } else
            {
                throw new SerializationException("I don't know how to write type " + obj.getClass().getName() + " yet");
            }
        }
    }

    /**
     * @param output
     * @param obj
     */
    private void writeFloat(DataOutput output, Object obj) throws IOException
    {
        if (obj instanceof FloatContainer)
        {
            output.writeByte(FLOAT);
            output.writeFloat(((FloatContainer)obj).getValue());
        }
        else
        {
            output.writeByte(FLOATOBJ);
            output.writeFloat(((Float)obj).floatValue());
        }
    }

    private void writeByteArray(DataOutput output, Object obj) throws IOException
    {
        output.writeByte(BYTEARRAY);
        output.writeInt(((byte[])obj).length);
        output.write((byte[])obj);
    }

    /**
     * @param output
     * @param obj
     */
    private void writeBoolean(DataOutput output, Object obj) throws IOException
    {
        if (obj instanceof BooleanContainer)
        {
            output.writeByte(BOOLEAN);
            output.writeBoolean(((BooleanContainer)obj).getValue());
        }
        else
        {
            output.writeByte(BOOLEANOBJ);
            output.writeBoolean(((Boolean)obj).booleanValue());
        }
    }

    /**
     * @param output
     * @param obj
     */
    private void writeLong(DataOutput output, Object obj) throws IOException
    {
        if (obj instanceof LongContainer)
        {
            output.writeByte(LONG);
            output.writeLong(((LongContainer)obj).getValue());
        }
        else
        {
            output.writeByte(LONGOBJ);
            output.writeLong(((Long)obj).longValue());
        }
    }

    /**
     * @param output
     * @param obj
     */
    private void writeShort(DataOutput output, Object obj) throws IOException
    {
        if (obj instanceof ShortContainer)
        {
            output.writeByte(SHORT);
            output.writeShort(((ShortContainer)obj).getValue());
        }
        else
        {
            output.writeByte(SHORTOBJ);
            output.writeShort(((Short)obj).shortValue());
        }
    }

    /**
     * @param output
     * @param obj
     */
    private void writeCharacter(DataOutput output, Object obj) throws IOException
    {
        if (obj instanceof Character)
        {
            output.writeByte(CHARACTEROBJ);
            output.writeChar(((Character)obj).charValue());
        }
        else
        {
            output.writeByte(CHARACTER);
            output.writeChar(((CharacterContainer)obj).getValue());
        }
    }

    /**
     * This is probably never used as we always use byteArrays for bytes, due to our implementation in the DataOutput.
     * @param output
     * @param obj
     */
    private void writeByte(DataOutput output, Object obj) throws IOException
    {
        if (obj instanceof ByteContainer)
        {
            output.writeByte(BYTE);
            output.writeByte(((ByteContainer)obj).getValue());
        }
        else
        {
            output.writeByte(BYTEOBJ);
            output.writeByte(((Byte)obj).byteValue());
        }
    }

    /* (non-Javadoc)
     * @see org.jboss.serial.objectmetamodel.DataExport#readMyself(java.io.DataInput)
     */
    public void readMyself(DataInput input) throws IOException
    {
        int size = input.readInt();

        content.clear();

        for (int i=0;i<size;i++)
        {
            byte type = input.readByte();

            switch (type)
            {
                case NULLREF:
                    content.add(null);break;
                case STRING:
                    content.add(StringUtil.readString(input,cache.getStringBuffer())); break;
                case BYTE:
                    content.add(new ByteContainer(input.readByte()));break;
                case BYTEOBJ:
                    content.add(new Byte(input.readByte()));break;
                case SHORT:
                    content.add(new ShortContainer(input.readShort()));break;
                case SHORTOBJ:
                    content.add(new Short(input.readShort()));break;
                case INTEGER:
                    content.add(new IntegerContainer(input.readInt()));break;
                case INTEGEROBJ:
                    content.add(new Integer(input.readInt()));break;
                case LONG:
                    content.add(new LongContainer(input.readLong()));break;
                case LONGOBJ:
                    content.add(new Long(input.readLong()));break;
                case FLOAT:
                    content.add(new FloatContainer(input.readFloat()));break;
                case FLOATOBJ:
                    content.add(new Float(input.readFloat()));break;
                case DOUBLE:
                    content.add(new DoubleContainer(input.readDouble()));break;
                case DOUBLEOBJ:
                    content.add(new Double(input.readDouble()));break;
                case CHARACTER:
                    content.add(new CharacterContainer(input.readChar()));break;
                case CHARACTEROBJ:
                    content.add(new Character(input.readChar()));break;
                case BOOLEAN:
                    content.add(BooleanContainer.valueOf(input.readBoolean()));break;
                case BOOLEANOBJ:
                    content.add(Boolean.valueOf(input.readBoolean()));break;
                case BYTEARRAY:
                    int sizebArray = input.readInt();
                    byte [] barray = new byte[sizebArray ];
                    input.readFully(barray);
                    content.add(barray);
                    break;
                default:
                throw new SerializationException ("I don't know how to read type " + type + " yet");
            }
        }
    }


    public void writeExternal(ObjectOutput out) throws IOException {
        this.saveData(out);
    }

    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
        this.loadData(in);
    }

    public byte[] getControlStreaming() {
        if (controlStreaming==null)
        {
            controlStreaming=new byte[0];
        }
        return controlStreaming;
    }

    public void setControlStreaming(byte[] controlStreaming) {
        this.controlStreaming = controlStreaming;
    }

	public ClassResolver getClassResolver() {
		return cache.getClassResolver();
	}

	public void setClassResolver(ClassResolver resolver) {
		cache.setClassResolver(resolver);
	}

	public StringUtilBuffer getStringBuffer() {
		return cache.getStringBuffer();
	}

	public void setStringBuffer(StringUtilBuffer stringBuffer) {
		cache.setStringBuffer(stringBuffer);
	}
    
}