leosutic 2002/10/03 11:10:27
Modified: util/src/java/org/apache/excalibur/util
MultiDelegateProxy.java MultiDelegate.java
Delegate.java
Log:
Merged MultiDelegateFactory with Delegate, and added a MultiDelegateProxy so
MultiDelegates can be exposed safely without having to write getters and setters.
Revision Changes Path
1.2 +1 -1
jakarta-avalon-excalibur/util/src/java/org/apache/excalibur/util/MultiDelegateProxy.java
Index: MultiDelegateProxy.java
===================================================================
RCS file:
/home/cvs/jakarta-avalon-excalibur/util/src/java/org/apache/excalibur/util/MultiDelegateProxy.java,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- MultiDelegateProxy.java 3 Oct 2002 17:50:32 -0000 1.1
+++ MultiDelegateProxy.java 3 Oct 2002 18:10:26 -0000 1.2
@@ -62,7 +62,7 @@
{
private final MultiDelegate multiDelegate;
- public MultiDelegateWrapper( MultiDelegate multiDelegate )
+ public MultiDelegateProxy( MultiDelegate multiDelegate )
{
this.multiDelegate = multiDelegate;
}
1.3 +25 -13
jakarta-avalon-excalibur/util/src/java/org/apache/excalibur/util/MultiDelegate.java
Index: MultiDelegate.java
===================================================================
RCS file:
/home/cvs/jakarta-avalon-excalibur/util/src/java/org/apache/excalibur/util/MultiDelegate.java,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- MultiDelegate.java 3 Oct 2002 17:37:15 -0000 1.2
+++ MultiDelegate.java 3 Oct 2002 18:10:26 -0000 1.3
@@ -57,25 +57,37 @@
*
* <p>
* <pre><code>
- * public static class Publisher {
+ * public class Publisher {
*
* public static interface Listener
* {
* public void onEvent ();
* }
*
- * public MultiDelegate event = MultiDelegateFactory.newMultiDelegate(
Listener.class );
+ * private final Listener eventDelegate = (Listener)
MultiDelegateFactory.newMultiDelegate( Listener.class );
+ *
+ * // Expose the MultiDelegate's add and remove methods in a safe way.
+ * // Make sure the field exposed is final.
+ * public final MultiDelegate event = new MultiDelegateProxy( (MultiDelegate)
eventDelegate );
*
* public void fireEvent ()
* {
- * ((Listener) event).onEvent ();
+ * eventDelegate.onEvent ();
* }
* }
* </code></pre>
* <p>
*
- * Note the following: We can cast the MultiDelegate instance to the interface
given to
- * the MultiDelegateFactory.newMultiDelegate method.
+ * Note the following:
+ * <ul>
+ * <li>We can cast the <code>MultiDelegate</code> instance to the interface given to
+ * the <code>MultiDelegateFactory.newMultiDelegate</code> method.
+ * <li>While we do expose a <code>MultiDelegate</code> interface to the client, we
do so via a proxy.
+ * This is because the <code>MultiDelegate</code> instance returned from the
<code>MultiDelegateFactory</code> can
+ * be cast to the delegate interface, meaning that a client can invoke
<code>onEvent ()</code>
+ * on it.
+ * </ul>
+ * <p>
* Subscribers/Listeners to the Publisher above can register themselves with:
*
* <p>
@@ -93,13 +105,13 @@
*
* <b>Note that the add and remove methods have Set-like behavior. Adding a
delegate twice has
* the same effect as only adding it once.</b>
- *
- * All MultiDelegates are thread-safe, provided that the delegates they in turn call
- * are thread-safe. For example, if you invoke a MultiDelegate while some other
thread
- * modifies the MultiDelegate (via add or remove), those changes will not affect the
- * current invocation.
- *
- * It is also legal for delegates to remove themselves from the MultiDelegate when
invoked,
+ * <p>
+ * All <code>MultiDelegates</code> are thread-safe, provided that the delegates
they in turn call
+ * are thread-safe. For example, if you invoke a <code>MultiDelegate</code> while
some other thread
+ * modifies the <code>MultiDelegate</code> (via <code>add</code> or
<code>remove</code>), those changes
+ * will not affect the current invocation.
+ * <p>
+ * It is also legal for delegates to remove themselves from the
<code>MultiDelegate</code> when invoked,
* or perform operations on it.
*
* @author <a href="mailto:[EMAIL PROTECTED]">Berin Loritsch</a>
@@ -109,7 +121,7 @@
{
/**
- * Adds a new delegate to the MultiDelegate's list. The Delegate is only added
+ * Adds a new delegate to the MultiDelegate's list. The delegate is only added
* if it does not yet exist in the list.
*
* @param o The delegate we are adding to this MultiDelegate. The delegate
1.8 +169 -1
jakarta-avalon-excalibur/util/src/java/org/apache/excalibur/util/Delegate.java
Index: Delegate.java
===================================================================
RCS file:
/home/cvs/jakarta-avalon-excalibur/util/src/java/org/apache/excalibur/util/Delegate.java,v
retrieving revision 1.7
retrieving revision 1.8
diff -u -r1.7 -r1.8
--- Delegate.java 3 Oct 2002 16:58:48 -0000 1.7
+++ Delegate.java 3 Oct 2002 18:10:26 -0000 1.8
@@ -49,7 +49,14 @@
*/
package org.apache.excalibur.util;
-import java.lang.reflect.*;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.Method;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
/**
@@ -271,6 +278,167 @@
else
{
return m_delegateMethod.invoke( m_instance, args );
+ }
+ }
+ }
+
+ //-------------------------------------------------------
+ // MultiDelegate related methods and classes
+ //-------------------------------------------------------
+
+ /**
+ * Creates a new MultiDelegate.
+ *
+ * @param delegateInterface the delegate interface. The multiDelegate's add and
remove
+ * methods will only accept Objects that implement
this interface.
+ * The interface may only contain a single method that
must have a
+ * return type of void. The rationale behind the
return type is this:
+ * Since the MultiDelegate will in turn invoke many
delegates, there
+ * is no good way of determining which return value to
give back to the
+ * caller.
+ *
+ * @return the MultiDelegate instance. You have to cast it to the interface
+ * you passed in (<code>delegateInterface</code>) to invoke it.
+ */
+ public static MultiDelegate newMultiDelegate( Class delegateInterface )
+ {
+ Method[] declaredMethods = delegateInterface.getDeclaredMethods();
+
+ if( declaredMethods.length != 1 )
+ {
+ throw new IllegalArgumentException("The delegate interface must have
one (1) and only one method.");
+ }
+
+ Class methodReturnType = declaredMethods[0].getReturnType();
+
+ if( !methodReturnType.equals(Void.TYPE) )
+ {
+ throw new IllegalArgumentException("The delegate interface's only
method must return void. (was: " +
+ methodReturnType.getName () + ")");
+ }
+
+ ClassLoader proxyClassLoader = Thread.currentThread
().getContextClassLoader ();
+
+ if (proxyClassLoader == null)
+ {
+ proxyClassLoader = delegateInterface.getClassLoader ();
+ }
+
+ return (MultiDelegate) Proxy.newProxyInstance(
+ proxyClassLoader,
+ new Class[]{ MultiDelegate.class, delegateInterface },
+ new MultiDelegateHandler( delegateInterface ));
+ }
+
+ /**
+ * InvocationHandler for MultiDelegates
+ */
+ static private final class MultiDelegateHandler implements InvocationHandler
+ {
+
+ private final Class delegateClass;
+ private final List handlers = new ArrayList ();
+ private static final Method addMethod;
+ private static final Method removeMethod;
+
+ /*
+ * Static initializer. Finds the add and remove methods in the
MultiDelegate interface.
+ */
+ static
+ {
+ try
+ {
+ addMethod = MultiDelegate.class.getDeclaredMethod( "add", new
Class[]{ Object.class } );
+ removeMethod = MultiDelegate.class.getDeclaredMethod( "remove", new
Class[]{ Object.class } );
+ }
+ catch (Exception e)
+ {
+ throw new LinkageError( "Unable to find MultiDelegate.add and
MultiDelegate.remove in the MultiDelegate interface." );
+ }
+ }
+
+ /**
+ * Creates a new MultiDelegateHandler. The handler's add and remove methods
will
+ * only accept objects that implements the interface given in delegateClass.
+ * The Class passed in must be an interface with only one method that
returns
+ * <code>void</code>
+ *
+ * @param delegateClass The interface representing the MultiDelegate.
+ */
+ public MultiDelegateHandler( Class delegateClass )
+ {
+ this.delegateClass = delegateClass;
+ }
+
+ /**
+ * Handles invocations. If the call is made through the MultiDelegate
interface,
+ * the appropriate methods are called. Otherwise the delegate methods are
called.
+ *
+ * @param proxy The object to forward the method invocations to.
+ * @param method The method that was called
+ * @param args The arguments used for the method
+ *
+ * @return Object must always be an "instance" of void.class
+ *
+ * @throws IllegalAccessException When the method or proxy object
+ * is not accessible
+ * @throws InvocationTargetException When the method throws an
+ * exception
+ */
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws IllegalAccessException, InvocationTargetException
+ {
+ if (method.equals (addMethod))
+ {
+ if( args[0] == null )
+ {
+ throw new IllegalArgumentException( "Argument to
MultiDelegate.add(Object) is null." );
+ }
+
+ if( !delegateClass.isAssignableFrom(args[0].getClass()) )
+ {
+ throw new ClassCastException( "The argument to
MultiDelegate.add(Object) does not have the " +
+ "correct delegate interface, which is " +
delegateClass.getName () + ". Instead, it is a " +
+ args[0].getClass().getName () + ".");
+ }
+
+ synchronized( handlers )
+ {
+ if( !handlers.contains( args[0] ) )
+ {
+ handlers.add (args[0]);
+ }
+ }
+ return null;
+ }
+ else if (method.equals (removeMethod))
+ {
+ synchronized( handlers )
+ {
+ handlers.remove (args[0]);
+ }
+ return null;
+ }
+ else
+ {
+ List activeList = new ArrayList ();
+ synchronized( handlers )
+ {
+ // Copy the list of handlers to allow for updates to
+ // the list during invocation.
+
+ activeList.addAll( handlers );
+ }
+
+ Iterator iter = activeList.iterator ();
+ while (iter.hasNext ())
+ {
+ method.invoke (iter.next (), args);
+ }
+
+ // As MultiDelegates are multicast, the only allowed return value
is void.
+
+ return null;
}
}
}
--
To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]>
For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>