/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.sun.jini.reggie;

import com.sun.jini.proxy.MarshalledWrapper;
import com.sun.jini.reggie.ClassMapper.EntryField;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.rmi.MarshalException;
import java.rmi.RemoteException;
import net.jini.core.entry.Entry;

/**
 * An EntryRep contains the fields of an Entry packaged up for
 * transmission between client-side proxies and the registrar server.
 * Instances are never visible to clients, they are private to the
 * communication between the proxies and the server.
 * <p>
 * This class only has a bare minimum of methods, to minimize
 * the amount of code downloaded into clients.
 *
 * 
 *
 */
class EntryRep implements Serializable, Cloneable {

    private static final long serialVersionUID = 2L;

    /**
     * The Class of the Entry converted to EntryClass.
     *
     * @serial
     */
    public EntryClass eclass;
    /**
     * The codebase of the entry class.
     *
     * @serial
     */
    public String codebase;
    /**
     * The public fields of the Entry, each converted as necessary to
     * a MarshalledWrapper (or left as is if of known java.lang immutable
     * type).  The fields are in super- to subclass order.
     *
     * @serial
     */
    public Object[] fields;

    /**
     * Converts an Entry to an EntryRep.  Any exception that results
     * is bundled up into a MarshalException.
     */
    public EntryRep(Entry entry) throws RemoteException {
	EntryClassBase ecb = ClassMapper.toEntryClassBase(entry.getClass());
	eclass = ecb.eclass;
	codebase = ecb.codebase;
	try {
	    EntryField[] efields = ClassMapper.getFields(entry.getClass());
	    fields = new Object[efields.length];
	    for (int i = efields.length; --i >= 0; ) {
		EntryField f = efields[i];
		Object val = f.field.get(entry);
		if (f.marshal && val != null)
		    val = new MarshalledWrapper(val);
		fields[i] = val;
	    }
	} catch (IOException e) {
	    throw new MarshalException("error marshalling arguments", e);
	} catch (IllegalAccessException e) {
	    throw new MarshalException("error marshalling arguments", e);
	}
    }

    /**
     * Convert back to an Entry.  If the Entry cannot be constructed,
     * null is returned.  If a field cannot be unmarshalled, it is set
     * to null.
     */
    public Entry get() {
	try {
	    Class clazz = eclass.toClass(codebase);
	    EntryField[] efields = ClassMapper.getFields(clazz);
	    Entry entry = (Entry)clazz.newInstance();
	    for (int i = efields.length; --i >= 0; ) {
		Object val = fields[i];
		EntryField f = efields[i];
		Field rf = f.field;
		try {
		    if (f.marshal && val != null)
			val = ((MarshalledWrapper) val).get();
		    rf.set(entry, val);
		} catch (Throwable e) {
		    if (e instanceof IllegalArgumentException) {
			// fix 4872566: work around empty exception message
			String msg = "unable to assign " +
			    ((val != null) ?
				"value of type " + val.getClass().getName() :
				"null") +
			    " to field " + rf.getDeclaringClass().getName() +
			    "." + rf.getName() + " of type " +
			    rf.getType().getName();
			e = new ClassCastException(msg).initCause(e);
		    }
		    RegistrarProxy.handleException(e);
		}
	    }
	    return entry;
	} catch (Throwable e) {
	    RegistrarProxy.handleException(e);
	}
	return null;
    }

    /**
     * We don't need this in the client or the server, but since we
     * redefine equals we provide a minimal hashCode that works.
     */
    public int hashCode() {
	return eclass.hashCode();
    }

    /**
     * EntryReps are equal if they have the same class and the fields
     * are pairwise equal.  This is really only needed in the server,
     * but it's very convenient to have here.
     */
    public boolean equals(Object obj) {
	if (obj instanceof EntryRep) {
	    EntryRep entry = (EntryRep)obj;
	    if (!eclass.equals(entry.eclass) ||
		fields.length != entry.fields.length)
		return false;
	    for (int i = fields.length; --i >= 0; ) {
		if ((fields[i] == null && entry.fields[i] != null) ||
		    (fields[i] != null && !fields[i].equals(entry.fields[i])))
		    return false;
	    }	    
	    return true;
	}
	return false;
    }

    /**
     * Deep clone (which just means cloning the fields array too).
     * This is really only needed in the server, but it's very
     * convenient to have here.
     */
    public Object clone() {
	try { 
	    EntryRep entry = (EntryRep)super.clone();
	    entry.fields = (Object[])entry.fields.clone();
	    return entry;
	} catch (CloneNotSupportedException e) { 
	    throw new InternalError();
	}
    }

    /**
     * Converts an array of Entry to an array of EntryRep.  If needCodebase
     * is false, then the codebase of every EntryRep will be null.
     */
    public static EntryRep[] toEntryRep(Entry[] entries, boolean needCodebase)
	throws RemoteException
    {
	EntryRep[] reps = null;
	if (entries != null) {
	    reps = new EntryRep[entries.length];
	    for (int i = entries.length; --i >= 0; ) {
		if (entries[i] != null) {
		    reps[i] = new EntryRep(entries[i]);
		    if (!needCodebase)
			reps[i].codebase = null;
		}
	    }
	}
	return reps;
    }

    /** Converts an array of EntryRep to an array of Entry. */
    public static Entry[] toEntry(EntryRep[] reps) {
	Entry[] entries = null;
	if (reps != null) {
	    entries = new Entry[reps.length];
	    for (int i = reps.length; --i >= 0; ) {
		entries[i] = reps[i].get();
	    }
	}
	return entries;
    }
}
