/*
 * 
 * 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 com.sun.jini.outrigger;

import net.jini.io.MarshalledInstance;
import com.sun.jini.landlord.LeasedResource;

/**
 * This object holds an annotated reference to an
 * <code>EntryRep</code> object.  Currently there is one annotation,
 * which is a hash code for the object that can be used as a
 * quick-reject comparison when scanning through the list.  The handle
 * holds a hash code that is based on the bytes that encode the first
 * <i>N</i> fields, where <i>N</i> is the number of fields in the
 * entry up to a maximum (currently this maximum is 16 (64 bits
 * divided by 4 bits/field), so that 4 is the minimum number of bits
 * per field in the hash).
 * <p>
 * When comparing, the template's own hash is calculated, and also a
 * mask that masks out the hash codes of wildcard fields.  A template
 * will match an entry only if the entry's EntryHandle hash masked with
 * the template's wildcard mask is the same as the template's hash.
 * <p>
 * Care must be taken since the template may be a supertype of the type
 * being searched.  This is why the number of fields in the static
 * methods is passed as an argument, not simply taken from the entry in
 * question.  When a template's hash is being created, its hash value
 * is calculated as if it were of the class being searched, with the
 * subclass's field count.  Any extra fields are assumed to be
 * wildcards.  This means that the template's hash must be recalculated
 * for each subclass it is compared against, but this only happens once
 * per known subclass, and so is probably not onerous.  
 * <p>
 * Note some of the method of this class are synchronized internally, while
 * other are synchronized externally.  Methods which need to be
 * synchronized externally are called out in their comments.
 *
 * @author Sun Microsystems, Inc.
 *
 */
// We do not store this data on the EntryRep object itself because it
// is not really part of the client<->JavaSpaces(TM) service protocol -- 
// some implementations of EntryHolder may not choose to use this
// mechanism.  It does add an extra object per EntryRep object in
// those that *do* use it, and so we may want to re-examine this in the
// future.

class EntryHandle extends BaseHandle implements LeaseDesc, Transactable {
    /** the content hash for the rep */
    private long	hash;

    /** 
     * If this entry is locked by one or more transaction the info
     * on those transactions, otherwise <code>null</code>.
     */
    private transient TxnState    txnState;

    /**
     * Create a new handle, calculating the hash for the object.
     * If <code>mgr</code> is non-<code>null</code> start the entry
     * as write locked under the given transaction.
     * @param rep The rep of the entry this is a handle for
     * @param mgr If this entry is being written under a transaction the
     *            manager for that transaction, otherwise <code>null</code>
     * @param holder If mgr is non-<code>null</code> this must be
     *            the holder holding this handle.  Otherwise it may be
     *            <code>null</code> 
     */
    EntryHandle(EntryRep rep, TransactableMgr mgr, EntryHolder holder) {
	super(rep);
	hash = (rep != null ? hashFor(rep, rep.numFields()) : -1);
	if (mgr == null) {
	    txnState = null;
	} else {
	    if (holder == null) 
		throw new NullPointerException("EntryHandle:If mgr is " +
 	            "non-null holder must be non-null");
	    txnState = new TxnState(mgr, TransactableMgr.WRITE, holder);
	}
    }

    // inherit doc comment
    public LeasedResource getLeasedResource() {
	return rep();
    }

    /**
     * Return this handle's content hash.
     */
    long hash() {
	return hash;
    }

    /**
     * Calculate the hash for a particular entry, assuming the given number
     * of fields.
     *
     * @see #hashFor(EntryRep,int,EntryHandleHashDesc)
     */
    static long hashFor(EntryRep rep, int numFields) {
	return hashFor(rep, numFields, null);
    }

    /**
     * Calculate the hash for a particular entry, assuming the given
     * number of fields, filling in the fields of <code>desc</code>
     * with the relevant values.  <code>desc</code> may be
     * <code>null</code>.  <code>numFields</code> must be >= the number
     * of fields in the <code>rep</code> object (this is not
     * checked).
     *
     * @see #hashFor(EntryRep,int)
     * @see #descFor(EntryRep,int)
     * @see EntryHandleHashDesc
     */
    private static long
	hashFor(EntryRep rep, int numFields, EntryHandleHashDesc hashDesc)
    {
	if (rep == null || numFields == 0)
	    return 0;

	int bitsPerField = Math.max(64 / numFields, 4);	// at least 4 bits
	int fieldsInHash = 64 / bitsPerField;		// max fields used
	long mask =					// per-field bit mask
		    0xffffffffffffffffL >>> (64 - bitsPerField);
	long hash = 0;					// current hash value

	// field counts will be different if rep is a template of a superclass
	long endField = Math.min(fieldsInHash, rep.numFields());

	// set the appropriate rep of the overall hash for the field's hash
	for (int i = 0; i < endField; i++)
	    hash |= (hashForField(rep, i) & mask) << (i * bitsPerField);

	// If someone wants to remember these results, fill 'em in
	if (hashDesc != null) {
	    hashDesc.bitsPerField = bitsPerField;
	    hashDesc.fieldsInHash = fieldsInHash;
	    hashDesc.mask = mask;
	}

	return hash;
    }

