/* Copyright (c) 2001, Oracle Corporation. All rights reserved. */
package oracle.portal.provider.v2.preference;

import java.io.*;
import java.util.*;
import oracle.portal.provider.v2.*;
import oracle.portal.log.LogManager;

/**
 * Base preference data class. Uses proprietary data format rather than native
 * Java serialization so that data classes may be extended and renamed without
 * breaking their existing data.
 */
public class NameValuePreferenceDataObject implements PreferenceDataObject
{
    private String mVersion = null;
    private Hashtable mContent = new Hashtable();

    // Constants enumerating allowed value types
    private static final int INTEGER_TYPE       = 1;
    private static final int DOUBLE_TYPE        = 2;
    private static final int FLOAT_TYPE         = 3;
    private static final int LONG_TYPE          = 4;
    private static final int BOOLEAN_TYPE       = 5;
    private static final int STRING_TYPE        = 6;
    private static final int INTEGER_ARRAY_TYPE = 7;
    private static final int DOUBLE_ARRAY_TYPE  = 8;
    private static final int FLOAT_ARRAY_TYPE   = 9;
    private static final int LONG_ARRAY_TYPE    = 10;
    private static final int BOOLEAN_ARRAY_TYPE = 11;
    private static final int STRING_ARRAY_TYPE  = 12;

    /**
     * Null Constructor.
     */
    public NameValuePreferenceDataObject() 
    {
    }

    /**
     * Sets the version associated with the content of the
     * <code>NameValuePreferenceDataObject</code>. The version number is a String to allow
     * you to define the structure of your version numbers.
     *
     * @param version version string to be associated with the content of the
     * <code>NameValuePreferenceDataObject</code>
     */
    public void setContentVersion(String version) 
    {
        mVersion = version;
    }

    /**
     * @return the version String associated with the content of the
     * <code>NameValuePreferenceDataObject</code>
     */
    public String getContentVersion() 
    {
        return mVersion;
    }

    /**
     * Records the given Integer value against the given name in the
     * <code>NameValuePreferenceDataObject</code> content.  If the name already exists, then 
     * the value for that name will be overwritten with the new value given
     * @param name String name against which the value is to be stored
     * @param value Integer value
     */
    public void putInteger(String name, Integer value)
    {
        if (value != null)
        {
            mContent.put(name, new TypeValueWrapper(value));
        }
    }


    /**
     * Records the given Double value against the given name in the
     * <code>NameValuePreferenceDataObject</code> content.  If the name already exists, then 
     * the value for that name will be overwritten with the new value given
     * @param name String name against which the value is to be stored
     * @param value Double value
     */
    public void putDouble(String name, Double value)
    {
        if (value != null)
        {
            mContent.put(name, new TypeValueWrapper(value));
        }
    }

    /**
     * Records the given Float value against the given name in the
     * <code>NameValuePreferenceDataObject</code> content.  If the name already exists, then 
     * the value for that name will be overwritten with the new value given
     * @param name String name against which the value is to be stored
     * @param value Float value
     */
    public void putFloat(String name, Float value)
    {
        if (value != null)
        {
            mContent.put(name, new TypeValueWrapper(value));
        }
    }

    /**
     * Records the given Long value against the given name in the
     * <code>NameValuePreferenceDataObject</code> content.  If the name already exists, then 
     * the value for that name will be overwritten with the new value given
     * @param name String name against which the value is to be stored
     * @param value Long value
     */
    public void putLong(String name, Long value)
    {
        if (value != null)
        {
            mContent.put(name, new TypeValueWrapper(value));
        }
    }

    /**
     * Records the given Boolean value against the given name in the
     * <code>NameValuePreferenceDataObject</code> content.  If the name already exists, then 
     * the value for that name will be overwritten with the new value given
     * @param name String name against which the value is to be stored
     * @param value Boolean value
     */
    public void putBoolean(String name, Boolean value)
    {
        if (value != null)
        {
            mContent.put(name, new TypeValueWrapper(value));
        }
    }

