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

import gnu.trove.TLongObjectHashMap;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;

import org.jboss.serial.references.MethodPersistentReference;
import org.jboss.serial.references.PersistentReference;
import org.jboss.serial.util.ClassMetaConsts;
import org.jboss.serial.util.HashStringUtil;

public class ClassMetaDataSlot implements ClassMetaConsts
{
	
	public ClassMetaDataSlot(Class slotClass)
	{
		this.slotClass = new WeakReference(slotClass);
		this.name = slotClass.getName();
		this.shaHash = HashStringUtil.hashName(this.name);

		if (!Serializable.class.isAssignableFrom(slotClass))
		{
			explorefieldsNonSerializable(slotClass);
		}
		else
		{
			exploreFields(slotClass);
		}
		explorePrivateMethod(slotClass);
	}
	
	private void explorePrivateMethod(Class slotClass)
	{
    	Method method = null;
        try
        {
            method = slotClass.getDeclaredMethod("readObject",new Class[]{ObjectInputStream.class});
            method.setAccessible(true);
            this.setPrivateMethodRead(method);
        }
        catch (Exception ignored)
        {
        }

        try
        {
            method = slotClass.getDeclaredMethod("writeObject",new Class[]{ObjectOutputStream.class});
            method.setAccessible(true);
            this.setPrivateMethodWrite(method);
        }
        catch (Exception ignored)
        {
        }
	}
	
	private void exploreFields(Class slotClass)
	{
        Field[] fields = slotClass.getDeclaredFields();
        ArrayList fieldsList = new ArrayList();
        for (short i = 0; i < fields.length; i++)
        {
            if ((fields[i].getModifiers() & (Modifier.TRANSIENT | Modifier.STATIC))==0)
            {
                fields[i].setAccessible(true);
                ClassMetadataField classfield = new ClassMetadataField(fields[i]);
                FieldsManager.getFieldsManager().fillMetadata(classfield);
                this.addField(classfield.getShaHash(),classfield.getFieldName(),classfield);
                classfield.setOrder((short)fieldsList.size());
                fieldsList.add(classfield);
            }
        }
        this.fieldsCollection =(ClassMetadataField[])fieldsList.toArray(new ClassMetadataField[fieldsList.size()]);
	}
	
	private void explorefieldsNonSerializable(Class slotClass)
	{
        ArrayList fieldsList = new ArrayList();
		while (slotClass!=null && slotClass!=Object.class)
		{
	        Field[] fields = slotClass.getDeclaredFields();
	        for (short i = 0; i < fields.length; i++)
	        {
	            if ((fields[i].getModifiers() & (Modifier.TRANSIENT | Modifier.STATIC))==0)
	            {
	                fields[i].setAccessible(true);
	                ClassMetadataField classfield = new ClassMetadataField(fields[i]);
	                FieldsManager.getFieldsManager().fillMetadata(classfield);
	                this.addField(classfield.getShaHash(),classfield.getFieldName(),classfield);
	                classfield.setOrder((short)fieldsList.size());
	                fieldsList.add(classfield);
	            }
	        }
	        slotClass=slotClass.getSuperclass();
		}
        this.fieldsCollection =(ClassMetadataField[])fieldsList.toArray(new ClassMetadataField[fieldsList.size()]);
	}
	
	WeakReference slotClass;
	String name;
    HashMap fields = new HashMap();
    TLongObjectHashMap hashFields = new TLongObjectHashMap();
    long shaHash;
    
    PersistentReference privateMethodWrite = emptyReference;
    PersistentReference privateMethodRead = emptyReference;
    
    /** This collection exists just to ensure order.
     *  @todo - If it's possible to use a fastHashMap that keeps the order, this fieldsCollections should go away */
    ClassMetadataField[] fieldsCollection;

    public Class getSlotClass()
    {
    	return (Class)slotClass.get();
    }

    /**
     * @param fieldName
     * @param classfield
     */
    private void addField(long shaHashKey, String fieldName, ClassMetadataField classfield)
    {
        this.fields.put(fieldName,classfield);
        this.hashFields.put(shaHashKey,classfield);
    }

    /**
     * @return
     */
    public ClassMetadataField getField(String name)
    {
        return (ClassMetadataField)this.fields.get(name);
    }
    
    public ClassMetadataField getField(long shaKey)
    {
    	return (ClassMetadataField)this.hashFields.get(shaKey);
    }
    
    public ClassMetadataField[] getFields()
    {
    	return fieldsCollection;
    }

    public Method getPrivateMethodRead() 
    {
        return (Method)privateMethodRead.get();
    }

    public void setPrivateMethodRead(Method privateMethodRead)
    {
        this.privateMethodRead = new MethodPersistentReference(privateMethodRead,REFERENCE_TYPE_IN_USE);
    }

    public Method getPrivateMethodWrite() 
    {
        return (Method)privateMethodWrite.get();
    }

    public void setPrivateMethodWrite(Method privateMethodWrite)
    {
        this.privateMethodWrite = new MethodPersistentReference(privateMethodWrite,REFERENCE_TYPE_IN_USE);
    }

	public long getShaHash() {
		return shaHash;
	}

	public void setShaHash(long shaHash) {
		this.shaHash = shaHash;
	}
	

}
