View Javadoc

1   //----------------------------------------------------------------------
2   // 
3   // PerfectJPattern: "Design patterns are good but components are better!" 
4   // AbstractDelegator.java Copyright (c) 2009 Giovanni Azua Garcia
5   // bravegag@hotmail.com
6   //  
7   // This program is free software; you can redistribute it and/or
8   // modify it under the terms of the GNU General Public License
9   // as published by the Free Software Foundation; either version 3
10  // of the License, or (at your option) any later version.
11  //
12  // This program is distributed in the hope that it will be useful,
13  // but WITHOUT ANY WARRANTY; without even the implied warranty of
14  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  // GNU General Public License for more details.
16  //
17  // You should have received a copy of the GNU General Public License
18  // along with this program; if not, see <http://www.gnu.org/licenses/>.
19  //
20  //----------------------------------------------------------------------
21  package org.perfectjpattern.core.extras.delegate;
22  
23  import java.lang.reflect.*;
24  import java.util.*;
25  
26  import org.apache.commons.lang.*;
27  import org.perfectjpattern.core.api.extras.delegate.*;
28  
29  
30  /**
31   * Abstract base implementation of <code>IDelegator</code> interface.
32   * <br/><br/>
33   * <b>Notes</b>: Base source code implemented by Steve Lewis and Wilhelm 
34   * Fitzpatrick and adapted to fit PerfectJPattern componentization 
35   * criteria and code conventions.
36   *
37   * @see IDelegator
38   * 
39   * @param <I> Interface type built by this <code>Delegator</code> instance.
40   * 
41   * @author <a href="mailto:smlewis@lordjoe.com">Steve Lewis</a>
42   * @author <a href="mailto:wilhelmf@agileinformatics.com">Wilhelm 
43   * Fitzpatrick</a>
44   * @author <a href="mailto:bravegag@hotmail.com">Giovanni Azua</a>
45   * @version $Revision: 1.0 $ $Date: Jun 25, 2007 7:01:04 AM $
46   */
47  @SuppressWarnings("unchecked")
48  public abstract
49  class AbstractDelegator<I>
50  implements IDelegator<I> 
51  {
52      //------------------------------------------------------------------------
53      // protected
54      //------------------------------------------------------------------------
55      /**
56       * Constructs <code>Delegator</code> instance using a method
57       * signature. <br/><br/>
58       * 
59       * @param aReturnClass Return class type.
60       * @param aParameters Parameter class types.
61       */
62      protected
63      AbstractDelegator(Class aReturnClass, Class... aParameters) 
64      {
65          theReturn = aReturnClass;
66          theArguments = aParameters;
67      }    
68      
69      //------------------------------------------------------------------------
70      /**
71       * Constructs <code>Delegator</code> instance using an interface type 
72       * that implements exactly one method.
73       * 
74       * @param anInterface Interface with EXACTLY one method.
75       * @throws IllegalArgumentException DelegateTemplate must be constructed 
76       * with an interface implementing exactly one method!
77       */
78      protected
79      AbstractDelegator(Class<I> anInterface) 
80      {
81          Method myMethod = findMethod(anInterface);
82          
83          theReturn = myMethod.getReturnType();
84          theArguments = myMethod.getParameterTypes();
85      }
86  
87      //------------------------------------------------------------------------
88      /**
89       * Returns true if the Method matches the given signature specified by 
90       * Arguments and Return, false otherwise.
91       * 
92       * @param aTestMethod
93       * @param anArguments
94       * @param aReturnClass
95       * @return true if the Method matches the given signature specified by 
96       * Arguments and Return, false otherwise.
97       */
98      @SuppressWarnings("unchecked")
99      protected boolean 
100     isSuitableMethod(Method aTestMethod, Class aReturnClass, 
101         Class... anArguments)
102     {
103         Class[] myMethodArgs = aTestMethod.getParameterTypes();
104 
105         for (int i = 0; i < myMethodArgs.length; i++) 
106         {
107             Class myArgument = myMethodArgs[i];
108 
109             if (!myArgument.isAssignableFrom(anArguments[i])) 
110             {
111                 return false;
112             }
113         }
114 
115         return isValidReturn(aTestMethod, aReturnClass);
116     }
117 
118     //------------------------------------------------------------------------
119     /**
120      * Returns true if return type matches the expected type, false otherwise.
121      * 
122      * @param aTestMethod
123      * @param aReturnClass
124      * @return true if return type matches the expected type, false otherwise.
125      */
126     @SuppressWarnings("unchecked")
127     protected boolean 
128     isValidReturn(Method aTestMethod, Class aReturnClass) 
129     {
130         return (aReturnClass == null             
131              || aTestMethod.getReturnType() == aReturnClass
132              || aTestMethod.getReturnType().equals(aReturnClass)
133              || aReturnClass.isAssignableFrom(aTestMethod.getReturnType()));
134     }
135 
136     //------------------------------------------------------------------------
137     /**
138      * Returns all candidate methods within a class that match the given 
139      * signature.
140      * 
141      * @param aTargetClass
142      * @param aMethodName
143      * @param aNumberOfArguments
144      * @return all candidate methods within a class that match the given 
145      *         signature.
146      */
147     @SuppressWarnings("unchecked")
148     protected static Method[] 
149     findMethod(Class aTargetClass, String aMethodName, 
150         int aNumberOfArguments) 
151     {
152         Method[] myPossibilities = aTargetClass.getMethods();
153         List myHolder = new ArrayList();
154 
155         for (int i = 0; i < myPossibilities.length; i++) 
156         {
157             Method myPossibility = myPossibilities[i];
158 
159             if (myPossibility.getName().equals(aMethodName) &&
160                 myPossibility.getParameterTypes().length == 
161                     aNumberOfArguments && Modifier.isPublic(myPossibility.
162                         getModifiers())) 
163             {
164                 myHolder.add(myPossibility);
165             }
166         }
167 
168         return (Method[]) myHolder.toArray(EMPTY_METHOD_ARRAY);
169     }
170 
171     //------------------------------------------------------------------------
172     /**
173      * Returns suitable Method within class type that matches the given 
174      * Delegator signature.
175      * 
176      * @param aTargetClass Class type to search for matching method.
177      * @param aMethodName Method name to search for.
178      * @param aDelegator Delegate specification.
179      * @return suitable Method within class type that matches the given 
180      * Delegate specification.
181      * @throws IllegalArgumentException Requested method returns wrong type.
182      * @throws IllegalArgumentException Requested method is not public.
183      * @throws UnsupportedOperationException No suitable method found.
184      */
185     @SuppressWarnings("unchecked")
186     protected Method 
187     findMethod(Class aTargetClass, String aMethodName, 
188         AbstractDelegator aDelegator)
189     throws IllegalArgumentException, UnsupportedOperationException
190     {
191         Class[] myArguments = aDelegator.getArguments();
192         Class myReturnClass = aDelegator.getReturn();
193 
194         try
195         {
196             Method myReturn = aTargetClass.getMethod(aMethodName, myArguments);
197 
198             Validate.isTrue(isValidReturn(myReturn, myReturnClass), 
199                 "Requested method returns wrong type.");
200             
201             Validate.isTrue(Modifier.isPublic(myReturn.getModifiers()), 
202                 "Requested method is not public.");
203 
204             return myReturn;
205         }
206         catch (SecurityException anException)
207         {
208             // try next possibility
209         }
210         catch (NoSuchMethodException anException)
211         {
212             // try next possibility
213         }
214 
215         Method[] myPossibilities = findMethod(aTargetClass, aMethodName, 
216             myArguments.length);
217 
218         for (int i = 0; i < myPossibilities.length; i++) 
219         {
220             Method myPossibility = myPossibilities[i];
221 
222             if (isSuitableMethod(myPossibility, myReturnClass, myArguments)) 
223             {
224                 return myPossibility;
225             }
226         }
227 
228         throw new UnsupportedOperationException("No suitable method found.");
229     }
230 
231     //------------------------------------------------------------------------
232     /**
233      * Returns suitable <code>Method</code> found for the given interface type.
234      * 
235      * @param anInterface
236      * @return The suitable <code>Method</code>
237      * @throws IllegalArgumentException 'anInterface' must not be null.
238      * @throws IllegalArgumentException DelegateTemplate must be constructed 
239      * with an interface implementing exactly one method!
240      */
241     protected static Method 
242     findMethod(Class anInterface) 
243     {
244         Validate.notNull(anInterface, "'anInterface' must not be null");
245         Validate.isTrue(anInterface.isInterface(), 
246             "DelegateTemplate must be constructed with an interface");
247 
248         Method[] myMethods = anInterface.getMethods();
249         Method myReturn = null;
250 
251         for (int i = 0; i < myMethods.length; i++) 
252         {
253             Method myTest = myMethods[i];
254 
255             if (Modifier.isAbstract(myTest.getModifiers())) 
256             {
257                 Validate.isTrue(myReturn == null, "DelegateTemplate must be " +
258                     "constructed with an interface implementing exactly one " +
259                     "method!");
260 
261                 myReturn = myTest;
262             }
263         }
264 
265         Validate.notNull(myReturn, "DelegateTemplate must be constructed " +
266             "with an interface implementing exactly one method!");
267 
268         return myReturn;
269     }
270     
271     //------------------------------------------------------------------------
272     /**
273      * Returns the Return class type.
274      * 
275      * @return the Return class type.
276      */
277     protected Class 
278     getReturn() 
279     {
280         return theReturn;
281     }
282 
283     //------------------------------------------------------------------------
284     /**
285      * Returns the Arguments class types.
286      * 
287      * @return Arguments class types.
288      */
289     protected Class[] 
290     getArguments() 
291     {
292         return theArguments;
293     }
294 
295     //------------------------------------------------------------------------
296     // inner classes
297     //------------------------------------------------------------------------
298     protected
299     class DelegateProxy 
300     implements IDelegate, InvocationHandler 
301     {
302         //--------------------------------------------------------------------
303         /**
304          * Constructs a DelegateProxy supplying a Class passing in types not 
305          * template.
306          * 
307          * @param aTarget
308          * @param aTargetClass
309          * @param aMethodName
310          * @param aTemplate
311          * @throws UnsupportedOperationException
312          */
313         public 
314         DelegateProxy(Object aTarget, Class aTargetClass, String aMethodName, 
315             AbstractDelegator aTemplate) 
316         throws UnsupportedOperationException
317         {
318             theTarget = aTarget;
319 
320             Method myMethod = 
321                 findMethod(aTargetClass, aMethodName, aTemplate);
322             theMethod = myMethod;
323         }
324 
325         //--------------------------------------------------------------------
326         /** 
327          * {@inheritDoc}
328          */
329         public Object 
330         invoke(Object aProxy, Method aMethod, Object[] anArguments) 
331         {
332             return invoke(anArguments);
333         }
334 
335         //--------------------------------------------------------------------
336         /** 
337          * {@inheritDoc}
338          */
339         public Object 
340         invoke(Object... anArguments)
341         throws IllegalArgumentException, UnsupportedOperationException
342         {
343             validateArgs(anArguments, getArguments());
344             
345             try 
346             {
347                 Object myReturn = getMethod().invoke(getTarget(), anArguments);
348 
349                 return myReturn;
350             } 
351             catch (IllegalAccessException anException) 
352             {
353                 throw new UnsupportedOperationException(
354                     anException.getMessage()); 
355             } 
356             catch (InvocationTargetException anException) 
357             {
358                 throw new UnsupportedOperationException(anException.getCause());
359             }
360         }
361 
362         //--------------------------------------------------------------------
363         /**
364          * Validate actual arguments to match those expected from the Delegate 
365          * type.
366          * 
367          * @param anArguments Arguments to validate
368          * @param anExpectedArguments Expected arguments signature
369          * @throws IllegalArgumentException 'anExpectedArguments' must not be 
370          *         null
371          * @throws IllegalArgumentException Delegator required 'N' arguments
372          * @throws IllegalArgumentException Argument 'i' must be of type 'X'
373          */
374         protected void 
375         validateArgs(Object[] anArguments, Class[] anExpectedArguments)
376         throws IllegalArgumentException 
377         {
378             Validate.notNull(anExpectedArguments, 
379                 "'anExpectedArguments' must not be null");
380             
381             Validate.isTrue((anArguments == null && anExpectedArguments.length 
382                 == 0) || (anArguments != null && anArguments.length == 
383                     anExpectedArguments.length), "Delegator required '" + 
384                         anExpectedArguments.length + "' arguments");
385 
386             if (anArguments != null)
387             {
388                 for (int i = 0; i < anArguments.length; i++) 
389                 {
390                     Validate.isTrue(anExpectedArguments[i].isInstance(
391                         anArguments[i]), "Argument '" + i + "' must be of " +
392                             "type '" + anExpectedArguments[i].getName() + "'");
393                 }
394             }
395         }
396 
397         //--------------------------------------------------------------------
398         /**
399          * Returns the method.
400          * 
401          * @return Method.
402          */
403         protected Method 
404         getMethod() 
405         {
406             return theMethod;
407         }
408 
409         //--------------------------------------------------------------------
410         /**
411          * Returns the target Object.
412          * 
413          * @return Target Object.
414          */
415         protected Object 
416         getTarget() 
417         {
418             return theTarget;
419         }
420 
421         //--------------------------------------------------------------------
422         // members
423         //--------------------------------------------------------------------
424         private final Method theMethod;
425         private final Object theTarget;        
426     }
427             
428     //------------------------------------------------------------------------
429     // members
430     //------------------------------------------------------------------------
431     private static final Method[] EMPTY_METHOD_ARRAY = {};
432 
433     private final Class theReturn;
434     private final Class[] theArguments;    
435 }