/*
* 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.io;

import org.jboss.serial.objectmetamodel.DataContainer;
import org.jboss.serial.objectmetamodel.DataContainerConstants;
import org.jboss.serial.objectmetamodel.ObjectSubstitutionInterface;
import org.jboss.serial.objectmetamodel.safecloning.SafeCloningRepository;
import org.jboss.serial.exception.SerializationException;
import org.jboss.serial.util.StringUtilBuffer;
import org.jboss.serial.util.StringUtil;

import java.io.*;
import java.lang.reflect.Field;

/**
 * @author <a href="mailto:clebert.suconic@jboss.com">Clebert Suconic</a>
 */
public class JBossObjectOutputStream extends ObjectOutputStream implements DataContainerConstants
{
    OutputStream output;
    DataOutputStream dataOutput;

    boolean checkSerializableClass=false;

    static Field fieldEnableReplace;

    /** one of the optimizations we do is to reuse byte arrays on writeUTF operations, look at {@link org.jboss.serial.util.StringUtil}.
     *  StringUtil has also the capability of creating Buffers on ThreadLocal over demand, but having these buffers pre-created
     *  is more efficient */
    StringUtilBuffer buffer;

    static
    {
        try {
            fieldEnableReplace = ObjectOutputStream.class.getDeclaredField("enableReplace");
            fieldEnableReplace.setAccessible(true);
        } catch (NoSuchFieldException e) {
        	e.printStackTrace();
        }
    }


    protected ObjectSubstitutionInterface getSubstitutionInterface() throws IOException
    {
        try
        {
            if (fieldEnableReplace.getBoolean(this))
            {
                return new ObjectSubstitutionInterface()
                {
                    public Object replaceObject(Object obj) throws IOException {
                        return JBossObjectOutputStream.this.replaceObject(obj);
                    }
                };
            } else
            {
                return null;
            }
        }
        catch (IllegalAccessException ex)
        {
            throw new SerializationException(ex.getMessage(),ex);
        }
    }


    protected boolean enableReplaceObject(boolean enable)
    {
        try
        {
            if (enable == fieldEnableReplace.getBoolean(this)) {
                return enable;
            }
            fieldEnableReplace.setBoolean(this,enable);
            return !fieldEnableReplace.getBoolean(this);
        }
        catch (Exception e)
        {
        	e.printStackTrace();
            // trying the super method
            return super.enableReplaceObject(enable);
        }
    }


    /**Creates an OutputStream, that by default doesn't require */
    public JBossObjectOutputStream(OutputStream output) throws IOException
    {
        this(output,false);
    }

    /**Creates an OutputStream, that by default doesn't require */
    public JBossObjectOutputStream(OutputStream output,StringUtilBuffer buffer) throws IOException
    {
        this(output,false,buffer);
    }

    public JBossObjectOutputStream(OutputStream output, boolean checkSerializableClass) throws IOException
    {
        this(output,checkSerializableClass,null);
    }

    public JBossObjectOutputStream(OutputStream output, boolean checkSerializableClass,StringUtilBuffer buffer) throws IOException
    {
        super();
        
        if (output!=null)
        {
        	output.write(openSign);
        }
        
        this.buffer=buffer;
        this.output=output;
        this.checkSerializableClass=checkSerializableClass;

        if (output instanceof DataOutputStream)
        {
            dataOutput = (DataOutputStream) output;
        }
        else
        {
            dataOutput = new DataOutputStream(output);
        }
    }

    public void writeObjectUsingDataContainer(Object obj) throws IOException {
        DataContainer dataContainer = new DataContainer(null,this.getSubstitutionInterface(),checkSerializableClass,buffer);
        if (output instanceof DataOutputStream)
        {
            dataOutput = (DataOutputStream) output;
        }
        else
        {
            dataOutput = new DataOutputStream(output);
        }

        ObjectOutput objectOutput = dataContainer.getOutput();
        objectOutput.writeObject(obj);

        //objectOutput.flush();
        dataContainer.saveData(dataOutput);

        //this.flush();
    }

