View Javadoc
1   package org.apache.commons.beanutils2;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import static java.lang.System.getSecurityManager;
23  import static java.lang.reflect.Modifier.isPublic;
24  import static java.security.AccessController.doPrivileged;
25  import static org.apache.commons.beanutils2.Assertions.checkArgument;
26  import static org.apache.commons.beanutils2.TypeUtils.checkTypesCompatible;
27  import static org.apache.commons.beanutils2.TypeUtils.getPrimitiveWrapper;
28  import static org.apache.commons.beanutils2.TypeUtils.isAssignmentCompatible;
29  
30  import java.lang.reflect.AccessibleObject;
31  import java.lang.reflect.Constructor;
32  import java.lang.reflect.Member;
33  import java.lang.reflect.Method;
34  import java.security.PrivilegedAction;
35  import java.util.Arrays;
36  import java.util.Map;
37  import java.util.WeakHashMap;
38  import java.util.concurrent.locks.Lock;
39  import java.util.concurrent.locks.ReentrantLock;
40  
41  abstract class AccessibleObjectsRegistry<AO extends AccessibleObject & Member>
42  {
43  
44      // extracted from BeanUtils1
45      private static final float NON_FOUND_COST = 1.5f;
46  
47      // extracted from BeanUtils1
48      private static final float SUPERCLASS_COST = 1.0f;
49  
50      // extracted from BeanUtils1
51      private static final float INTERFACE_COST = 0.25f;
52  
53      // extracted from BeanUtils1
54      private static final float WRAPPER_COST = 0.25f;
55  
56      private static final AccessibleObjectsRegistry<Constructor<?>> CONSTRUCTORS_REGISTRY = new ConstructorsRegistry();
57  
58      private static final AccessibleObjectsRegistry<Method> METHODS_REGISTRY = new MethodsRegistry();
59  
60      public static AccessibleObjectsRegistry<Constructor<?>> getConstructorsRegistry()
61      {
62          return CONSTRUCTORS_REGISTRY;
63      }
64  
65      public static AccessibleObjectsRegistry<Method> getMethodsRegistry()
66      {
67          return METHODS_REGISTRY;
68      }
69  
70      private final Map<AccessibleObjectDescriptor, AO> cache = new WeakHashMap<AccessibleObjectDescriptor, AO>();
71  
72      private AccessibleObjectsRegistry()
73      {
74          // this class cannot be instantiated
75      }
76  
77      public final AO get( boolean exact, Class<?> type, Class<?>... parameterTypes )
78      {
79          return get( exact, type, null, parameterTypes );
80      }
81  
82      public final AO get( boolean exact, Class<?> type, String name, Class<?>... parameterTypes )
83      {
84          AccessibleObjectDescriptor key = new AccessibleObjectDescriptor( exact, type, name, parameterTypes );
85  
86          final Lock lock = new ReentrantLock();
87          lock.lock();
88          try
89          {
90              AO accessibleObject = cache.get( key );
91              if ( accessibleObject != null )
92              {
93                  return accessibleObject;
94              }
95  
96              // see if we can find the accessible object directly
97              // most of the time this works and it's much faster
98              try
99              {
100                 accessibleObject = resolveDirectly( type, name, parameterTypes );
101                 if ( exact || accessibleObject != null )
102                 {
103                     return makeAccessible( accessibleObject );
104                 }
105             }
106             catch ( NoSuchMethodException e )
107             {
108                 /* SWALLOW */
109                 if ( exact )
110                 {
111                     return null;
112                 }
113             }
114 
115             // start calculating the best matching
116 
117             int paramSize = parameterTypes.length;
118             AO bestMatch = null;
119             AO[] accessibleObjectsArray = getAccessibleObjectsArray( type );
120             float bestMatchCost = Float.MAX_VALUE;
121             float myCost = Float.MAX_VALUE;
122             for ( int i = 0, size = accessibleObjectsArray.length; i < size; i++ )
123             {
124                 if ( matches( accessibleObjectsArray[i], name ) )
125                 {
126                     // compare parameters
127                     Class<?>[] methodsParams = getParameterTypes( accessibleObjectsArray[i] );
128                     int methodParamSize = methodsParams.length;
129                     if ( methodParamSize == paramSize )
130                     {
131                         boolean match = checkTypesCompatible( methodsParams, parameterTypes );
132 
133                         if ( match )
134                         {
135                             // get accessible version of method
136                             AO current = resolveAccessible( type, accessibleObjectsArray[i] );
137                             if ( current != null )
138                             {
139                                 // determine which method the compiler would chose
140                                 myCost = getTotalTransformationCost( parameterTypes, getParameterTypes( current ) );
141                                 if ( myCost < bestMatchCost )
142                                 {
143                                     bestMatch = current;
144                                     bestMatchCost = myCost;
145                                 }
146                             }
147                         }
148                     }
149                 }
150             }
151 
152             if ( bestMatch != null )
153             {
154                 bestMatch = makeAccessible( bestMatch );
155             }
156             cache.put( key, bestMatch );
157             return bestMatch;
158         }
159         finally
160         {
161             lock.unlock();
162         }
163     }
164 
165     protected abstract AO resolveDirectly( Class<?> type, String name, Class<?>... parameterTypes )
166         throws NoSuchMethodException;
167 
168     protected abstract AO[] getAccessibleObjectsArray( Class<?> type );
169 
170     protected abstract boolean matches( AO accessibleObject, String name );
171 
172     protected abstract Class<?>[] getParameterTypes( AO accessibleObject );
173 
174     protected abstract AO resolveAccessible( Class<?> type, AO accessibleObject );
175 
176     /**
177      * Returns the sum of the object transformation cost for each class in the source argument list.
178      *
179      * @param srcArgs The source arguments
180      * @param destArgs The destination arguments
181      * @return The total transformation cost
182      */
183     private static float getTotalTransformationCost( Class<?>[] srcArgs, Class<?>[] destArgs )
184     {
185         float totalCost = 0.0f;
186         for ( int i = 0; i < srcArgs.length; i++ )
187         {
188             Class<?> srcClass, destClass;
189             srcClass = srcArgs[i];
190             destClass = destArgs[i];
191             totalCost += getObjectTransformationCost( srcClass, destClass );
192         }
193         return totalCost;
194     }
195 
196     /**
197      * Determines the costs of turning the source class into the destination class. This represents the weighted number
198      * of steps in the object hierarchy graph.
199      *
200      * @param srcClass The source class
201      * @param destClass The destination class
202      * @return The cost of transforming an object
203      */
204     private static float getObjectTransformationCost( Class<?> srcClass, Class<?> destClass )
205     {
206         float cost = 0.0f;
207         while ( srcClass != null && !destClass.equals( srcClass ) )
208         {
209             if ( destClass.isPrimitive() )
210             {
211                 Class<?> destClassWrapperClazz = getPrimitiveWrapper( destClass );
212                 if ( destClassWrapperClazz != null && destClassWrapperClazz.equals( srcClass ) )
213                 {
214                     cost += WRAPPER_COST;
215                     break;
216                 }
217             }
218             if ( destClass.isInterface() && isAssignmentCompatible( destClass, srcClass ) )
219             {
220                 // slight penalty for interface match.
221                 // we still want an exact match to override an interface match, but
222                 // an interface match should override anything where we have to get a
223                 // superclass.
224                 cost += INTERFACE_COST;
225                 break;
226             }
227             cost += SUPERCLASS_COST;
228             srcClass = srcClass.getSuperclass();
229         }
230 
231 
232         // If the destination class is null, we've travelled all the way up to an Object match.
233         if ( srcClass == null )
234         {
235             cost += NON_FOUND_COST;
236         }
237 
238         return cost;
239     }
240 
241     private AO makeAccessible( AO accessibleObject )
242     {
243         PrivilegedAction<AO> action = new MakeAccessiblePrivilegedAction<AO>( accessibleObject );
244         if ( getSecurityManager() != null )
245         {
246             return doPrivileged( action );
247         }
248         return action.run();
249     }
250 
251     /**
252      * Constructors registry implementation.
253      */
254     private static class ConstructorsRegistry extends AccessibleObjectsRegistry<Constructor<?>>
255     {
256 
257         @Override
258         protected Constructor<?> resolveDirectly( Class<?> type, String name, Class<?>...parameterTypes )
259             throws NoSuchMethodException
260         {
261             return type.getConstructor( parameterTypes );
262         }
263 
264         @Override
265         protected Constructor<?>[] getAccessibleObjectsArray( Class<?> type )
266         {
267             return type.getConstructors();
268         }
269 
270         @Override
271         protected boolean matches( Constructor<?> accessibleObject, String name )
272         {
273             return true;
274         }
275 
276         @Override
277         protected Class<?>[] getParameterTypes( Constructor<?> accessibleObject )
278         {
279             return accessibleObject.getParameterTypes();
280         }
281 
282         @Override
283         protected Constructor<?> resolveAccessible( Class<?> type, Constructor<?> accessibleObject )
284         {
285             if ( accessibleObject == null )
286             {
287                 return null;
288             }
289 
290             if ( !isPublic( accessibleObject.getModifiers() ) )
291             {
292                 return null;
293             }
294 
295             // If the declaring class is public, we are done
296             Class<?> beanClass = accessibleObject.getDeclaringClass();
297             if ( isPublic( beanClass.getModifiers() ) )
298             {
299                 return accessibleObject;
300             }
301 
302             // what else can we do?
303             return null;
304         }
305 
306     }
307 
308     /**
309      * Methods registry
310      */
311     private static final class MethodsRegistry extends AccessibleObjectsRegistry<Method>
312     {
313 
314         @Override
315         protected Method resolveDirectly( Class<?> type, String name, Class<?>...parameterTypes )
316             throws NoSuchMethodException
317         {
318             return type.getMethod( name, parameterTypes );
319         }
320 
321         @Override
322         protected Method[] getAccessibleObjectsArray( Class<?> type )
323         {
324             return type.getMethods();
325         }
326 
327         @Override
328         protected boolean matches( Method accessibleObject, String name )
329         {
330             return name.equals( accessibleObject.getName() );
331         }
332 
333         @Override
334         protected Class<?>[] getParameterTypes( Method accessibleObject )
335         {
336             return accessibleObject.getParameterTypes();
337         }
338 
339         @Override
340         protected Method resolveAccessible( Class<?> type, Method method )
341         {
342             // Make sure we have a method to check
343             if ( method == null )
344             {
345                 return null;
346             }
347 
348             // If the requested method is not public we cannot call it
349             if ( !isPublic( method.getModifiers() ) )
350             {
351                 return null;
352             }
353 
354             if ( type == null )
355             {
356                 type = method.getDeclaringClass();
357             }
358             else
359             {
360                 checkArgument( method.getDeclaringClass().isAssignableFrom( type ),
361                                "%s is not assignable from ", type.getName(), method.getDeclaringClass().getName() );
362             }
363 
364             // If the class is public, we are done
365             if ( isPublic( type.getModifiers() ) )
366             {
367                 return method;
368             }
369 
370             String methodName = method.getName();
371             Class<?>[] parameterTypes = method.getParameterTypes();
372 
373             // Check the implemented interfaces and subinterfaces
374             method = getAccessibleMethodFromInterfaceNest( type, methodName, parameterTypes );
375 
376             // Check the superclass chain
377             if ( method == null )
378             {
379                 method = getAccessibleMethodFromSuperclass( type, methodName, parameterTypes );
380             }
381 
382             return method;
383         }
384 
385         /**
386          * <p>Return an accessible method (that is, one that can be invoked via
387          * reflection) by scanning through the superclasses. If no such method
388          * can be found, return <code>null</code>.</p>
389          *
390          * @param clazz Class to be checked
391          * @param methodName Method name of the method we wish to call
392          * @param parameterTypes The parameter type signatures
393          */
394         private static Method getAccessibleMethodFromSuperclass( Class<?> clazz, String methodName,
395                                                                  Class<?>... parameterTypes )
396         {
397             Class<?> parentClazz = clazz.getSuperclass();
398             while ( parentClazz != null )
399             {
400                 if ( isPublic( parentClazz.getModifiers() ) )
401                 {
402                     try
403                     {
404                         return parentClazz.getMethod( methodName, parameterTypes );
405                     }
406                     catch ( NoSuchMethodException e )
407                     {
408                         // go ahead until found or parentClazz == null
409                     }
410                 }
411                 parentClazz = parentClazz.getSuperclass();
412             }
413             return null;
414         }
415 
416         /**
417          * <p>Return an accessible method (that is, one that can be invoked via
418          * reflection) that implements the specified method, by scanning through
419          * all implemented interfaces and subinterfaces.  If no such method
420          * can be found, return <code>null</code>.</p>
421          *
422          * <p> There isn't any good reason why this method must be private.
423          * It is because there doesn't seem any reason why other classes should
424          * call this rather than the higher level methods.</p>
425          *
426          * @param clazz Parent class for the interfaces to be checked
427          * @param methodName Method name of the method we wish to call
428          * @param parameterTypes The parameter type signatures
429          */
430         private static Method getAccessibleMethodFromInterfaceNest( Class<?> clazz, String methodName,
431                                                                     Class<?>... parameterTypes )
432         {
433             Method method = null;
434 
435             // Search up the superclass chain
436             while ( clazz != null )
437             {
438                 // Check the implemented interfaces of the parent class
439                 for ( Class<?> interfaceType : clazz.getInterfaces() )
440                 {
441                     // Is this interface public?
442                     if ( !isPublic( interfaceType.getModifiers() ) )
443                     {
444                         continue;
445                     }
446 
447                     // Does the method exist on this interface?
448                     try
449                     {
450                         method = interfaceType.getDeclaredMethod( methodName, parameterTypes );
451                     }
452                     catch ( NoSuchMethodException e )
453                     {
454                         /*
455                          * Swallow, if no method is found after the loop then this method returns null.
456                          */
457                     }
458                     if ( method != null )
459                     {
460                         return method;
461                     }
462 
463                     // Recursively check our parent interfaces
464                     method = getAccessibleMethodFromInterfaceNest( interfaceType, methodName, parameterTypes );
465                     if ( method != null )
466                     {
467                         return method;
468                     }
469                 }
470 
471                 clazz = clazz.getSuperclass();
472             }
473 
474             // We did not find anything
475             return null;
476         }
477 
478     }
479 
480     /**
481      * Represents the key to looking up an AccessibleObject by reflection.
482      */
483     private static final class AccessibleObjectDescriptor
484     {
485 
486         private final Class<?> type;
487 
488         private final String methodName;
489 
490         private final Class<?>[] parameterTypes;
491 
492         private final boolean exact;
493 
494         private final int hashCode;
495 
496         /**
497          * The sole constructor.
498          *
499          * @param type  the class to reflect, must not be null
500          * @param methodName  the method name to obtain
501          * @param parameterTypes the array of classes representing the parameter types
502          * @param exact whether the match has to be exact.
503          */
504         public AccessibleObjectDescriptor( boolean exact, Class<?> type, String methodName, Class<?>... parameterTypes )
505         {
506             final int prime = 31;
507             int hashCode = 1;
508 
509             this.exact = exact;
510             hashCode = prime * hashCode + ( exact ? 1231 : 1237 );
511 
512             this.type = type;
513             hashCode = prime * hashCode + ( ( type == null ) ? 0 : type.getName().hashCode() );
514 
515             this.methodName = methodName;
516             hashCode = prime * hashCode + ( ( methodName == null ) ? 0 : methodName.hashCode() );
517 
518             this.parameterTypes = parameterTypes;
519             hashCode = prime * hashCode + Arrays.hashCode( parameterTypes );
520 
521             this.hashCode = hashCode;
522         }
523 
524         @Override
525         public int hashCode()
526         {
527             return hashCode;
528         }
529 
530         @Override
531         public boolean equals( Object obj )
532         {
533             if ( this == obj )
534             {
535                 return true;
536             }
537             if ( obj == null )
538             {
539                 return false;
540             }
541 
542             AccessibleObjectDescriptor other = (AccessibleObjectDescriptor) obj;
543 
544             if ( type != other.type )
545             {
546                 return false;
547             }
548 
549             if ( exact != other.exact )
550             {
551                 return false;
552             }
553 
554             /* methodName can  */
555             if ( methodName == null )
556             {
557                 if ( other.methodName != null )
558                 {
559                     return false;
560                 }
561             }
562             else if ( !methodName.equals( other.methodName ) )
563             {
564                 return false;
565             }
566 
567             if ( !Arrays.equals( parameterTypes, other.parameterTypes ) )
568             {
569                 return false;
570             }
571 
572             return true;
573         }
574 
575     }
576 
577     private static class MakeAccessiblePrivilegedAction<T extends AccessibleObject>
578         implements PrivilegedAction<T>
579     {
580 
581         private final T accessibleObject;
582 
583         /**
584          * hidden constructor, this class cannot be instantiated directly
585          */
586         private MakeAccessiblePrivilegedAction( T accessibleObject )
587         {
588             this.accessibleObject = accessibleObject;
589         }
590 
591         /**
592          * {@inheritDoc}
593          */
594         public T run()
595         {
596             try
597             {
598                 accessibleObject.setAccessible( true );
599             }
600             catch ( SecurityException se )
601             {
602                 /*
603                  * Swallow SecurityException TODO: Why?
604                  */
605             }
606             return accessibleObject;
607         }
608 
609     }
610 
611 }