    /**
     * Records the given String value against the given name in the
     * <code>NameValuePreferenceDataObject</code> content.  If the name already exists, then 
     * the value for that name will be overwritten with the new value given
     * @param name String name against which the value is to be stored
     * @param value String value
     */
    public void putString(String name, String value)
    {
        if (value != null)
        {
            mContent.put(name, new TypeValueWrapper(value));
        }
    }

    /**
     * Records the given Integer Array value against the given name in the
     * <code>NameValuePreferenceDataObject</code> content.  If the name already exists, then 
     * the value for that name will be overwritten with the new value given
     * @param name String name against which the value is to be stored
     * @param value Integer array value
     */
    public void putIntegerArray(String name, Integer[] value)
    {
        if (value != null)
        {
            mContent.put(name, new TypeValueWrapper(value));
        }
    }

    /**
     * Records the given Double Array value against the given name in the
     * <code>NameValuePreferenceDataObject</code> content.  If the name already exists, then 
     * the value for that name will be overwritten with the new value given
     * @param name String name against which the value is to be stored
     * @param value Double array value
     */
    public void putDoubleArray(String name, Double[] value)
    {
        if (value != null)
        {
            mContent.put(name, new TypeValueWrapper(value));
        }
    }

    /**
     * Records the given Float Array value against the given name in the
     * <code>NameValuePreferenceDataObject</code> content.  If the name already exists, then 
     * the value for that name will be overwritten with the new value given
     * @param name String name against which the value is to be stored
     * @param value Float array value
     */
    public void putFloatArray(String name, Float[] value)
    {
        if (value != null)
        {
            mContent.put(name, new TypeValueWrapper(value));
        }
    }

    /**
     * Records the given Long Array value against the given name in the
     * <code>NameValuePreferenceDataObject</code> content.  If the name already exists, then 
     * the value for that name will be overwritten with the new value given
     * @param name String name against which the value is to be stored
     * @param value Long array value
     */
    public void putLongArray(String name, Long[] value)
    {
        if (value != null)
        {
            mContent.put(name, new TypeValueWrapper(value));
        }
    }

    /**
     * Records the given Boolean Array value against the given name in the
     * <code>NameValuePreferenceDataObject</code> content.  If the name already exists, then 
     * the value for that name will be overwritten with the new value given
     * @param name String name against which the value is to be stored
     * @param value Boolean array value
     */
    public void putBooleanArray(String name, Boolean[] value)
    {
        if (value != null)
        {
            mContent.put(name, new TypeValueWrapper(value));
        }
    }

    /**
     * Records the given String Array value against the given name in the
     * <code>NameValuePreferenceDataObject</code> content.  If the name already exists, then 
     * the value for that name will be overwritten with the new value given
     * @param name String name against which the value is to be stored
     * @param value String array value
     */
    public void putStringArray(String name, String[] value)
    {
        if (value != null)
        {
            mContent.put(name, new TypeValueWrapper(value));
        }
    }

    /**
     * Retrieves the Integer value stored against the given name from the
     * content of the <code>NameValuePreferenceDataObject</code>.
     * @param name String name against which the value is stored
     * @return Integer value stored against the given name if one exists, or
     * null otherwise
     * @exception ClassCastException if the value stored against the given name
     * is not an Integer.
     */
    public Integer getInteger(String name)
    {
        TypeValueWrapper value = (TypeValueWrapper)mContent.get(name);
        return value == null ? null : (Integer)(value.getValue());
    }

    /**
     * Retrieves the Double value stored against the given name from the content
     * of the <code>NameValuePreferenceDataObject</code>.
     * @param name String name against which the value is stored
     * @return Double value stored against the given name if one exists, or
     * null otherwise
     * @exception ClassCastException if the value stored against the given
     * name is not a Double.
     */
    public Double getDouble(String name)
    {
        TypeValueWrapper value = (TypeValueWrapper)mContent.get(name);
        return value == null ? null : (Double)(value.getValue());
    }

