/*
 * 
 * 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.core.transaction.TransactionException;
import net.jini.space.InternalSpaceException;

/**
 * Subclass of QueryWatcher for takes and transactional reads.
 * Resolves with the first matching transition where the entry is
 * visible to the associated transaction and the entry is still
 * available.
 */
class ConsumingWatcher extends  QueryWatcher implements Transactable {
    /**
     * If non-null the transaction this query is
     * being performed under. If <code>null</code> 
     * this query is not associated with a transaction.
     */
    private final Txn txn;

    /**
     * <code>true</code> if this query is a take and
     * <code>false</code> otherwise.
     */
    private final boolean takeIt;

    /**
     * Create a new <code>ConsumingWatcher</code>.
     * @param expiration the initial expiration time
     *        for this <code>TransitionWatcher</code> in 
     *        milliseconds since the beginning of the epoch.
     * @param timestamp the value that is used
     *        to sort <code>TransitionWatcher</code>s.
     * @param startOrdinal the highest ordinal associated
     *        with operations that are considered to have occurred 
     *        before the operation associated with this watcher.
     * @param txn If the query is being performed under
     *        a transaction the <code>Txn</code> object
     *        associated with that transaction.
     * @param takeIt <code>true</code> if this query is a take and
     *        <code>false</code> otherwise.
     */
    ConsumingWatcher(long expiration, long timestamp, long startOrdinal, 
		     Txn txn, boolean takeIt)
    {
	super(expiration, timestamp, startOrdinal);
	this.txn = txn;
	this.takeIt = takeIt;
    }

    boolean isInterested(EntryTransition transition, long ordinal) {
	/* Note, !isResolved() without the lock will result only in
	 * false positives, not false negatives - it will only
	 * cause isInterested() to return false if we are resolved,
	 * we may still return true if we are resolved though.
	 */
	final Txn transitionTxn = transition.getTxn();
	return (ordinal>startOrdinal) && !isResolved() &&
	    transition.isAvailable() &&
	    ((null == transitionTxn) || (txn == transitionTxn));
    }

    synchronized void process(EntryTransition transition, long now) {
	// If isInterested was true then we can just resolve
	if (isResolved())
	    return; // Already done.

	final EntryHandle handle = transition.getHandle();

	// Is it still available?
	if (owner.getServer().attemptCapture(
	    handle, txn, takeIt, null, now, this)) 
	{
	    // Got it
	    resolve(handle.rep(), null);
	} 
    }

    synchronized boolean catchUp(EntryTransition transition, long now) {
	if (isResolved())
	    return true;

	final Txn transitionTxn = transition.getTxn();
	final EntryHandle handle = transition.getHandle();
	if (transition.isAvailable() &&
	    ((null == transitionTxn) || (txn == transitionTxn)) &&
	    (owner.getServer().attemptCapture(handle, txn, takeIt, null, 
					      now, this)))
	{
	    // Got it
	    resolve(handle.rep(), null);
	    return true;
	} 

	// Not interesting, or could not get it, either way not resolved
	return false;
    }


    /**
     * If a transaction ends in the middle of a query we want
     * to throw an exception to the client making the query
     * not the <code>Txn</code> calling us here.)
     */
    public synchronized int prepare(TransactableMgr mgr,
				    OutriggerServerImpl space) 
    {
	assert txn != null:"Transactable method called on a " +
	    "non-transactional ConsumingWatcher";

	// only throw an exception if we are not resolved.
	if (!isResolved()) {
	    // Query still in progress, kill it
	    resolve(null, new TransactionException("completed while " +
						   "operation in progress"));
	}

	// If this object has made changes they have been recorded elsewhere
	return NOTCHANGED;
    }

    /**
     * This should never happen since we always return
     * <code>NOTCHANGED</code> from <code>prepare</code>.
     */
    public void commit(TransactableMgr mgr, OutriggerServerImpl space) {
	throw new InternalSpaceException("committing a blocking query");
			   
    }

    /**
     * If a transaction ends in the middle of a query we want
     * to throw an exception to the client making the query 
     * (not the <code>Txn</code> calling us here.)
     */
    public void abort(TransactableMgr mgr, OutriggerServerImpl space) {
	// prepare does the right thing, and should forever
	prepare(mgr, space);
    }
}

