All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.refcodes.remoting.RemoteClient Maven / Gradle / Ivy

Go to download

Artifact with proxy functionality for plain remote procedure calls (RPC), a POJO alternative to RMI.

The newest version!
// /////////////////////////////////////////////////////////////////////////////
// REFCODES.ORG
// =============================================================================
// This code is copyright (c) by Siegfried Steiner, Munich, Germany, distributed
// on an "AS IS" BASIS WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, and licen-
// sed under the following (see "http://en.wikipedia.org/wiki/Multi-licensing")
// licenses:
// =============================================================================
// GNU General Public License, v3.0 ("http://www.gnu.org/licenses/gpl-3.0.html")
// together with the GPL linking exception applied; as being applied by the GNU
// Classpath ("http://www.gnu.org/software/classpath/license.html")
// =============================================================================
// Apache License, v2.0 ("http://www.apache.org/licenses/TEXT-2.0")
// =============================================================================
// Please contact the copyright holding author(s) of the software artifacts in
// question for licensing issues not being covered by the above listed licenses,
// also regarding commercial licensing models or regarding the compatibility
// with other open source licenses.
// /////////////////////////////////////////////////////////////////////////////
package org.refcodes.remoting;

import java.io.IOException;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ExecutorService;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.refcodes.component.DigestException;
import org.refcodes.controlflow.ControlFlowUtility;
import org.refcodes.controlflow.RetryTimeout;
import org.refcodes.data.IoTimeout;
import org.refcodes.data.RetryLoopCount;
import org.refcodes.data.SleepLoopTime;
import org.refcodes.exception.TimeoutIOException;
import org.refcodes.exception.VetoException;
import org.refcodes.generator.Generator;
import org.refcodes.generator.UniqueIdGenerator;
import org.refcodes.mixin.BusyAccessor;
import org.refcodes.mixin.Disposable;
import org.refcodes.mixin.Lockable;

/**
 * A {@link RemoteClient} promotes subjects to be operated on by a
 * {@link RemoteServer}.
 */
public class RemoteClient extends AbstractRemote {

	// /////////////////////////////////////////////////////////////////////////
	// STATICS:
	// /////////////////////////////////////////////////////////////////////////

	private static final Logger LOGGER = Logger.getLogger( RemoteClient.class.getName() );

	// /////////////////////////////////////////////////////////////////////////
	// VARIABLES:
	// /////////////////////////////////////////////////////////////////////////

	private InstanceHandler _instanceHandler = new InstanceHandler();

	// /////////////////////////////////////////////////////////////////////////
	// CONSTRUCTORS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Default constructor of the {@link RemoteClient} will use a default
	 * {@link ExecutorService}.
	 */
	public RemoteClient() {}

	/**
	 * Instantiates a new {@link RemoteClient} instance with the given
	 * {@link ExecutorService} to be used.
	 *
	 * @param aExecutorService The {@link ExecutorService} to be used.
	 */
	public RemoteClient( ExecutorService aExecutorService ) {
		super( aExecutorService );
	}

	// /////////////////////////////////////////////////////////////////////////
	// METHODS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void clear() {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() );
		signOffAllProxies();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public synchronized void close() throws IOException {
		if ( IS_LOG_DEBUG_ENABLED ) {
			LOGGER.info( "CLOSE called on <" + getClass().getName() + ">." );
		}
		close( null );
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isBusy() {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() );
		return _instanceHandler.isBusy();
	}

	/**
	 * Returns true if the provided proxy is contained inside the
	 * {@link RemoteClient}.
	 * 
	 * @param aProxy The proxy to be tested if it is contained inside the
	 *        {@link RemoteClient}.
	 * 
	 * @return True if the given proxy is contained inside the
	 *         {@link RemoteClient}.
	 */
	public boolean hasProxy( Object aProxy ) {
		return getProxyDescriptor( aProxy ) != null;
	}

	/**
	 * Returns the proxy which can be cast to the given type. Note that this
	 * method may throw an {@link AmbiguousProxyException} even if
	 * {@link #hasProxy(Class)} returns true! The method
	 * {@link #hasProxy(Class)} returns true in case at least one proxy of the
	 * given type was found whereas this method only returns a proxy in case
	 * there is exactly one proxy of the required type is present.
	 *
	 * @param  the generic type
	 * @param aType the type
	 * 
	 * @return The proxy for an instance of the given type.
	 * 
	 * @throws AmbiguousProxyException Thrown in case a proxy for a given type
	 *         was requested but more than one proxies matched the requested
	 *         type.
	 * @throws NoSuchProxyException Thrown in case a proxy for a given type was
	 *         requested but not any proxy matched the requested type.
	 */
	@SuppressWarnings("unchecked")
	public  T getProxy( Class aType ) throws AmbiguousProxyException, NoSuchProxyException {
		final Iterator e = proxies();
		Object eProxy;
		Object theProxy = null;
		while ( e.hasNext() ) {
			eProxy = e.next();
			if ( aType.isAssignableFrom( eProxy.getClass() ) ) {
				if ( theProxy != null ) {
					throw new AmbiguousProxyException( "More than one proxy matching the given class \"" + aType.getName() + "\" found; an ambigous state detected; only one proxy must match your type!" );
				}
				theProxy = eProxy;
			}

		}
		if ( theProxy == null ) {
			throw new NoSuchProxyException( "Not one proxy matching the given class \"" + aType.getName() + "\" found; exactly one proxy must match your type!" );
		}
		return (T) theProxy;
	}

	/**
	 * Returns true if there is at least one proxy of the given type.
	 *
	 * @param aType the type
	 * 
	 * @return True in case there is at least one proxy of the given type.
	 */
	public boolean hasProxy( Class aType ) {
		final Iterator e = proxies();
		Object eProxy;
		while ( e.hasNext() ) {
			eProxy = e.next();
			if ( aType.isAssignableFrom( eProxy.getClass() ) ) {
				return true;
			}

		}
		return false;
	}