    /**
     * Retrieves the Float value stored against the given name from the content
     * of the <code>NameValuePreferenceDataObject</code>.
     * @param name String name against which the value is stored
     * @return Float value stored against the given name if one exists, or
     * null otherwise
     * @exception ClassCastException if the value stored against the given
     * name is not a Float.
     */
    public Float getFloat(String name)
    {
        TypeValueWrapper value = (TypeValueWrapper)mContent.get(name);
        return value == null ? null : (Float)(value.getValue());
    }

    /**
     * Retrieves the Long value stored against the given name from the content
     * of the <code>NameValuePreferenceDataObject</code>.
     * @param name String name against which the value is stored
     * @return Long value stored against the given name if one exists, or
     * null otherwise
     * @exception ClassCastException if the value stored against the given
     * name is not a Long.
     */
    public Long getLong(String name)
    {
        TypeValueWrapper value = (TypeValueWrapper)mContent.get(name);
        return value == null ? null : (Long)(value.getValue());
    }

    /**
     * Retrieves the Boolean value stored against the given name from the content
     * of the <code>NameValuePreferenceDataObject</code>.
     * @param name String name against which the value is stored
     * @return Boolean value stored against the given name if one exists, or
     * null otherwise
     * @exception ClassCastException if the value stored against the given
     * name is not a Boolean.
     */
    public Boolean getBoolean(String name)
    {
        TypeValueWrapper value = (TypeValueWrapper)mContent.get(name);
        return value == null ? null : (Boolean)(value.getValue());
    }

    /**
     * Retrieves the String value stored against the given name from the content
     * of the <code>NameValuePreferenceDataObject</code>.
     * @param name String name against which the value is stored
     * @return String value stored against the given name if one exists, or
     * null otherwise
     * @exception ClassCastException if the value stored against the given
     * name is not a String.
     */
    public String getString(String name)
    {
        TypeValueWrapper value = (TypeValueWrapper)mContent.get(name);
        return value == null ? null : (String)(value.getValue());
    }

    /**
     * Retrieves the Integer Array value stored against the given name from the content
     * of the <code>NameValuePreferenceDataObject</code>.
     * @param name String name against which the value is stored
     * @return Integer Array value stored against the given name if one exists, or
     * null otherwise
     * @exception ClassCastException if the value stored against the given
     * name is not an Integer array.
     */
    public Integer[] getIntegerArray(String name)
    {
        TypeValueWrapper value = (TypeValueWrapper)mContent.get(name);
        return value == null ? null : (Integer[])(value.getValue());
    }

    /**
     * Retrieves the Double Array value stored against the given name from the content
     * of the <code>NameValuePreferenceDataObject</code>.
     * @param name String name against which the value is stored
     * @return Double Array value stored against the given name if one exists, or
     * null otherwise
     * @exception ClassCastException if the value stored against the given
     * name is not a Double array.
     */
    public Double[] getDoubleArray(String name)
    {
        TypeValueWrapper value = (TypeValueWrapper)mContent.get(name);
        return value == null ? null : (Double[])(value.getValue());
    }

    /**
     * Retrieves the Float Array value stored against the given name from the content
     * of the <code>NameValuePreferenceDataObject</code>.
     * @param name String name against which the value is stored
     * @return Float Array value stored against the given name if one exists, or
     * null otherwise
     * @exception ClassCastException if the value stored against the given
     * name is not a Float array.
     */
    public Float[] getFloatArray(String name)
    {
        TypeValueWrapper value = (TypeValueWrapper)mContent.get(name);
        return value == null ? null : (Float[])(value.getValue());
    }

    /**
     * Retrieves the Long Array value stored against the given name from the content
     * of the <code>NameValuePreferenceDataObject</code>.
     * @param name String name against which the value is stored
     * @return Long Array value stored against the given name if one exists, or
     * null otherwise
     * @exception ClassCastException if the value stored against the given
     * name is not a Long array.
     */
    public Long[] getLongArray(String name)
    {
        TypeValueWrapper value = (TypeValueWrapper)mContent.get(name);
        return value == null ? null : (Long[])(value.getValue());
    }