    protected void writeObjectOverride(Object obj) throws IOException {
        DataContainer dataContainer = new DataContainer(null,this.getSubstitutionInterface(),checkSerializableClass,buffer);
        if (output instanceof DataOutputStream)
        {
            dataOutput = (DataOutputStream) output;
        }
        else
        {
            dataOutput = new DataOutputStream(output);
        }
        
        dataContainer.setStringBuffer(buffer);

        ObjectOutput objectOutput = dataContainer.getDirectOutput(this.dataOutput);
        objectOutput.writeObject(obj);

        //objectOutput.flush();
        //dataContainer.saveData(dataOutput);

        //this.flush();
    }

    public void writeUnshared(Object obj) throws IOException {
        writeObjectOverride(obj);
    }

    public void defaultWriteObject() throws IOException {
    }

    public void writeFields() throws IOException {
    }

    public void reset() throws IOException {
    }

    protected void writeStreamHeader() throws IOException {
    }

    protected void writeClassDescriptor(ObjectStreamClass desc)
    throws IOException
    {
    }

    /**
     * Writes a byte. This method will block until the byte is actually
     * written.
     *
     * @param   val the byte to be written to the stream
     * @throws  IOException If an I/O error has occurred.
     */
    public void write(int val) throws IOException {
           dataOutput.write(val);
    }

    /**
     * Writes an array of bytes. This method will block until the bytes are
     * actually written.
     *
     * @param   buf the data to be written
     * @throws  IOException If an I/O error has occurred.
     */
    public void write(byte[] buf) throws IOException {
        dataOutput.write(buf);
    }

    public void write(byte[] buf, int off, int len) throws IOException {
    if (buf == null) {
        throw new SerializationException("buf parameter can't be null");
    }
        dataOutput.write(buf, off, len);
    }

    /**
     * Flushes the stream. This will write any buffered output bytes and flush
     * through to the underlying stream.
     *
     * @throws  IOException If an I/O error has occurred.
     */
    public void flush() throws IOException {
        if (dataOutput!=null)
        {
            dataOutput.flush();
        }
        else
        {
            output.flush();
        }
    }

    protected void drain() throws IOException {
        //bout.drain();
    }

    public void close() throws IOException {
        flush();
        dataOutput.close();
    }

    public void writeBoolean(boolean val) throws IOException {
        dataOutput.writeBoolean(val);
    }

    public void writeByte(int val) throws IOException  {
        dataOutput.writeByte(val);
    }

    public void writeShort(int val)  throws IOException {
        dataOutput.writeShort(val);
    }

    public void writeChar(int val)  throws IOException {
        dataOutput.writeChar(val);
    }

    public void writeInt(int val)  throws IOException {
        dataOutput.writeInt(val);
    }

    public void writeLong(long val)  throws IOException {
        dataOutput.writeLong(val);
    }

    public void writeFloat(float val) throws IOException {
        dataOutput.writeFloat(val);
    }

    public void writeDouble(double val) throws IOException {
        dataOutput.writeDouble(val);
    }

    public void writeBytes(String str) throws IOException {
        dataOutput.writeBytes(str);
    }

    public void writeChars(String str) throws IOException {
        dataOutput.writeChars(str);
    }

    public void writeUTF(String str) throws IOException {
        StringUtil.saveString(dataOutput,str,buffer);
    }

    /** Reuses every primitive value to recreate another object. */
    public Object smartClone(Object obj) throws IOException
    {
        return smartClone(obj,null,Thread.currentThread().getContextClassLoader());
    }

    /** Reuses every primitive value to recreate another object. */
    public Object smartClone(Object obj, SafeCloningRepository safeToReuse) throws IOException
    {
        return smartClone(obj,safeToReuse,Thread.currentThread().getContextClassLoader());
    }

    /** Reuses every primitive value to recreate another object.
     *  and if safeToReuse!=null, it can reuse the entire object */
    public Object smartClone(Object obj, SafeCloningRepository safeToReuse, ClassLoader loader) throws IOException
    {

        DataContainer container = new DataContainer(loader,this.getSubstitutionInterface(),safeToReuse,checkSerializableClass,buffer);
        ObjectOutput output = container.getOutput();
        output.writeObject(obj);
        output.flush();

        ObjectInput input = container.getInput();
        try
        {
            return input.readObject();
        }
        catch (ClassNotFoundException e)
        {
            throw new SerializationException (e.getMessage(),e);
        }
    }




}
