Coverage Report - org.apache.maven.shared.utils.introspection.ReflectionValueExtractor
 
Classes in this File Line Coverage Branch Coverage Complexity
ReflectionValueExtractor
67%
70/104
79%
19/24
12.75
 
 1  
 package org.apache.maven.shared.utils.introspection;
 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 java.lang.reflect.InvocationTargetException;
 23  
 import java.lang.reflect.Method;
 24  
 import java.util.Arrays;
 25  
 import java.util.List;
 26  
 import java.util.Map;
 27  
 import java.util.StringTokenizer;
 28  
 import java.util.WeakHashMap;
 29  
 import java.util.regex.Matcher;
 30  
 import java.util.regex.Pattern;
 31  
 import org.apache.maven.shared.utils.StringUtils;
 32  
 import org.apache.maven.shared.utils.introspection.MethodMap.AmbiguousException;
 33  
 
 34  
 import javax.annotation.Nonnull;
 35  
 import javax.annotation.Nullable;
 36  
 
 37  
 
 38  
 /**
 39  
  * <p>Using simple dotted expressions to extract the values from an Object instance,
 40  
  * For example we might want to extract a value like: <code>project.build.sourceDirectory</code></p>
 41  
  * <p/>
 42  
  * <p>The implementation supports indexed, nested and mapped properties similar to the JSP way.</p>
 43  
  *
 44  
  * @author <a href="mailto:jason@maven.org">Jason van Zyl </a>
 45  
  * @author <a href="mailto:vincent.siveton@gmail.com">Vincent Siveton</a>
 46  
  * @version $Id$
 47  
  * @see <a href="http://struts.apache.org/1.x/struts-taglib/indexedprops.html">http://struts.apache.org/1.x/struts-taglib/indexedprops.html</a>
 48  
  */
 49  
 public class ReflectionValueExtractor
 50  
 {
 51  1
     private static final Class<?>[] CLASS_ARGS = new Class[0];
 52  
 
 53  1
     private static final Object[] OBJECT_ARGS = new Object[0];
 54  
 
 55  
     /**
 56  
      * Use a WeakHashMap here, so the keys (Class objects) can be garbage collected.
 57  
      * This approach prevents permgen space overflows due to retention of discarded
 58  
      * classloaders.
 59  
      */
 60  1
     private static final Map<Class<?>, ClassMap> classMaps = new WeakHashMap<Class<?>, ClassMap>();
 61  
 
 62  
     /**
 63  
      * Indexed properties pattern, ie <code>(\\w+)\\[(\\d+)\\]</code>
 64  
      */
 65  1
     private static final Pattern INDEXED_PROPS = Pattern.compile( "(\\w+)\\[(\\d+)\\]" );
 66  
 
 67  
     /**
 68  
      * Indexed properties pattern, ie <code>(\\w+)\\((.+)\\)</code>
 69  
      */
 70  1
     private static final Pattern MAPPED_PROPS = Pattern.compile( "(\\w+)\\((.+)\\)" );
 71  
 
 72  
     private ReflectionValueExtractor()
 73  0
     {
 74  0
     }
 75  
 
 76  
     /**
 77  
      * <p>The implementation supports indexed, nested and mapped properties.</p>
 78  
      * <p/>
 79  
      * <ul>
 80  
      * <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
 81  
      * <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
 82  
      * pattern, i.e. "user.addresses[1].street"</li>
 83  
      * <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern, i.e. "user.addresses(myAddress).street"</li>
 84  
      * <ul>
 85  
      *
 86  
      * @param expression not null expression
 87  
      * @param root       not null object
 88  
      * @return the object defined by the expression
 89  
      * @throws IntrospectionException if any
 90  
      */
 91  
     public static Object evaluate( @Nonnull String expression, @Nullable Object root )
 92  
         throws IntrospectionException
 93  
     {
 94  17
         return evaluate( expression, root, true );
 95  
     }
 96  
 
 97  
     /**
 98  
      * <p>The implementation supports indexed, nested and mapped properties.</p>
 99  
      * <p/>
 100  
      * <ul>
 101  
      * <li>nested properties should be defined by a dot, i.e. "user.address.street"</li>
 102  
      * <li>indexed properties (java.util.List or array instance) should be contains <code>(\\w+)\\[(\\d+)\\]</code>
 103  
      * pattern, i.e. "user.addresses[1].street"</li>
 104  
      * <li>mapped properties should be contains <code>(\\w+)\\((.+)\\)</code> pattern, i.e. "user.addresses(myAddress).street"</li>
 105  
      * <ul>
 106  
      *
 107  
      * @param expression not null expression
 108  
      * @param root       not null object
 109  
      * @return the object defined by the expression
 110  
      * @throws IntrospectionException if any
 111  
      */
 112  
     public static Object evaluate( @Nonnull String expression, @Nullable Object root, boolean trimRootToken )
 113  
         throws IntrospectionException
 114  
     {
 115  
         // if the root token refers to the supplied root object parameter, remove it.
 116  17
         if ( trimRootToken )
 117  
         {
 118  17
             expression = expression.substring( expression.indexOf( '.' ) + 1 );
 119  
         }
 120  
 
 121  17
         Object value = root;
 122  
 
 123  
         // ----------------------------------------------------------------------
 124  
         // Walk the dots and retrieve the ultimate value desired from the
 125  
         // MavenProject instance.
 126  
         // ----------------------------------------------------------------------
 127  
 
 128  17
         StringTokenizer parser = new StringTokenizer( expression, "." );
 129  
 
 130  36
         while ( parser.hasMoreTokens() )
 131  
         {
 132  
             // if we have nothing, stop now
 133  22
             if ( value == null )
 134  
             {
 135  0
                 return null;
 136  
             }
 137  
 
 138  22
             String token = parser.nextToken();
 139  
 
 140  22
             ClassMap classMap = getClassMap( value.getClass() );
 141  
 
 142  
             Method method;
 143  22
             Object[] localParams = OBJECT_ARGS;
 144  
 
 145  
             // do we have an indexed property?
 146  22
             Matcher matcher = INDEXED_PROPS.matcher( token );
 147  22
             if ( matcher.find() )
 148  
             {
 149  6
                 String methodBase = StringUtils.capitalizeFirstLetter( matcher.group( 1 ) );
 150  6
                 String methodName = "get" + methodBase;
 151  
                 try
 152  
                 {
 153  6
                     method = classMap.findMethod( methodName, CLASS_ARGS );
 154  
                 }
 155  0
                 catch ( AmbiguousException e )
 156  
                 {
 157  0
                     throw new IntrospectionException( e );
 158  6
                 }
 159  
                 
 160  
                 try
 161  
                 {
 162  6
                     value = method.invoke( value, OBJECT_ARGS );
 163  
                 }
 164  0
                 catch ( IllegalArgumentException e )
 165  
                 {
 166  0
                     throw new IntrospectionException( e );
 167  
                 }
 168  0
                 catch ( IllegalAccessException e )
 169  
                 {
 170  0
                     throw new IntrospectionException( e );
 171  
                 }
 172  0
                 catch ( InvocationTargetException e )
 173  
                 {
 174  0
                     throw new IntrospectionException( e );
 175  6
                 }                
 176  
                 
 177  6
                 classMap = getClassMap( value.getClass() );
 178  
 
 179  6
                 if ( classMap.getCachedClass().isArray() )
 180  
                 {
 181  2
                     value = Arrays.asList( (Object[]) value );
 182  2
                     classMap = getClassMap( value.getClass() );
 183  
                 }
 184  
 
 185  6
                 if ( value instanceof List )
 186  
                 {
 187  
                     // use get method on List interface
 188  6
                     localParams = new Object[1];
 189  6
                     localParams[0] = Integer.valueOf( matcher.group( 2 ) );
 190  
                     try
 191  
                     {
 192  6
                         method = classMap.findMethod( "get", localParams );
 193  
                     }
 194  0
                     catch ( AmbiguousException e )
 195  
                     {
 196  0
                         throw new IntrospectionException( e );
 197  6
                     }
 198  
                 }
 199  
                 else
 200  
                 {
 201  0
                     throw new IntrospectionException( "The token '" + token
 202  
                                              + "' refers to a java.util.List or an array, but the value seems is an instance of '"
 203  
                                              + value.getClass() + "'." );
 204  
                 }
 205  6
             }
 206  
             else
 207  
             {
 208  
                 // do we have a mapped property?
 209  16
                 matcher = MAPPED_PROPS.matcher( token );
 210  16
                 if ( matcher.find() )
 211  
                 {
 212  2
                     String methodBase = StringUtils.capitalizeFirstLetter( matcher.group( 1 ) );
 213  2
                     String methodName = "get" + methodBase;
 214  
                     try
 215  
                     {
 216  2
                         method = classMap.findMethod( methodName, CLASS_ARGS );
 217  
                     }
 218  0
                     catch ( AmbiguousException e )
 219  
                     {
 220  0
                         throw new IntrospectionException( e );
 221  2
                     }
 222  
                     
 223  
                     try
 224  
                     {
 225  2
                         value = method.invoke( value, OBJECT_ARGS );
 226  
                     }
 227  0
                     catch ( IllegalArgumentException e )
 228  
                     {
 229  0
                         throw new IntrospectionException( e );
 230  
                     }
 231  0
                     catch ( IllegalAccessException e )
 232  
                     {
 233  0
                         throw new IntrospectionException( e );
 234  
                     }
 235  0
                     catch ( InvocationTargetException e )
 236  
                     {
 237  0
                         throw new IntrospectionException( e );
 238  2
                     }
 239  2
                     classMap = getClassMap( value.getClass() );
 240  
 
 241  2
                     if ( value instanceof Map )
 242  
                     {
 243  
                         // use get method on List interface
 244  2
                         localParams = new Object[1];
 245  2
                         localParams[0] = matcher.group( 2 );
 246  
                         try
 247  
                         {
 248  2
                             method = classMap.findMethod( "get", localParams );
 249  
                         }
 250  0
                         catch ( AmbiguousException e )
 251  
                         {
 252  0
                             throw new IntrospectionException( e );
 253  2
                         }
 254  
                     }
 255  
                     else
 256  
                     {
 257  0
                         throw new IntrospectionException( "The token '" + token
 258  
                                                  + "' refers to a java.util.Map, but the value seems is an instance of '"
 259  
                                                  + value.getClass() + "'." );
 260  
                     }
 261  2
                 }
 262  
                 else
 263  
                 {
 264  14
                     String methodBase = StringUtils.capitalizeFirstLetter( token );
 265  14
                     String methodName = "get" + methodBase;
 266  
                     try
 267  
                     {
 268  14
                         method = classMap.findMethod( methodName, CLASS_ARGS );
 269  
                     }
 270  0
                     catch ( AmbiguousException e )
 271  
                     {
 272  0
                         throw new IntrospectionException( e );
 273  14
                     }
 274  
 
 275  14
                     if ( method == null )
 276  
                     {
 277  
                         // perhaps this is a boolean property??
 278  2
                         methodName = "is" + methodBase;
 279  
 
 280  
                         try
 281  
                         {
 282  2
                             method = classMap.findMethod( methodName, CLASS_ARGS );
 283  
                         }
 284  0
                         catch ( AmbiguousException e )
 285  
                         {
 286  0
                             throw new IntrospectionException( e );
 287  2
                         }
 288  
                     }
 289  
                 }
 290  
             }
 291  
 
 292  22
             if ( method == null )
 293  
             {
 294  2
                 return null;
 295  
             }
 296  
 
 297  
             try
 298  
             {
 299  20
                 value = method.invoke( value, localParams );
 300  
             }
 301  1
             catch ( InvocationTargetException e )
 302  
             {
 303  
                 // catch array index issues gracefully, otherwise release
 304  1
                 if ( e.getCause() instanceof IndexOutOfBoundsException )
 305  
                 {
 306  1
                     return null;
 307  
                 }
 308  
 
 309  0
                 throw new IntrospectionException( e );
 310  
             }
 311  0
             catch ( IllegalArgumentException e )
 312  
             {
 313  0
                 throw new IntrospectionException( e );
 314  
             }
 315  0
             catch ( IllegalAccessException e )
 316  
             {
 317  0
                 throw new IntrospectionException( e );
 318  19
             }
 319  19
         }
 320  
 
 321  14
         return value;
 322  
     }
 323  
 
 324  
     private static ClassMap getClassMap( Class<?> clazz )
 325  
     {
 326  32
         ClassMap classMap = classMaps.get( clazz );
 327  
 
 328  32
         if ( classMap == null )
 329  
         {
 330  7
             classMap = new ClassMap( clazz );
 331  
 
 332  7
             classMaps.put( clazz, classMap );
 333  
         }
 334  
 
 335  32
         return classMap;
 336  
     }
 337  
 }