    /**
     * Retrieves the Boolean Array value stored against the given name from the content
     * of the <code>NameValuePreferenceDataObject</code>.
     * @param name String name against which the value is stored
     * @return Boolean Array value stored against the given name if one exists, or
     * null otherwise
     * @exception ClassCastException if the value stored against the given
     * name is not a Boolean array.
     */
    public Boolean[] getBooleanArray(String name)
    {
        TypeValueWrapper value = (TypeValueWrapper)mContent.get(name);
        return value == null ? null : (Boolean[])(value.getValue());
    }

    /**
     * Retrieves the String Array value stored against the given name from the content
     * of the <code>NameValuePreferenceDataObject</code>.
     * @param name String name against which the value is stored
     * @return String Array value stored against the given name if one exists, or
     * null otherwise
     * @exception ClassCastException if the value stored against the given
     * name is not a String array.
     */
    public String[] getStringArray(String name)
    {
        TypeValueWrapper value = (TypeValueWrapper)mContent.get(name);
        return value == null ? null : (String[])(value.getValue());
    }

    /**
     * Retrieves an Iterator object containing all names in the content of the
     * <code>NameValuePreferenceDataObject</code>.
     * @return Iterator object containing all names
     */
    public Iterator getNames()
    {
      Set s = mContent.keySet();
      return s.iterator();
    }

    /**
     * Removes the value stored against the given name from the content of the
     * <code>NameValuePreferenceDataObject</code>.
     * @param name String name against which the value is stored
     */
    public void remove(String name)
    {
        mContent.remove(name);
    }

    /**
     * Writes the content of the <code>PreferenceDataObject</code> to a given OutputStream
     *
     * @param os OutputStream to which content should be written
     */
    public void write(OutputStream os) throws IOException 
    {

        // Get a DataOutputStream based on the given output stream, so that
        // we can encode primitive types
        DataOutputStream dos = new DataOutputStream(os);

        // First, write the version number
        dos.writeUTF(mVersion == null ? "-1" : mVersion);

        // Iterate through each key/value pair in the content hash and
        // write type and value to reference file
        for (Enumeration enum = mContent.keys(); enum.hasMoreElements(); )
        {
            String currentKey = (String)enum.nextElement();
            TypeValueWrapper currentVal = (TypeValueWrapper)mContent.get(currentKey);
            writeEntry(currentKey, currentVal, dos);
        }
    }


    /**
     * Populates the <code>PreferenceDataObject</code> with content read from a
     * given InputStream
     *
     * @param is InputStream from which content should be read
     */
    public void read(InputStream is) throws IOException 
    {

        // Get a DataInputStream based on the given incode stream, so that
        // we can decode primitive types
        DataInputStream dis = new DataInputStream(is);

        // Make sure we overwrite the old contents, if there were any!
        mContent.clear();

        // First, get the version number
        mVersion = dis.readUTF();

        if(mVersion.equals("-1"))
        {
            mVersion = null;
        }

        // Continue reading entries until we reach the end of the file
        try 
        {
            for(;;) 
            {
                Object[] currentEntry = readEntry(dis);
                if(currentEntry[0] != null && currentEntry[1] != null) 
                {
                    mContent.put(currentEntry[0], currentEntry[1]);
                }
            }

        }
        catch(EOFException eof) 
        {
        }
    }