	/**
	 * Returns a read-only iterator containing all the proxy objects previously
	 * being published. Use the sign-off methods in order to remove a published
	 * proxy object.
	 * 
	 * @return An iterator containing the published proxy objects.
	 */
	public Iterator proxies() {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() && !isOpened() );
		final ArrayList theProxyList = new ArrayList<>( _instanceHandler.size() );
		synchronized ( _instanceHandler ) {
			final Iterator e = _instanceHandler.proxyDescriptors();
			while ( e.hasNext() ) {
				theProxyList.add( e.next().getProxy() );
			}
		}
		return theProxyList.iterator();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean isEmpty() {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() );
		return _instanceHandler.isEmpty();
	}

	/**
	 * Signs off an instance previously published using the
	 * publishClassDescriptor() method.
	 * 
	 * @param aProxy An object of type GenericInstanceDescriptor containing the
	 *        information needed to sign-off an instance.
	 * 
	 * @return True is returned if the instance described by the proxy object
	 *         could be signed off, else false is returned
	 * 
	 * @throws IOException Thrown in case opening or accessing an open line
	 *         (connection, junction, link) caused problems.
	 */
	public boolean signOffProxy( Object aProxy ) throws IOException {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() );
		if ( aProxy == null ) {
			return false;
		}
		final ProxyDescriptor theProxyDescriptor = getProxyDescriptor( aProxy );
		if ( theProxyDescriptor == null ) {
			return false;
		}
		final String theInstanceId = theProxyDescriptor.getInstanceId();
		if ( !isClosed() ) {
			if ( theProxyDescriptor.getInstanceId() == null ) {
				return false;
			}
			if ( _instanceHandler.hasSignedOffInstanceId( theInstanceId ) ) {
				return false;
			}
			if ( !_instanceHandler.hasProxyControl( theInstanceId ) ) {
				return false;
			}
		}
		ProxyControl theProxyControl = _instanceHandler.getProxyControl( theInstanceId );

		// ---------------------------------------------------------------------
		// Signal hook:
		// ---------------------------------------------------------------------
		onProxySignedOff( theProxyDescriptor.getProxy() );
		// ---------------------------------------------------------------------

		theProxyControl.lock();

		waitForActiveSessions( theProxyControl );

		synchronized ( _instanceHandler ) {
			if ( _instanceHandler.hasSignedOffInstanceId( theInstanceId ) ) {
				return false;
			}
			theProxyControl = _instanceHandler.getProxyControl( theInstanceId );
			_instanceHandler.removeProxyDescriptor( theInstanceId );
			// <--|
			if ( _instanceHandler.hasMethodReplyDescriptor( theInstanceId ) ) {
				theProxyControl.dispose();
				throw new DuplicateInstanceIdRuntimeException( "The instance of the provided  object in arguemnt  is already in use and being calluted. Sorry - aborting operation !!!" );
			}
		}
		final SignOffProxyMessage theSignOffProxyJob = new SignOffProxyMessage();
		theSignOffProxyJob.setInstanceId( theProxyDescriptor.getInstanceId() );
		final CancelMethodReplyMessage theCancelMethodReplyJobImpl = new CancelMethodReplyMessage();
		theCancelMethodReplyJobImpl.setInstanceId( theInstanceId );
		theCancelMethodReplyJobImpl.setHasReply( false );
		_instanceHandler.addReplyDescriptor( theCancelMethodReplyJobImpl, theInstanceId );

		try {
			toReceiver( theSignOffProxyJob );
		}
		catch ( IOException aException ) {
			theProxyControl.dispose();
			theProxyControl.unlock();
			_instanceHandler.removeMethodReplyDescriptor( theInstanceId );

			if ( aException.getCause() instanceof IOException ) {
				closeOnException();
			}
			throw aException;
		}

		final RetryTimeout theRetryTimeout = new RetryTimeout( WAIT_FOR_REPLY_TIMEOUT, WAIT_FOR_REPLY_LOOPS );
		while ( !theCancelMethodReplyJobImpl.hasReply() && theRetryTimeout.hasNextRetry() && isOpened() && !theProxyControl.isUnusable() ) {
			// @formatter:off
			if ( IS_LOG_DEBUG_ENABLED ) {
				LOGGER.info( "Wait loop <" + theRetryTimeout.getRetryCount() + "> while waiting for method reply for <" + WAIT_FOR_REPLY_LOOPS + "> ms; connection status is " + getConnectionStatus() + "." );
			}
			// @formatter:on
			theRetryTimeout.nextRetry( theCancelMethodReplyJobImpl );
		}
		theProxyControl.dispose();
		_instanceHandler.removeMethodReplyDescriptor( theInstanceId );

		if ( !theCancelMethodReplyJobImpl.hasReply() ) {
			throw new TimeoutIOException( WAIT_FOR_REPLY_TIMEOUT, "While processing the request a timeout of " + WAIT_FOR_REPLY_TIMEOUT + " ms has been overshot! Propably lost the connection (you propably should close the connection). Sorry - request aborted!" );
		}

		// evaluate the return value:
		if ( theCancelMethodReplyJobImpl.isReturnValue() ) {
			if ( theCancelMethodReplyJobImpl.getReturnValue() instanceof Boolean ) {
				final boolean returnValue = ( (Boolean) theCancelMethodReplyJobImpl.getReturnValue() ).booleanValue();
				if ( returnValue ) {
					theProxyControl.dispose();
				}
				return returnValue;
			}
		}
		throw new InvalidMethodReplyRuntimeException( "Unexpected reply when publishing a class descripter." );
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public int size() {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() );
		return _instanceHandler.size();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public synchronized void destroy() {
		if ( !isDestroyed() ) {
			super.destroy();
			closeQuietly();
			_instanceHandler.clear();
			_instanceHandler = null;
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	// HELPER:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * A reply from a {@link RemoteServer} is pushed to the {@link RemoteClient}
	 * using this method.
	 * 
	 * @param aMethodReply An object of type BlueprintMethodReply from which the
	 *        result of a request can be retrieved.
	 */
	private void pushMethodReply( Reply aMethodReply ) {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() );
		final ProxyControl theProxyControl = _instanceHandler.getProxyControl( aMethodReply.getInstanceId() );
		if ( theProxyControl == null ) {
			throw new IllegalArgumentException( "Unable to retrieve the the proxy control assosiated the instance ID <" + aMethodReply.getInstanceId() + "> of the given  method reply." );
		}
		theProxyControl.pushMethodReply( aMethodReply );
	}

	// /////////////////////////////////////////////////////////////////////////
	// DEBUG:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * For testing purposes, any job can be manually pushed into the job process
	 * to the receiver.
	 * 
	 * @param aJob The job to be pushed to the receiver.
	 * 
	 * @throws IOException Thrown in case the operation failed due to an I/O
	 *         malfunction such as the input- or output-connection (of an input-
	 *         and output-connection pair) is not available or being
	 *         disconnected.
	 */
	protected void doSendJob( Message aJob ) throws IOException {
		toReceiver( aJob );
	}

	// /////////////////////////////////////////////////////////////////////////
	// HOOKS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected void digest( Message aJob ) throws DigestException {
		try {
			if ( aJob == null ) {
				return;
			}
			// -----------------------------------------------------------------
			// CLOSE CONNECTION JOB:
			// -----------------------------------------------------------------
			if ( aJob instanceof CloseConnectionMessage ) {
				if ( IS_LOG_DEBUG_ENABLED ) {
					LOGGER.info( "Received a close connection job to <" + getClass().getName() + "> and closing connection." );
				}
				close( (CloseConnectionMessage) aJob );
			}
			// -----------------------------------------------------------------
			// CANCEL METHOD REPLY JOB:
			// -----------------------------------------------------------------
			else if ( aJob instanceof CancelMethodReplyMessage theReplyJob ) {
				final String aInstanceId = theReplyJob.getInstanceId();
				if ( aInstanceId != null ) {
					final Object tmpReply = _instanceHandler.getMethodReplyDescriptor( aInstanceId );
					if ( tmpReply instanceof CancelMethodReplyMessage cancelMethodReplyMessage ) {
						cancelMethodReplyMessage.setReply( theReplyJob );
						synchronized ( cancelMethodReplyMessage ) {
							cancelMethodReplyMessage.notifyAll();
						}
					}
					else {
						if ( isOpened() && tmpReply != null ) {
							throw new InvalidMethodReplyRuntimeException( "Expected a <" + CancelMethodReplyMessage.class.getName() + "> reply message for instance id <" + aInstanceId + "> but got  a <" + tmpReply.getClass().getName() + "> message!" );
						}
					}
				}
			}
			// -----------------------------------------------------------------
			// METHOD REPLY JOB:
			// -----------------------------------------------------------------
			else if ( aJob instanceof MethodReplyMessage ) {
				pushMethodReply( (Reply) aJob );
			}
			// -----------------------------------------------------------------
			// PULISH SUBJECT JOB:
			// -----------------------------------------------------------------
			else if ( aJob instanceof PublishSubjectMessage ) {
				try {
					final boolean returnValue = publishClassDescriptor( (ClassDescriptor) aJob );
					final PublishSubjectReplyMessage thePublishSubjectReplyJob = new PublishSubjectReplyMessage();
					thePublishSubjectReplyJob.setInstanceId( aJob.getInstanceId() );
					thePublishSubjectReplyJob.setException( null );
					thePublishSubjectReplyJob.setReturnValue( returnValue );
					thePublishSubjectReplyJob.setHasReply( true );
					toReceiver( thePublishSubjectReplyJob );
				}
				catch ( VetoException aException ) {
					final PublishSubjectReplyMessage theMethodReplyJob = new PublishSubjectReplyMessage();
					theMethodReplyJob.setInstanceId( aJob.getInstanceId() );
					theMethodReplyJob.setException( aException );
					theMethodReplyJob.setReturnValue( null );
					theMethodReplyJob.setHasReply( false );
					toReceiver( theMethodReplyJob );
				}
			}
			// -----------------------------------------------------------------
			// SIGN-OFF SUBJECT JOB:
			// -----------------------------------------------------------------
			else if ( aJob instanceof SignOffSubjectMessage ) {
				final InstanceId theInstanceDescriptor = aJob;
				final boolean isSignOff = signoffInstanceDescriptor( theInstanceDescriptor );
				final PublishSubjectReplyMessage theMethodReplyJob = new PublishSubjectReplyMessage();
				theMethodReplyJob.setInstanceId( aJob.getInstanceId() );
				theMethodReplyJob.setReturnValue( isSignOff );
				theMethodReplyJob.setException( null );
				theMethodReplyJob.setHasReply( true );
				toReceiver( theMethodReplyJob );
			}
		}
		catch ( IOException aException ) {
			closeOnException();
			throw new DigestException( "Digesting the job caused an exception to be thrown.", aException );
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	protected synchronized void close( CloseConnectionMessage aJob ) {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() );
		if ( IS_LOG_DEBUG_ENABLED ) {
			LOGGER.info( "CLOSE called on <" + getClass().getName() + "> with job <" + aJob + ">; connection status is " + getConnectionStatus() + "." );
		}
		if ( !isClosed() ) {
			signOffAllProxies();
			_instanceHandler.lock();
			final RetryTimeout theRetryTimeout = new RetryTimeout( IoTimeout.NORM.getTimeMillis(), RetryLoopCount.NORM_NUM_RETRY_LOOPS.getValue() );
			while ( ( isBusy() ) && theRetryTimeout.hasNextRetry() ) {
				if ( IS_LOG_DEBUG_ENABLED ) {
					LOGGER.info( "Wait loop <" + theRetryTimeout.getRetryCount() + "> while being BUSY for <" + SleepLoopTime.NORM.getTimeMillis() + "> ms." );
				}
				theRetryTimeout.nextRetry();
			}
			super.close( aJob );
			if ( isBusy() ) {
				LOGGER.log( Level.WARNING, "Still being BUSY even after reaching the timeout of <" + IoTimeout.NORM.getTimeMillis() + "> ms, closing connection nonetheless." );
			}
			try {
				super.close();
			}
			catch ( IOException e ) {
				LOGGER.log( Level.WARNING, "Unable to close malfunctioning connection.", e );
			}
			onClosed();
		}
	}

	// /////////////////////////////////////////////////////////////////////////
	// SIGNALS:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * Hook when a proxy is about to be published.
	 * 
	 * @param aType The type to be stored by the property.
	 * 
	 * @throws VetoException Thrown to signal that an operation is being vetoed
	 *         by a third party observing the invocation of the given operation.
	 */
	protected void onPublishProxy( Class aType ) throws VetoException {}

	/**
	 * Hook when a proxy has been published.
	 * 
	 * @param aProxy The proxy to being published.
	 */
	protected void onProxyPublished( Object aProxy ) {}

	/**
	 * Hook when a proxy has been signed-off.
	 * 
	 * @param aProxy The proxy which has been signed-off.
	 */
	protected void onProxySignedOff( Object aProxy ) {}

	// /////////////////////////////////////////////////////////////////////////
	// HELPER:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * A request from a proxy control only is pushed to the remotor using this
	 * method.
	 *
	 * @param aMethodRequestDescriptor An object of type BlueprintMethodRequest
	 *        from which the request can be retrieved.
	 * 
	 * @throws IOException the open exception
	 */
	void pushMethodRequest( MethodRequest aMethodRequestDescriptor ) throws IOException {
		final MethodRequestMessage theMethodRequestJob = new MethodRequestMessage();
		theMethodRequestJob.setMethodRequestDescriptor( aMethodRequestDescriptor );
		toReceiver( theMethodRequestJob );
	}

	/**
	 * Only cleanup, no close!.
	 */
	private void clearOnException() {
		ProxyDescriptor eProxyDescriptor;
		ProxyControl eProxyControl;
		synchronized ( _instanceHandler ) {
			final Iterator e = _instanceHandler.proxyDescriptors();
			while ( e.hasNext() ) {
				eProxyDescriptor = e.next();
				eProxyControl = _instanceHandler.getProxyControl( eProxyDescriptor.getInstanceId() );
				if ( eProxyControl != null ) {
					eProxyControl.dispose();
				}
				onProxySignedOff( eProxyDescriptor.getProxy() );
			}
		}
		_instanceHandler.clear();
	}

	/**
	 * Only cleanup, no close!.
	 */
	private void signOffAllProxies() {
		boolean hasException = false;
		synchronized ( _instanceHandler ) {
			ProxyControl eProxyControl;
			final Iterator e = _instanceHandler.proxyControls();
			while ( e.hasNext() ) {
				eProxyControl = e.next();
				try {
					signOffProxy( eProxyControl.getProxy() );
				}
				catch ( IOException aException ) {
					hasException = true;
				}
			}
			if ( hasException == true ) {
				clearOnException();
			}
			_instanceHandler.clear();
		}
	}

	/**
	 * Closes the connection and disposes all proxy controls. This method is
	 * called whenever an exception indicated some not reparable damage on the
	 * connection so that the system is finished up and events are fired for all
	 * proxies being removed.
	 */
	private void closeOnException() {
		clearOnException();
		try {
			super.close();
		}
		catch ( IOException e ) {
			LOGGER.log( Level.WARNING, "Unable to close malfunctioning connection.", e );
		}
		onClosed();
	}

	/**
	 * This method is used to publish a subject which resides in a
	 * {@link RemoteServer}.
	 *
	 * @param aClassDescriptor The {@link ClassDescriptor} describing the
	 *        subject.
	 * 
	 * @return True in case the according proxy has been published successfully.
	 * 
	 * @throws VetoException the veto exception
	 */
	private boolean publishClassDescriptor( ClassDescriptor aClassDescriptor ) throws VetoException {
		assert ( aClassDescriptor != null );
		assert ( aClassDescriptor.getInstanceId() != null );

		if ( _instanceHandler.hasProxyControl( aClassDescriptor.getInstanceId() ) ) {
			throw new DuplicateInstanceIdRuntimeException( "The instance ID <" + aClassDescriptor.getInstanceId() + "> of the given class descriptor is already in use." );
		}
		final String aInstanceId = aClassDescriptor.getInstanceId();

		onPublishProxy( aClassDescriptor.getType() );

		final ProxyControl theProxyControl = new ProxyControl( aClassDescriptor );
		final ProxyDescriptor proxyDescriptor = new ProxyDescriptor( aClassDescriptor, theProxyControl.getProxy() );
		_instanceHandler.addProxyControl( theProxyControl, aInstanceId );
		_instanceHandler.addProxyDescriptor( proxyDescriptor, aInstanceId );

		onProxyPublished( proxyDescriptor.getProxy() );
		return true;
	}

	/**
	 * Signs off an instance previously published using the
	 * publishClassDescriptor() method. This method is to be used by services
	 * only - the toolkit uses the corresponding signoffInstanceDescriptor().
	 * 
	 * @param aInstanceDescriptor An object of type GenericInstanceDescriptor
	 *        containing the information needed to sign-off an instance.
	 * 
	 * @return True is returned if the instance could be signed-off
	 */
	private boolean signoffInstanceDescriptor( InstanceId aInstanceDescriptor ) {
		if ( aInstanceDescriptor == null ) {
			throw new NullPointerException( "Expected an object of type  instead of a null value in argument ." );
		}
		if ( aInstanceDescriptor.getInstanceId() == null ) {
			throw new NullPointerException( "Expected an object of type  instead of a null value when retrieving the instance ID of argument ." );
		}
		final String aInstanceId = aInstanceDescriptor.getInstanceId();
		if ( _instanceHandler.hasSignedOffInstanceId( aInstanceId ) ) {
			return true;
		}
		if ( !_instanceHandler.hasProxyControl( aInstanceId ) ) {
			throw new UnknownInstanceIdRuntimeException( "Expected a known instance ID in argument ." );
		}
		final ProxyDescriptor theProxyDescriptor = _instanceHandler.getProxyDescriptor( aInstanceId );
		final ProxyControl theProxyControl = _instanceHandler.getProxyControl( aInstanceId );

		onProxySignedOff( theProxyDescriptor.getProxy() );

		theProxyControl.lock();
		waitForActiveSessions( theProxyControl );
		_instanceHandler.removeProxyDescriptor( aInstanceId );
		theProxyControl.dispose();
		return true;
	}

	/**
	 * Wait for active sessions.
	 *
	 * @param aProxyControl the proxy control
	 */
	private void waitForActiveSessions( ProxyControl aProxyControl ) {
		final RetryTimeout theRetryTimeout = new RetryTimeout( WAIT_FOR_ACTIVE_SESSIONS_TIMEOUT_IN_MS, WAIT_FOR_ACTIVE_SESSIONS_LOOPS );
		while ( ( aProxyControl.isBusy() ) && theRetryTimeout.hasNextRetry() && isOpened() ) {
			if ( IS_LOG_DEBUG_ENABLED ) {
				LOGGER.info( "Wait loop <" + theRetryTimeout.getRetryCount() + "> while proxy \"" + aProxyControl.getProxy() + "\" (<" + aProxyControl.getClass() + ">) having ACTIVE SESSIONS for <" + WAIT_FOR_REPLY_LOOPS + "> ms." );
			}
			theRetryTimeout.nextRetry( aProxyControl );
		}
	}

	/**
	 * Retrieves the {@link ProxyDescriptor} describing a proxy.
	 * 
	 * @param aProxy The proxy for which to get the descriptor.
	 * 
	 * @return The according descriptor or null if none such descriptor was
	 *         found.
	 */
	private ProxyDescriptor getProxyDescriptor( Object aProxy ) {
		ControlFlowUtility.throwIllegalStateException( isDestroyed() && !isOpened() );
		ProxyDescriptor eProxyDescriptor;
		synchronized ( _instanceHandler ) {
			final Iterator e = _instanceHandler.proxyDescriptors();
			while ( e.hasNext() ) {
				eProxyDescriptor = e.next();
				if ( eProxyDescriptor.getProxy() == aProxy ) {
					return eProxyDescriptor;
				}
			}
		}
		return null;
	}

	// /////////////////////////////////////////////////////////////////////////
	// PROXY CONTROL:
	// /////////////////////////////////////////////////////////////////////////

	/**
	 * The {@link ProxyControl} is used to manage a proxy being provided from a
	 * subject be the {@link RemoteServer}. This is an inner interface as it is
	 * just required by one implementation internally; in comparison other
	 * interface's implementations are required by at least two different
	 * implementations (e.g. client and server).
	 */
	private class ProxyControl implements InstanceId, InvocationHandler, Lockable, Disposable, ProxyAccessor, BusyAccessor {

		private static final boolean IS_THROW_UNKNOWN_INSTANCE_ID_EXCETIONS_ENABLED = false;
		private final ClassDescriptor _instanceDescriptor;
		private int _hasActiveSessionsCount = 0;
		private boolean _isDisposeable = false;
		private boolean _isLocked = false;
		private boolean _isDisposed = false;
		private final Object _proxy;
		private final Generator _sessionIdGenerator = new UniqueIdGenerator( SESSION_ID_LENGTH );
		private final Map _sessionIds2MethodReply = new HashMap<>();

		/**
		 * Creates a new ProxyControl object.
		 *
		 * @param aInstanceDescriptor Description is currently not available!
		 */
		ProxyControl( ClassDescriptor aInstanceDescriptor ) {
			// |--> put the GenericProxy interface into the proxy's definition:
			final int length = aInstanceDescriptor.getType().getInterfaces().length;
			final Class[] interfaceArray = aInstanceDescriptor.getType().getInterfaces();
			final Class[] proxyInterfaceArray = new Class[length + 1];
			for ( int i = 0; i < length; i++ ) {
				proxyInterfaceArray[i] = interfaceArray[i];
			}
			proxyInterfaceArray[length] = Disposable.class;
			if ( Disposable.class.isAssignableFrom( aInstanceDescriptor.getType() ) ) {
				_isDisposeable = true;
			}
			// <--|
			_proxy = Proxy.newProxyInstance( this.getClass().getClassLoader(), proxyInterfaceArray, this );
			_instanceDescriptor = aInstanceDescriptor;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void dispose() {
			synchronized ( getProxy() ) {
				_isDisposed = true;
			}
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public boolean equals( Object obj ) {
			return super.equals( obj );
		}

		/**
		 * Returns the class descriptor which has been used to create the Proxy
		 * object.
		 * 
		 * @return An object of type BlueprintClassDescriptor which has been
		 *         used to generate the Proxy object.
		 */
		public ClassDescriptor getInstanceDescriptor() {
			return _instanceDescriptor;
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		public String getInstanceId() {
			return _instanceDescriptor.getInstanceId();
		}

		/**
		 * {@inheritDoc}
		 */
		@Override
		@SuppressWarnings("unchecked")
		public 

P getProxy() { return (P) _proxy; } /** * {@inheritDoc} */ @Override public boolean isBusy() { return ( _hasActiveSessionsCount > 0 ); } /** * {@inheritDoc} */ @Override public int hashCode() { return super.hashCode(); } /** * Invoke. * * @param proxy the proxy * @param method the method * @param arguments the arguments * * @return the object * * @throws Throwable the throwable */ @Override public Object invoke( Object proxy, Method method, Object[] arguments ) throws Throwable { _hasActiveSessionsCount++; if ( ( "equals".equals( method.getName() ) ) && ( arguments != null ) && ( arguments.length == 1 ) ) { _hasActiveSessionsCount--; return getProxy() == arguments[0]; } // |--> test the GenericProxy interface method: if ( ( "isProxyDisposed".equals( method.getName() ) ) && ( arguments == null ) ) { _hasActiveSessionsCount--; // return new Boolean(isProxyDisposed()); return isUnusable(); } // |--> test the GenericDisposeablePublic interface method: if ( ( "isDisposed".equals( method.getName() ) ) && ( arguments == null ) ) { if ( _isDisposeable ) { if ( isUnusable() ) { _hasActiveSessionsCount--; return true; } } else { _hasActiveSessionsCount--; return isUnusable(); } } // <--| if ( ( "hashCode".equals( method.getName() ) ) && ( arguments == null ) ) { _hasActiveSessionsCount--; return super.hashCode(); } // if ((method.getName().equals("equals")) && (arguments != null) // && (arguments.length == 1)) // return new Boolean(equals(arguments[0])); if ( isUnusable() ) { // |--> !!! // if ((method.getName().equals("equals")) && (arguments != // null) && (arguments.length == 1)) { // _hasActiveSessionsCount --; // return new Boolean(super.equals(arguments[0])); / ? // return new Boolean(getProxy() == arguments[0]); / ! // } // <--| !!! // |--> !!! if ( ( "toString".equals( method.getName() ) ) && ( arguments == null ) ) { _hasActiveSessionsCount--; return super.toString(); } // <--| !!! // |--> !!! if ( ( "isDisposed".equals( method.getName() ) ) && ( arguments == null ) ) { _hasActiveSessionsCount--; return true; } // <--| !!! throw new ProxyDisposedRuntimeException( "The proxy object <" + getInstanceDescriptor().getType().getName() + "> is disposed!" ); } String theSessionId = null; synchronized ( _sessionIdGenerator ) { if ( _sessionIdGenerator.hasNext() ) { theSessionId = _sessionIdGenerator.next(); } else { _hasActiveSessionsCount--; throw new IllegalStateException( "The ID generator is unable to create more unique IDs" ); } } if ( theSessionId == null ) { _hasActiveSessionsCount--; throw new IllegalStateException( "The ID generator is unable to create more unique IDs" ); } Reply methodReply = null; synchronized ( _sessionIds2MethodReply ) { if ( _sessionIds2MethodReply.containsKey( theSessionId ) ) { _hasActiveSessionsCount--; throw new DuplicateSessionIdRuntimeException( "The session id generator seems to generatoe duplicate id objects. Sorry - aborting operation!" ); } final String instanceId = getInstanceId(); methodReply = new ReplyDescriptor( instanceId, theSessionId ); _sessionIds2MethodReply.put( theSessionId, methodReply ); } final MethodRequest methodRequest = new MethodRequestDescriptor( method, arguments, getInstanceId(), theSessionId ); pushMethodRequest( methodRequest ); // ----------------------------------------------------------------- // Pushed request - waiting for reply: // ----------------------------------------------------------------- long timeout = WAIT_FOR_REPLY_TIMEOUT; while ( !methodReply.hasReply() && ( timeout >= 0 ) && !isUnusable() && isOpened() ) { synchronized ( methodReply ) { try { if ( !methodReply.hasReply() && ( timeout >= 0 ) && ( !isUnusable() ) ) { methodReply.wait( WAIT_FOR_REPLY_LOOPS ); } } catch ( InterruptedException ie ) {} } timeout -= WAIT_FOR_REPLY_LOOPS; } _sessionIds2MethodReply.remove( theSessionId ); _hasActiveSessionsCount--; if ( !methodReply.hasReply() ) { if ( isUnusable() ) { throw new ProxyDisposedRuntimeException( "The proxy object <" + getInstanceDescriptor().getType().getName() + "> is disposed!" ); } else if ( timeout < 0 ) { throw new TimeoutIOException( WAIT_FOR_REPLY_TIMEOUT, "While processing the request a timeout of " + WAIT_FOR_REPLY_TIMEOUT + " ms has been overshot! Propably lost the connection (you propably should close the connection). Sorry - request aborted!" ); } else { throw new IOException( "The proxy object <" + getInstanceDescriptor().getType().getName() + "> is did not recieve the expected remote reply - unkown aCause!" ); } } // ----------------------------------------------------------------- if ( methodReply.isException() ) { throw methodReply.getException(); } else { if ( ( "equals".equals( method.getName() ) ) && ( arguments != null ) && ( arguments.length == 1 ) && ( methodReply.getReturnValue() instanceof Boolean ) ) { final boolean returnValue = ( (Boolean) methodReply.getReturnValue() ).booleanValue(); return super.equals( arguments[0] ) | returnValue; } return methodReply.getReturnValue(); } } /** * Used to push a reply to a request. * * @param aMethodReply An object of type BlueprintMethodReply used to * encapsulate a reply from a method call. */ public void pushMethodReply( Reply aMethodReply ) { if ( aMethodReply == null ) { return; } if ( !getInstanceId().equals( aMethodReply.getInstanceId() ) ) { if ( !isUnusable() ) { throw new UnknownInstanceIdRuntimeException( "The instance id of the argument ( object) is not the same as the expected instance id !!! Sorry - aborting operation!" ); } else { return; } } if ( !_sessionIds2MethodReply.containsKey( aMethodReply.getSessionId() ) ) { if ( !isUnusable() ) { if ( IS_THROW_UNKNOWN_INSTANCE_ID_EXCETIONS_ENABLED ) { throw new UnknownInstanceIdRuntimeException( "The session id of the argument ( object) is not the same as the expected session id !!! Sorry - aborting operation!" ); } else { return; } } else { return; } } final Reply waitingMethodReply = _sessionIds2MethodReply.remove( aMethodReply.getSessionId() ); if ( waitingMethodReply == null ) { if ( !isUnusable() ) { throw new IllegalStateException( "No prepared method reply object found." ); } else { return; } } waitingMethodReply.setReply( aMethodReply ); synchronized ( waitingMethodReply ) { waitingMethodReply.notifyAll(); } } /** * Lock. */ @Override public void lock() { synchronized ( getProxy() ) { _isLocked = true; } } /** * Unlock. */ @Override public void unlock() { synchronized ( getProxy() ) { _isLocked = false; } } /** * Checks if is locked. * * @return true, if is locked */ @Override public boolean isLocked() { return _isLocked; } /** * {@inheritDoc} */ public boolean isUnusable() { return ( ( _isDisposed ) || ( _isLocked ) || ( RemoteClient.this.isClosed() ) || ( RemoteClient.this.isEmpty() ) ); } } // ///////////////////////////////////////////////////////////////////////// // INSTANCE HANDLER: // ///////////////////////////////////////////////////////////////////////// /** * Helper class for providing synchronized access to vital data structures. */ private class InstanceHandler implements Lockable, BusyAccessor { // ///////////////////////////////////////////////////////////////////// // VARIABLES: // ///////////////////////////////////////////////////////////////////// private final HashMap _instanceIdsToProxyControl = new HashMap<>(); private final HashMap _instanceIdsToProxyDescriptor = new HashMap<>(); private final HashMap _instanceIdsToMethodReplyDescriptor = new HashMap<>(); private final Set _signedOffInstanceIds = Collections.newSetFromMap( new WeakHashMap<>() ); // ///////////////////////////////////////////////////////////////////// // METHODS: // ///////////////////////////////////////////////////////////////////// /** * Adds a {@link ProxyControl} to this instance associated to the given * instance ID. * * @param aProxyControl A {@link ProxyControl} to be added. * @param aInstanceId The instance ID to which the {@link ProxyControl} * is to be associated. * * @return True if the operation has been performed successfully. */ boolean addProxyControl( ProxyControl aProxyControl, String aInstanceId ) { synchronized ( this ) { if ( PERFORM_CONSISTENCY_CHECKS ) { if ( hasProxyControl( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is already in use by a proxy control; an unused instance ID must be provided." ); } } else if ( hasProxyControl( aInstanceId ) ) { return false; } _instanceIdsToProxyControl.put( aInstanceId, aProxyControl ); } return true; } /** * Adds a {@link ProxyDescriptor} to this instance associated to the * given instance ID. * * @param aPreoxyDescriptor A {@link ProxyDescriptor} to be added. * @param aInstanceId The instance ID to which the * {@link ProxyDescriptor} is to be associated. * * @return True if the operation has been performed successfully. */ boolean addProxyDescriptor( ProxyDescriptor aPreoxyDescriptor, String aInstanceId ) { synchronized ( this ) { if ( PERFORM_CONSISTENCY_CHECKS ) { if ( hasProxyDescriptor( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is already in use by a proxy descriptor; an unused instance ID must be provided." ); } } else if ( hasProxyDescriptor( aInstanceId ) ) { return false; } _instanceIdsToProxyDescriptor.put( aInstanceId, aPreoxyDescriptor ); } return true; } /** * Adds a reply descriptor to this instance associated to the given * instance ID. * * @param aMethodReplyDescriptor An object of type * BlueprintMethodReplyDescriptor to be added. * @param aInstanceId The aInstanceId to which the reply descriptor is * to be associated. * * @return True if the operation has been performed successfully. */ boolean addReplyDescriptor( Reply aMethodReplyDescriptor, String aInstanceId ) { if ( PERFORM_CONSISTENCY_CHECKS ) { if ( hasMethodReplyDescriptor( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is already in use by a method reply descriptor; an unused instance ID must be provided." ); } } else if ( hasMethodReplyDescriptor( aInstanceId ) ) { return false; } _instanceIdsToMethodReplyDescriptor.put( aInstanceId, aMethodReplyDescriptor ); return true; } /** * Clears the {@link InstanceHandler} instances. */ synchronized void clear() { _instanceIdsToProxyControl.clear(); _instanceIdsToProxyDescriptor.clear(); _instanceIdsToMethodReplyDescriptor.clear(); _signedOffInstanceIds.clear(); } /** * Returns the proxy control associated to the given instance ID. * * @param aInstanceId The instance ID which's proxy control is to be * returned. * * @return An object of type BlueprintMethodProxyControl which is * associated to the given instance ID */ ProxyControl getProxyControl( String aInstanceId ) { synchronized ( this ) { if ( PERFORM_CONSISTENCY_CHECKS ) { if ( !hasProxyControl( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is unknown by any proxy control; a valid instance ID must be provided." ); } } else if ( !hasProxyControl( aInstanceId ) ) { return null; } } return _instanceIdsToProxyControl.get( aInstanceId ); } /** * Returns the proxy descriptor associated to the given instance ID. * * @param aInstanceId The instance ID which's proxy descriptor is to be * returned. * * @return An object of type BlueprintMethodProxyDescriptor which is * associated to the given instance ID */ ProxyDescriptor getProxyDescriptor( String aInstanceId ) { if ( PERFORM_CONSISTENCY_CHECKS ) { if ( !hasProxyDescriptor( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is unknown by any proxy descriptor; a valid instance ID must be provided." ); } } else if ( !hasProxyDescriptor( aInstanceId ) ) { return null; } return _instanceIdsToProxyDescriptor.get( aInstanceId ); } /** * Returns the reply descriptor associated to the given instance ID. * * @param aInstanceId The instance ID which's reply descriptor is to be * returned. * * @return An object of type BlueprintMethodReplyDescriptor which is * associated to the given instance ID */ Reply getMethodReplyDescriptor( String aInstanceId ) { if ( PERFORM_CONSISTENCY_CHECKS ) { if ( !hasMethodReplyDescriptor( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is unknown by any method reply descriptor; a valid instance ID must be provided." ); } } else if ( !hasMethodReplyDescriptor( aInstanceId ) ) { return null; } return _instanceIdsToMethodReplyDescriptor.get( aInstanceId ); } /** * Tests if the given instance ID is in the list of the waiting proxy * controls. * * @param aInstanceId The instance ID for which the existence of a proxy * control is to be tested. * * @return True if there is a proxy control associated to the instance * id. */ boolean hasProxyControl( String aInstanceId ) { return _instanceIdsToProxyControl.containsKey( aInstanceId ); } /** * Tests if the given instance ID is in the list of the waiting proxy * descriptors. * * @param aInstanceId The instance ID for which the existence of a proxy * descriptor is to be tested. * * @return True if there is a proxy descriptor associated to the * instance ID */ boolean hasProxyDescriptor( String aInstanceId ) { return _instanceIdsToProxyDescriptor.containsKey( aInstanceId ); } /** * Tests if the given instance id is in the list of the waiting reply * descriptors. * * @param aInstanceId The instance ID for which the existence of a reply * descriptor is to be tested. * * @return True if there is a reply descriptor associated to the * instance ID */ boolean hasMethodReplyDescriptor( String aInstanceId ) { return _instanceIdsToMethodReplyDescriptor.containsKey( aInstanceId ); } /** * Tests if the provided instance ID has already been used and is not in * use any more. * * @param aInstanceId The ID which is to be tested. * * @return Description is currently not available! */ boolean hasSignedOffInstanceId( String aInstanceId ) { return ( _signedOffInstanceIds.contains( aInstanceId ) ); } /** * Returns an {@link Iterator} containing the currently managed * {@link ProxyControl} instances. * * @return An {@link Iterator} containing the currently managed * {@link ProxyControl} instances. */ synchronized Iterator proxyControls() { return new ArrayList( _instanceIdsToProxyControl.values() ).iterator(); } /** * Returns an {@link Iterator} containing the currently managed * {@link ProxyDescriptor} instances. * * @return An {@link Iterator} containing the currently managed * {@link ProxyDescriptor} instances. */ Iterator proxyDescriptors() { return _instanceIdsToProxyDescriptor.values().iterator(); } /** * Removes the proxy descriptor from this instance which has been * associated to the given instance ID. * * @param aInstanceId The aInstanceId which's proxy descriptor is to be * removed. * * @return The proxy descriptor which has been removed. */ ProxyDescriptor removeProxyDescriptor( String aInstanceId ) { synchronized ( this ) { if ( PERFORM_CONSISTENCY_CHECKS ) { if ( !hasProxyDescriptor( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is unknown by any proy descriptor; a valid instance ID must be provided." ); } if ( !hasProxyControl( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is unknown by any proxy control; a valid instance ID must be provided." ); } if ( hasSignedOffInstanceId( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is already invalidated; an unused instance ID must be provided." ); } } else { if ( !hasProxyDescriptor( aInstanceId ) ) { return null; } if ( !hasProxyControl( aInstanceId ) ) { return null; } if ( hasSignedOffInstanceId( aInstanceId ) ) { return null; } } addSignedOffInstanceId( aInstanceId ); removeProxyControl( aInstanceId ); return _instanceIdsToProxyDescriptor.remove( aInstanceId ); } } /** * Removes the reply descriptor from this instance which has been * associated to the given instance ID. * * @param aInstanceId The aInstanceId which's reply descriptor is to be * removed. * * @return The reply descriptor which has been removed. */ Reply removeMethodReplyDescriptor( String aInstanceId ) { if ( PERFORM_CONSISTENCY_CHECKS ) { if ( !hasMethodReplyDescriptor( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is unknown by any method reply descriptor; a valid instance ID must be provided." ); } } else if ( !hasMethodReplyDescriptor( aInstanceId ) ) { return null; } return _instanceIdsToMethodReplyDescriptor.remove( aInstanceId ); } /** * Returns the number if {@link ProxyDescriptor} instances being * currently managed. * * @return The number if {@link ProxyDescriptor} instances being * currently managed. */ int size() { return _instanceIdsToProxyDescriptor.size(); } /** * Adds an instance ID to the list of signed-off instance IDs. * * @param aInstanceId The instance ID to be added. * * @return True if added, false if already added before. */ private boolean addSignedOffInstanceId( String aInstanceId ) { synchronized ( this ) { return ( _signedOffInstanceIds.add( aInstanceId ) ); } } /** * Removes the {@link ProxyControl} identified by the given instance ID. * * @param aInstanceId The instance ID which's {@link ProxyControl} is to * be removed. * * @return The {@link ProxyControl} which has been removed. */ private ProxyControl removeProxyControl( String aInstanceId ) { synchronized ( this ) { if ( PERFORM_CONSISTENCY_CHECKS ) { if ( !hasProxyControl( aInstanceId ) ) { throw new IllegalArgumentException( "The instance ID <" + aInstanceId + "> is unknown by any proxy control; a valid instance ID must be provided." ); } } else if ( !hasProxyControl( aInstanceId ) ) { return null; } return _instanceIdsToProxyControl.remove( aInstanceId ); } } /** * Determines whether the currently managed number of * {@link ProxyDescriptor} instances is zero. * * @return True in case the currently managed number of * {@link ProxyDescriptor} instances is zero. */ boolean isEmpty() { return _instanceIdsToProxyDescriptor.isEmpty(); } /** * Checks if is busy. * * @return true, if is busy */ @Override public synchronized boolean isBusy() { if ( !_instanceIdsToMethodReplyDescriptor.isEmpty() ) { return false; } final Iterator e = proxyControls(); ProxyControl eProxyControl; while ( e.hasNext() ) { eProxyControl = e.next(); if ( eProxyControl.isBusy() ) { return true; } } return false; } /** * Lock. */ @Override public synchronized void lock() { final Iterator e = proxyControls(); while ( e.hasNext() ) { e.next().lock(); } } /** * Unlock. */ @Override public synchronized void unlock() { final Iterator e = proxyControls(); while ( e.hasNext() ) { e.next().unlock(); } } /** * Checks if is locked. * * @return true, if is locked */ @Override public synchronized boolean isLocked() { final Iterator e = proxyControls(); while ( e.hasNext() ) { if ( !e.next().isLocked() ) { return false; } } return true; } } }