/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.0 (the
 * "License"). You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at
 * http://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */
package net.jini.entry;

import net.jini.core.entry.Entry;
import java.lang.reflect.*;

import java.util.WeakHashMap;
import java.util.ArrayList;

/**
 * This class is a specific, generally useful base class for entry types.
 * An entry does not need to subclass this type, but you may find it useful
 * to do so.
 *
 * @author Sun Microsystems, Inc.
 *
 * @see Entry
 */
public abstract class AbstractEntry implements Entry {
    static final long serialVersionUID = 5071868345060424804L;

    /**
     * Returns <code>true</code> if the other object has the same value as this
     * one.  This means that the other object is of the same class, and
     * that, for each field <i>F</i> of the class, references in both
     * objects are <code>null</code> or <code>equals</code> returns
     * <code>true</code> when invoked on the value of <i>F</i> from one
     * object with the other object as a parameter.
     */
    public boolean equals(Object other) {
	if (!(other instanceof Entry))
	    return false;
	return equals(this, (Entry) other);
    }

    /**
     * Returns <code>true</code> if the two objects have the same value.
     * This means that both objects are of the same class, and
     * that, for each field <i>F</i> of the class, references in both
     * objects are <code>null</code> or <code>equals</code> returns
     * <code>true</code> when invoked on the value of <i>F</i> from one
     * object with the other object as a parameter.
     *
     * @param e1  an entry object to compare to e2
     * @param e2  an entry object to compare to e1
     * @return <code>true</code> if the two objects have the same value
     */
    public static boolean equals(Entry e1, Entry e2) {
	if (e1 == e2)
	    return true;

	if (e1.getClass() != e2.getClass())
	    return false;

	Field[] fields = fieldInfo(e1);
	try {
	    // compare each field
	    for (int i = 0; i < fields.length; i++) {

		// f works for other since other is the same type as this
		Field f = fields[i];
		Object ov = f.get(e1);
		Object tv = f.get(e2);

		if (tv == ov)			// same obj or both null is OK
		    continue;
		if (tv == null || ov == null)	// if only one is null, not OK
		    return false;
		if (!tv.equals(ov))		// not equals is not OK
		    return false;
	    }
	    return true;
	} catch (IllegalAccessException e) {
	    return false;	// should never happen -- all fields are public
	}
    }

    /**
     * Returns a hash code.  The hash code will be zero XOR the hash
     * code of each non-<code>null</code> field. Overrides 
     * Object.hashCode().
     */
    public int hashCode() {
	return hashCode(this);
    }

    /**
     * Returns a hash code.  The hash code will be zero XOR the hash
     * code of each non-<code>null</code> field.
     *
     * @param entry  the entry for which to generate a hash code
     * @return the hash code value for the given entry
     */
    public static int hashCode(Entry entry) {
	int hash = 0;
	Field[] fields = fieldInfo(entry);
	try {
	    for (int i = 0; i < fields.length; i++) {
		Object tv = fields[i].get(entry);
		if (tv != null)
		    hash ^= tv.hashCode();
	    }
	    return hash;
	} catch (IllegalAccessException e) {
	    return 0;		// should never happen -- all fields are public
	}
    }

    /**
     * Print out the entry as the sum of all its fields. Overrides 
     * Object.toString().
     */
    public String toString() {
	return toString(this);
    }

    /**
     * Print out the given entry as the sum of all its fields.
     *
     * @param entry  an entry to represent as a String
     * @return a String representing each of the fields of the entry
     */
    public static String toString(Entry entry) {
	Field[] fields = fieldInfo(entry);
	StringBuffer str = new StringBuffer(entry.getClass().getName());
	str.append('(');	// later matched with a ')'
	for (int i = 0; i < fields.length; i++) {
	    try {
		Field f = fields[i];
		if (i > 0)
		    str.append(',');
		str.append(f.getName());
		str.append('=');
		str.append(f.get(entry));
	    } catch (IllegalAccessException e) {
		// should never happen -- all fields are public
		str.append("<IllegalAccessException>");
	    }
	}
	// now add the ending '('
	str.append(')');
	return str.toString();
    }

    private static WeakHashMap fieldArrays;

    /**
     * Calculate the list of usable fields for this type
     */
    private static Field[] fieldInfo(Entry entry) {
	Field[] fields = null;
	if (fieldArrays == null)
	    fieldArrays = new WeakHashMap();
	else {
	    fields = (Field[]) fieldArrays.get(entry.getClass());
	    if (fields != null)
		return fields;
	}

	/*
	 * Scan the array to see if we can use it or if we must build up
	 * a smaller array because we must skip some fields.  If so, we
	 * create an ArrayList and add the unskippable fields to it, and
	 * then fetch the array back out of it.
	 */
	final int SKIP_MODIFIERS =
	    (Modifier.STATIC | Modifier.TRANSIENT | Modifier.FINAL);
	fields = entry.getClass().getFields();
	ArrayList usable = null;
	for (int i = 0; i < fields.length; i++) {
	    // exclude this one?
	    if ((fields[i].getModifiers() & SKIP_MODIFIERS) != 0) {
		if (usable == null) {		// first excluded: set up for it
		    usable = new ArrayList();	// allocate the list of usable
		    for (int j = 0; j < i; j++)	// earlier fields are usable
			usable.add(fields[j]);
		}
	    } else {				// not excluded
		if (usable != null)		// tracking usable fields?
		    usable.add(fields[i]);
	    }
	}
	if (usable != null)
	    fields = (Field[]) usable.toArray(new Field[usable.size()]);
	fieldArrays.put(entry.getClass(), fields);
	return fields;
    }
}