    // Encodes a single name/value entry to a DataOutputStream
    private boolean writeEntry(String name, TypeValueWrapper value, DataOutputStream dos)
        throws IOException 
    {

        boolean retCode = true;

        // Write the hash key, <name>, to the output stream
        dos.writeUTF(name);

        // Write the <type> to the output stream from the TypeValueWrapper
        dos.writeInt(value.getType());

        // Write the <value> to the ouput stream from the TypeValueWrapper
        switch (value.getType())
        {
        case INTEGER_TYPE:
            dos.writeInt(((Integer)((TypeValueWrapper)mContent.get(name)).getValue()).intValue());
            break;
        case DOUBLE_TYPE:
            dos.writeDouble(((Double)((TypeValueWrapper)mContent.get(name)).getValue()).doubleValue());
            break;
        case FLOAT_TYPE:
            dos.writeFloat(((Float)((TypeValueWrapper)mContent.get(name)).getValue()).floatValue());
            break;
        case LONG_TYPE:
            dos.writeLong(((Long)((TypeValueWrapper)mContent.get(name)).getValue()).longValue());
            break;
        case BOOLEAN_TYPE:
            dos.writeBoolean(((Boolean)((TypeValueWrapper)mContent.get(name)).getValue()).booleanValue());
            break;
        case STRING_TYPE:
            dos.writeUTF((String)((TypeValueWrapper)mContent.get(name)).getValue());
            break;
        case INTEGER_ARRAY_TYPE:
            {
                Integer[] temp = ((Integer[])((TypeValueWrapper)mContent.get(name)).getValue());
                dos.writeInt(temp.length);
                for (int i = 0; i < temp.length; i++) 
                {
                    dos.writeInt(temp[i].intValue());
                }
            }
            break;
        case DOUBLE_ARRAY_TYPE:
            {
                Double[] temp = ((Double[])((TypeValueWrapper)mContent.get(name)).getValue());
                dos.writeInt(temp.length);
                for (int i = 0; i < temp.length; i++) 
                {
                    dos.writeDouble(temp[i].doubleValue());
                }
            }
            break;
        case FLOAT_ARRAY_TYPE:
            {
                Float[] temp = ((Float[])((TypeValueWrapper)mContent.get(name)).getValue());
                dos.writeInt(temp.length);
                for (int i = 0; i < temp.length; i++) 
                {
                    dos.writeFloat(temp[i].floatValue());
                }
            }
            break;
        case LONG_ARRAY_TYPE:
            {
                Long[] temp = ((Long[])((TypeValueWrapper)mContent.get(name)).getValue());
                dos.writeInt(temp.length);
                for (int i = 0; i < temp.length; i++) 
                {
                    dos.writeLong(temp[i].longValue());
                }
            }
            break;
        case BOOLEAN_ARRAY_TYPE:
            {
                Boolean[] temp = ((Boolean[])((TypeValueWrapper)mContent.get(name)).getValue());
                dos.writeInt(temp.length);
                for (int i = 0; i < temp.length; i++) 
                {
                    dos.writeBoolean(temp[i].booleanValue());
                }
            }
            break;
        case STRING_ARRAY_TYPE:
            {
                String[] temp = ((String[])((TypeValueWrapper)mContent.get(name)).getValue());
                dos.writeInt(temp.length);
                for (int i = 0; i < temp.length; i++) 
                {
                    dos.writeUTF(temp[i]);
                }
            }
            break;
        }//end switch

        return retCode;
    }

