/*
 * 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.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>
 *
 * There is a particular risk with the removal of entries outside of
 * transactions. Ideally marking an entry as removed and making the
 * removal durable would be atomic with respect to other
 * operations. But this would require holding a lock across disk I/O
 * which we try to avoid. In particular it would hold up the progress
 * of searches that match the entry in question, even though the very
 * next entry might be a suitable match. One alternative would be to
 * make the removal durable, then while holding the entry's lock mark
 * the entry as removed, but this would allow competing takes to both
 * get the same entry (this could be corrected by making the 2nd take
 * lose when it goes back to try and complete the removal, and then
 * continues its query, but since logging happens up in
 * OutriggerServerImpl restarting the query would be inconvenient, it
 * would probably also result in a number of unnecessary log
 * records). We could mark the entry as removed, release the entry's
 * lock, and then make the removal durable. However, this allows for
 * the possibility of a 2nd query that matches the entry coming in
 * after the entry has been removed, but before the removal has been
 * made durable, finding no matches and returning null, and then the
 * server crashing before the removal is made durable. When the server
 * came back up the entry would be available again, and if the 2nd
 * query was repeated it could then return the entry that had been
 * marked as removed. Effectively an entry would have disappeared and
 * then reappeared. <p>
 * 
 * Our solution is to introduce the <i>removePending</i> flag. When an
 * entry is to be removed outside of a transaction the removePending
 * flag is set by calling <code>provisionallyRemove</code>, the
 * removal is made durable, the entry is removed internally and the
 * removePending flag cleared (generally by calling
 * <code>remove</code> on the appropriate <code>EntryHolder</code> or
 * on the <code>EntryHolderSet</code> - either will remove the entry
 * from all the internal tables and clear the removePending flag). <p>
 *
 * Any operation that will definitively indicate that a given entry
 * has been removed must not only check to see if the entry has been
 * removed but also that removePending is not set (the
 * <code>isProvisionallyRemoved</code> method returns the state of the
 * removePending flag). If removePending is set the operation must
 * either block until removePending is cleared (this can be
 * accomplished using the <code>waitOnCompleteRemoval</code> method),
 * indicating that the removal has been made durable, or return in
 * such a way that the entry's state is left ambiguous. Note, because
 * any I/O failure while logging will result in the space crashing a
 * set removePending flag will only transition to cleared after a
 * removal has been made durable, thus an operation blocked on the
 * removePending flag should never need to go back and see if the
 * entry has become available. <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.
 *
 * 
 */
// We do not store this data on the EntryRep object itself because it
// is not really part of the client<->JavaSpaces 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 TxnState txnState;

    /**
     * <code>true</code> if this entry has to been seen as removed,
     * but the removal has not yet been committed to disk
     */
    private boolean removePending = false;

    /**
     * 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();
    }

    /**
     * Returns <code>true</code> it this entry has been removed
     * outside of a transaction, but that removal has not yet been
     * committed to disk.The thread calling this method should own this
     * object's lock.
     */
    boolean isProvisionallyRemoved() {
	assert Thread.holdsLock(this);
	return removePending;
    }

    /**
     * Marks this entry as being removed outside of a transaction but
     * not yet committed to disk. The thread calling this method should
     * own this object's lock.
     */
    void provisionallyRemove() {
	assert Thread.holdsLock(this);
	assert !removePending;
	removePending = true;
    }

    /**
     * Called after the removal of a provisionally removed entry has
     * been committed to disk and the handle has been removed from its
     * holder. The thread calling this method should own this object's
     * lock.
     */
    void removalComplete() {
	assert Thread.holdsLock(this);

	if (removePending) {
	    removePending = false;
	    notifyAll();
	}
    }

    /**
     * If this entry has been marked for removal by a
     * non-transactional operation, but that operation has not be
     * yet been committed to disk, block until the operation has been
     * committed to disk, otherwise return immediately. The
     * thread calling this method should own this object's lock.
     */
    void waitOnCompleteRemoval() throws InterruptedException {
	assert Thread.holdsLock(this);
	while (removePending) {
	    wait();
	}
    }

    /**************************************************
     * 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;
    }
}