    /**
     * Return the template description -- mask and hash.
     *
     * @see EntryHandleTmplDesc
     */
    static EntryHandleTmplDesc descFor(EntryRep tmpl, int numFields) {
	EntryHandleHashDesc hashDesc = new EntryHandleHashDesc();
	EntryHandleTmplDesc tmplDesc = new EntryHandleTmplDesc();

	// Get the hash and the related useful information
	tmplDesc.hash = hashFor(tmpl, numFields, hashDesc);

	// Create the mask to mask away wildcard fields
	for (int i = 0; i < hashDesc.fieldsInHash; i++) {
	    // If this field is one we have a value for, set bits in the mask
	    if (i < tmpl.numFields() && tmpl.value(i) != null)
		tmplDesc.mask |= (hashDesc.mask << (i * hashDesc.bitsPerField));
	}

	// Ensure that the non-value fields are masked out
	tmplDesc.hash &= tmplDesc.mask;

	return tmplDesc;
    }

    /**
     * Return the hash value for a given field, which is then merged in
     * as part of the overall hash for the entry.  The last 32 bytes of
     * the field value are used (or fewer if there are fewer).
     *
     * @see #hashFor(EntryRep,int,EntryHandleHashDesc)
     */
    static long hashForField(EntryRep rep, int field) {
	MarshalledInstance v = rep.value(field);
	if (v == null)	  // for templates, it's just zero
	    return 0;
	else
	    return v.hashCode();
    }

    public String toString() {
	return "0x" + Long.toHexString(hash) + " [" + rep() + "]";
    }

    /**
     * Return <code>true</code> if the operation <code>op</code> under
     * the given transaction (represented by the transaction's manager)
     * can be performed on the object represented by this handle.  The 
     * thread calling this method should own this object's lock.
     */
    // $$$ Calling this method when we don't own the lock on this
    // object seems a bit dicey, but that is exactly what we do in 
    // EntryHolder.SimpleRepEnum.nextRep().  Working it through
    // it seems to work in that particular case, but it seems fragile.
    boolean canPerform(TransactableMgr mgr, int op) {
	if (txnState == null) 
	    return true; // all operations are legal on a non-transacted entry

	return txnState.canPerform(mgr, op);
    }

    /**
     * Return <code>true</code> if the given transaction is already
     * known to the entry this handle represents.  The 
     * thread calling this method should own this object's lock.
     */
    boolean knownMgr(TransactableMgr mgr) {
	if (txnState == null) 
	    return (mgr == null); // The only mgr we know about is the null mgr

	return txnState.knownMgr(mgr);
    }

    /**
     * Return <code>true</code> if we are being managed the given
     * manager is the only one we know about.  The thread calling this
     * method should own this object's lock.
     */
    boolean onlyMgr(TransactableMgr mgr) {
	if (txnState == null) 
	    return false;

	return txnState.onlyMgr(mgr);
    }

    /**
     * Return <code>true</code> if the entry this handle represents is
     * being managed within any transaction.  The thread calling this
     * method should own this object's lock.
     */
    boolean managed() {
	return txnState != null;
    }

    /**
     * Add into the collection any transactions that are known to this
     * handle. The thread calling this method should own this object's
     * lock.
     */
    void addTxns(java.util.Collection collection) {
	if (txnState == null) 
	    return; // nothing to add

	txnState.addTxns(collection);
    }

    /**
     * Add <code>mgr</code> to the list of known managers, setting the
     * the type of lock on this entry to <code>op</code>. The thread
     * calling this method should own this object's lock.  Assumes
     * that <code>op</code> is compatible with any lock currently
     * associated with this entry.  <code>holder</code> is the the
     * <code>EntryHolder</code> holding this handle.
     */
    void add(TransactableMgr mgr, int op, EntryHolder holder) {
	if (txnState == null) {
	    txnState = new TxnState(mgr, op, holder);
	} else {
	    txnState.add(mgr, op);
	}
    }

    /**
     * It this entry is read locked promote to take locked and return
     * true, otherwise return false.  Assumes that the object is
     * locked and the take is being performed under the one
     * transaction that owns a lock on the entry.
     */
    boolean promoteToTakeIfNeeded() {
	return txnState.promoteToTakeIfNeeded();
    }

    /**************************************************
     * Methods required by the Transactable interface 
     **************************************************/
    public synchronized int prepare(TransactableMgr mgr, 
				    OutriggerServerImpl space) 
    {
	if (txnState == null)
	    throw new IllegalStateException("Can't prepare an entry not " +
					    "involved in a transaction");
	final int rslt = txnState.prepare(mgr, space, this);
	if (txnState.empty())
	    txnState = null;

	return rslt;
    }

    public synchronized void abort(TransactableMgr mgr, 
				   OutriggerServerImpl space) 
    {
	if (txnState == null)
	    throw new IllegalStateException("Can't abort an entry not " +
					    "involved in a transaction");
	final boolean last = txnState.abort(mgr, space, this);
	if (last)
	    txnState = null;
    }

    public synchronized void commit(TransactableMgr mgr, 
				    OutriggerServerImpl space) 
    {
	if (txnState == null)
	    throw new IllegalStateException("Can't commit an entry not " +
					    "involved in a transaction");

	final boolean last = txnState.commit(mgr, space, this);
	if (last)
	    txnState = null;
    }
}