    // Decodes a single name/value entry from a DataInputStream
    private Object[] readEntry(DataInputStream dis) throws IOException 
    {

        Object[] retVal = {null, null};
        TypeValueWrapper currentValue = null;

        // First read the name and type
        String currentName = dis.readUTF();
        // Read correct value format and assign type and value to currentValue
        switch (dis.readInt())
        {
        case INTEGER_TYPE:
            currentValue = new TypeValueWrapper(new Integer(dis.readInt()));
            break;
        case DOUBLE_TYPE:
            currentValue = new TypeValueWrapper(new Double(dis.readDouble()));
            break;
        case FLOAT_TYPE:
            currentValue = new TypeValueWrapper(new Float(dis.readFloat()));
            break;
        case LONG_TYPE:
            currentValue = new TypeValueWrapper(new Long(dis.readLong()));
            break;
        case BOOLEAN_TYPE:
            currentValue = new TypeValueWrapper(new Boolean(dis.readBoolean()));
            break;
        case STRING_TYPE:
            currentValue = new TypeValueWrapper(dis.readUTF());
            break;
        case INTEGER_ARRAY_TYPE:
            {
                int arrayLength = dis.readInt();
                Integer[] temp = new Integer[arrayLength];
                for (int i = 0; i < arrayLength; i++)
                {
                    temp[i] = new Integer(dis.readInt());
                }
                currentValue = new TypeValueWrapper(temp);
            }
            break;
        case DOUBLE_ARRAY_TYPE:
            {
                int arrayLength = dis.readInt();
                Double[] temp = new Double[arrayLength];
                for (int i = 0; i < arrayLength; i++)
                {
                    temp[i] = new Double(dis.readDouble());
                }
                currentValue = new TypeValueWrapper(temp);
            }
            break;
        case FLOAT_ARRAY_TYPE:
            {
                int arrayLength = dis.readInt();
                Float[] temp = new Float[arrayLength];
                for (int i = 0; i < arrayLength; i++)
                {
                    temp[i] = new Float(dis.readFloat());
                }
                currentValue = new TypeValueWrapper(temp);
            }
            break;
        case LONG_ARRAY_TYPE:
            {
                int arrayLength = dis.readInt();
                Long[] temp = new Long[arrayLength];
                for (int i = 0; i < arrayLength; i++)
                {
                    temp[i] = new Long(dis.readLong());
                }
                currentValue = new TypeValueWrapper(temp);
            }
            break;
        case BOOLEAN_ARRAY_TYPE:
            {
                int arrayLength = dis.readInt();
                Boolean[] temp = new Boolean[arrayLength];
                for (int i = 0; i < arrayLength; i++)
                {
                    temp[i] = new Boolean(dis.readBoolean());
                }
                currentValue = new TypeValueWrapper(temp);
            }
            break;
        case STRING_ARRAY_TYPE:
            {
                int arrayLength = dis.readInt();
                String[] temp = new String[arrayLength];
                for (int i = 0; i < arrayLength; i++)
                {
                    temp[i] = dis.readUTF();
                }
                currentValue = new TypeValueWrapper(temp);
            }
            break;
        }

        retVal[0] = currentName;
        retVal[1] = currentValue;

        return retVal;
    }

    // Private wrapper class for storing value and type information
    private static class TypeValueWrapper 
    {

        private int mType;
        private Object mValue;

        // Overridden constructors for each of the supported types
        public TypeValueWrapper(Integer value)
        {
            mType = INTEGER_TYPE;
            mValue = value;
        }

        public TypeValueWrapper(Double value)
        {
            mType = DOUBLE_TYPE;
            mValue = value;
        }

        public TypeValueWrapper(Float value)
        {
            mType = FLOAT_TYPE;
            mValue = value;
        }

        public TypeValueWrapper(Long value)
        {
            mType = LONG_TYPE;
            mValue = value;
        }

        public TypeValueWrapper(Boolean value)
        {
            mType = BOOLEAN_TYPE;
            mValue = value;
        }

        public TypeValueWrapper(String value)
        {
            mType = STRING_TYPE;
            mValue = value;
        }

        public TypeValueWrapper(Integer[] value)
        {
            mType = INTEGER_ARRAY_TYPE;
            mValue = value;
        }

        public TypeValueWrapper(Double[] value)
        {
            mType = DOUBLE_ARRAY_TYPE;
            mValue = value;
        }

        public TypeValueWrapper(Float[] value)
        {
            mType = FLOAT_ARRAY_TYPE;
            mValue = value;
        }

        public TypeValueWrapper(Long[] value)
        {
            mType = LONG_ARRAY_TYPE;
            mValue = value;
        }

        public TypeValueWrapper(Boolean[] value)
        {
            mType = BOOLEAN_ARRAY_TYPE;
            mValue = value;
        }

        public TypeValueWrapper(String[] value)
        {
            mType = STRING_ARRAY_TYPE;
            mValue = value;
        }

        public int getType()
        {
            return mType;
        }

        public Object getValue(){
            return mValue;
        }
    }
}


