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

/** 
 * Subclass of <code>TransitionWatcher</code> for blocking queries.
 * <code>QueryWatcher</code>s are waiting for a set of conditions (time,
 * transitions, etc.) that will <em>resolve</em> the query.  Threads can
 * block on resolution by calling the <code>waitOnResolution</code> method
 * and can then discovery how the query was resolved by calling the
 * <code>resolvedWithEntry</code> and <code>resolvedWithThrowable</code>
 * (and in the case of <code>IfExistsWatcher</code>s
 * <code>isConflictSetEmpty</code>).
 * <p>
 * Resolution may involve <em>capturing</em> an entry (that is locking or
 * removing an entry from the space). It is important that a
 * <code>QueryWatcher</code> never capture an entry after the watcher
 * enters the resolved state, since that could allow a caller to believe
 * that the call failed, when it fact it locked or removed an entry. Put
 * another way, the decision to capture an entry has to be done atomically
 * with the check that ensures that the related query is
 * still unresolved.
 */
abstract class QueryWatcher extends TransitionWatcher {
    /** When this query ends */
    private final long expiration;

    /** Set to true when this query is resolved */
    private boolean resolved = false;

    /** If resolved and an entry was found the entry to return */
    private EntryRep rep;

    /** 
     * If resolved and an exception needs to be thrown the exception
     * to throw
     */
    private Throwable toThrow;

    /**
     * Create a new <code>QueryWatcher</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.
     */
    QueryWatcher(long expiration, long timestamp, long startOrdinal) 
    {
	super(timestamp, startOrdinal);
	this.expiration = expiration;
    }

    public long getExpiration() {
	return expiration;
    }

    /**
     * Process a transition which was posted before the watcher was
     * placed in <code>TransitionWatchers</code> object. Assumes that
     * the entry in the transition matches matches the template in the
     * <code>TemplateHandle</code> associated with this watcher. Does
     * not assume <code>isInterested</code> has been called.
     * @param transition A <code>EntryTransition</code> that
     *              describes the transition and what
     *              entry is transitioning. This method
     *              will assume that <code>transition.getHandle</code>
     *              returns a non-null value.
     * @param now   An estimate of the current time (not the time
     *              when the event occured).
     * @return <code>true</code> if the query has been resolved, and
     *         <code>false</code> otherwise. Note, even if this
     *         call returns <code>true</code>, <code>waitOnResolution</code>
     *         must still be called.
     *        
     * @throws NullPointerException if <code>transition</code> is 
     *         <code>null</code>.  
     */
    abstract boolean catchUp(EntryTransition transition, long now);
    
    /**
     * This method does nothing. Since each <code>QueryWatcher</code>
     * has a thread that blocks until the expiration time is reached
     * it simpler to do the removal there instead of in the
     * reaping thread.
     * @param now An estimate of the current time that must be
     *            less than or equal to the current time.
     */
    void removeIfExpired(long now) { }
        
    /**
     * Block until the query this object represents is resolved.  If the
     * query is already resolved, return immediately. This method must be
     * called even if it is know that the query has been resolved.
     * This method should be called exactly once.     
     */
    void waitOnResolution() throws InterruptedException {
	final TemplateHandle owner;
	synchronized (this) {
	    while (!resolved) {
		final long sleepTime =
		    expiration - System.currentTimeMillis();
		if (sleepTime <= 0) {
		    // All done
		    resolved = true;
		} else {
		    wait(sleepTime);
		}
	    }

	    owner = this.owner;
	    // Signal that we (well will be soon) removed from owner
	    this.owner = null;
	}

	// If we are called exactly once, owner should never be null
	owner.removeTransitionWatcher(this);	
    }

    /** 
     * If the query has been resolved by finding an matching entry,
     * returns the <code>EntryRep</code> for that entry. If the query has
     * been resolved but no entry is available (e.g. the expiration time has
     * been reached or an exception needs to be thrown) returns
     * <code>null</code>. Note, once resolution has been reached this
     * method can only return non-null if <code>resolvedWithThrowable</code>
     * returns <code>null</code>.
     *
     * @return The entry to be returned, or <code>null</code> if
     * no entry is available.
     * @throws IllegalStateException if the query has not
     * yet been resolved.
     */
    synchronized EntryRep resolvedWithEntry() {
	if (!resolved)
	    throw new IllegalStateException("Query not yet resolved");

	return rep;
    }

    /**
     * If the query has been resolved with an exceptional condition,
     * the exception that should be thrown to the client. Returns
     * <code>null</code> otherwise.  Note, once resolution has been
     * reached this method can only return non-null if
     * <code>resolvedWithEntry</code> returns <code>null</code>.
     * @return the exception (if any) that should
     * be thrown to the client.
     * @throws IllegalStateException if the query has not 
     * yet been resolved.  
     */
    synchronized Throwable resolvedWithThrowable() {
	if (!resolved)
	    throw new IllegalStateException("Query not yet resolved");

	return toThrow;
    }

    /** 
     * Returns <code>true</code> if this query has been resolved.  If the
     * calling thread is owns the lock on this object the answer is
     * definitive. If the lock is not held only a <code>true</code> answer
     * can be considered definitive.
     * @return <code>true</code> if the query has been
     * resolved, <code>false</code> otherwise.
     */
    boolean isResolved() {
	return resolved;
    }

    /**
     * Mark this query as resolved. This method assumes
     * the calling thread own the lock on this object.
     * This method is intended only for use by subclasses.
     * @param rep   If being resolved by finding an entry
     *              the entry which was found and that should be returned
     *              by <code>resolvedWithEntry</code>. Otherwise should be
     *              <code>null</code>.  May only be non-null if throwable
     *              is <code>null</code>.
     * @param throwable If being resolved by an exception
     *              the throwable to be thrown and that should be returned
     *              by <code>resolvedWithThrowable</code> otherwise should
     *              be <code>null</code>.  May only be non-null if entry is
     *              <code>null</code>.
     * @throws IllegalArgumentException if both 
     *         <code>entry</code> and <code>throwable</code>
     *         are non-null.
     * @throws IllegalStateException if the query has already
     *         been resolved.
     */
    void resolve(EntryRep rep, Throwable throwable) {
	assert Thread.holdsLock(this) : "Caller of resolve() must hold lock";

	if (resolved)
	    throw new IllegalStateException(
		"Can't call resolve on a resolved query ");

	if ((this.rep != null) || (toThrow != null))
	    throw new IllegalStateException(
		"At lease one argument must be null");

	resolved = true;
	this.rep = rep;
	toThrow = throwable;
	notifyAll();
    }    
}
