/*
 * 
 * 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 java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.rmi.MarshalledObject;
import java.rmi.RemoteException;
import net.jini.security.ProxyPreparer;

/**
 * This class holds a proxy for some remote resource. When
 * persisted the proxy is marshalled in its own 
 * {@link MarshalledObject} so this object can be unmarshalled
 * even if the proxy can't be (say because it codebase is 
 * unavailable). The {@link #get} method can
 * be used to retrieve the proxy on demand.
 *
 * @author Sun Microsystems, Inc.
 *
 */
class StorableReference implements Externalizable {
    /** The proxy in marshalled form */
    private MarshalledObject bytes;

    /** A cached copy of the unmarshalled proxy */
    private transient Object obj;

    /** True if the <code>obj</code> has been prepared */
    private transient boolean prepared;

    private static final boolean DEBUG = false;
    private static final long serialVersionUID = -3793675220968988873L;

    /**
     * Create a <code>StorableReference</code> that will hold
     * <code>obj</code>.
     */
    public StorableReference(Object obj) {
	if (obj == null)
	    throw new NullPointerException("obj cannot be null");
	this.obj = obj;
	prepared = true;
    }

    /** Used by the object output stream. */
    public StorableReference() { }

    /**
     * Return the proxy.  If necessary deserialize the 
     * proxy and optionally prepare it. Will only deserialize the
     * reference if it has not already been deserialized. Will
     * only prepare the object if <code>preparer</code> is non-null
     * and no previous call to get has succeeded. If this method
     * throws an exception, preparation has not succeeded. If a
     * previous call to this method has succeed, all future
     * calls will succeed and return the same object as the 
     * first successful call.
     *
     * @param preparer the <code>ProxyPreparer</code> to
     *                 be used to prepare the reference. May
     *                 be <code>null</code>.
     * @return the remote reference contained in this object
     * @throws IOException if the unmarshalling fails. Will
     *                 also throw {@link RemoteException}
     *                 if <code>preparer.prepareProxy</code>
     *                 does.
     * @throws ClassNotFoundException if unmarshalling fails
     *                 with one.
     * @throws SecurityException if <code>preparer</code> does.
     */
    public synchronized Object get(ProxyPreparer preparer)
	throws IOException, ClassNotFoundException 
    {
	/* Even though we may be doing remote ops, it is
	 * ok that we are synchronized.  It does not
	 * matter if a competing thread waits on this
	 * lock, or waits doing the same remote operation
	 * the owner of the lock is doing (in fact it
	 * is better for the second thread to wait instead
	 * of duplicating the work of the first).
	 */

	if (obj == null)
	    obj = bytes.get();

	if (!prepared) {
	    if (preparer != null)
		obj = preparer.prepareProxy(obj);
	    prepared = true;
	}
       
	return obj;
    }

    /**
     * @serialData
     */  
    // inherit doc comment
    public void writeExternal(ObjectOutput out) throws IOException {
	synchronized (this) {
	    if (bytes == null)
		bytes = new MarshalledObject(obj);
	}

	out.writeObject(bytes);
    }

    // inherit doc comment
    public void readExternal(ObjectInput in)
	throws IOException, ClassNotFoundException 
    {
	bytes = (MarshalledObject)in.readObject();
    }
}
