View Javadoc
1   package org.apache.maven.plugin.coreit;
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.Array;
23  import java.lang.reflect.Field;
24  import java.lang.reflect.Method;
25  import java.util.Arrays;
26  import java.util.Collection;
27  import java.util.Collections;
28  import java.util.Iterator;
29  import java.util.LinkedHashMap;
30  import java.util.List;
31  import java.util.Map;
32  
33  /**
34   * Assists in evaluating expressions.
35   *
36   * @author Benjamin Bentmann
37   *
38   */
39  class ExpressionUtil
40  {
41  
42      private static final Object[] NO_ARGS = {};
43  
44      private static final Class[] NO_PARAMS = {};
45  
46      private static final Class[] OBJECT_PARAM = { Object.class };
47  
48      private static final Class[] STRING_PARAM = { String.class };
49  
50      /**
51       * Evaluates the specified expression. Expressions are composed of segments which are separated by a forward slash
52       * ('/'). Each segment specifies a (public) bean property of the current object and drives the evaluation further
53       * down the object graph. For lists, arrays and maps segments can additionally specify the index/key of an element.
54       * The initial segment denotes the root object and the parameter <code>contexts</code> is used to specify which root
55       * objects are available. For instance, if <code>contexts</code> maps the token "project" to a Maven project
56       * instance, the expression "project/build/resources/0/directory" specifies the first resource directory of the
57       * project.
58       *
59       * @param expression The expression to evaluate, may be <code>null</code>.
60       * @param context    The object to start expression evaluation at, must not be <code>null</code>.
61       * @return The values of the evaluation, indexed by expression, or an empty map if the segments could not be
62       * evaluated.
63       */
64      public static Map evaluate( String expression, Object context )
65      {
66          Map values = Collections.EMPTY_MAP;
67  
68          if ( expression != null && expression.length() > 0 )
69          {
70              List segments = Arrays.asList( expression.split( "/", 0 ) );
71              values = evaluate( "", segments, context );
72          }
73  
74          return values;
75      }
76  
77      /**
78       * Evaluates the given expression segments against the specified object.
79       *
80       * @param prefix   The expression prefix that led to the current context, must not be <code>null</code>.
81       * @param segments The expression segments to evaluate, must not be <code>null</code>.
82       * @param context  The object to evaluate the segments against, may be <code>null</code>.
83       * @return The values of the evaluation, indexed by expression, or an empty map if the segments could not be
84       * evaluated.
85       */
86      private static Map evaluate( String prefix, List segments, Object context )
87      {
88          Map values = Collections.EMPTY_MAP;
89  
90          if ( segments.isEmpty() )
91          {
92              values = Collections.singletonMap( prefix, context );
93          }
94          else if ( context != null )
95          {
96              Map targets = Collections.EMPTY_MAP;
97              String segment = (String) segments.get( 0 );
98              if ( context.getClass().isArray() && Character.isDigit( segment.charAt( 0 ) ) )
99              {
100                 try
101                 {
102                     int index = Integer.parseInt( segment );
103                     targets = Collections.singletonMap( segment, Array.get( context, index ) );
104                 }
105                 catch ( RuntimeException e )
106                 {
107                     // invalid index, just ignore
108                 }
109             }
110             else if ( ( context instanceof List ) && Character.isDigit( segment.charAt( 0 ) ) )
111             {
112                 try
113                 {
114                     int index = Integer.parseInt( segment );
115                     targets = Collections.singletonMap( segment, ( (List) context ).get( index ) );
116                 }
117                 catch ( RuntimeException e )
118                 {
119                     // invalid index, just ignore
120                 }
121             }
122             else if ( ( context instanceof Collection ) && "*".equals( segment ) )
123             {
124                 targets = new LinkedHashMap();
125                 int index = 0;
126                 for ( Iterator it = ( (Collection) context ).iterator(); it.hasNext(); index++ )
127                 {
128                     targets.put( Integer.toString( index ), it.next() );
129                 }
130             }
131             else if ( context.getClass().isArray() && "*".equals( segment ) )
132             {
133                 targets = new LinkedHashMap();
134                 for ( int index = 0, n = Array.getLength( context ); index < n; index++ )
135                 {
136                     targets.put( Integer.toString( index ), Array.get( context, index ) );
137                 }
138             }
139             else
140             {
141                 targets = Collections.singletonMap( segment, getProperty( context, segment ) );
142             }
143 
144             values = new LinkedHashMap();
145             for ( Object key : targets.keySet() )
146             {
147                 Object target = targets.get( key );
148                 values.putAll(
149                     evaluate( concat( prefix, String.valueOf( key ) ), segments.subList( 1, segments.size() ),
150                               target ) );
151             }
152         }
153 
154         return values;
155     }
156 
157     private static String concat( String prefix, String segment )
158     {
159         return ( prefix == null || prefix.length() <= 0 ) ? segment : ( prefix + '/' + segment );
160     }
161 
162     /**
163      * Gets the value of a (public) bean property from the specified object.
164      *
165      * @param context  The object whose bean property should be retrieved, must not be <code>null</code>.
166      * @param property The name of the bean property, must not be <code>null</code>.
167      * @return The value of the bean property or <code>null</code> if the property does not exist.
168      */
169     static Object getProperty( Object context, String property )
170     {
171         Object value;
172 
173         Class type = context.getClass();
174         if ( context instanceof Collection )
175         {
176             type = Collection.class;
177         }
178         else if ( context instanceof Map )
179         {
180             type = Map.class;
181         }
182 
183         try
184         {
185             try
186             {
187                 Method method = type.getMethod( property, NO_PARAMS );
188                 method.setAccessible( true );
189                 value = method.invoke( context, NO_ARGS );
190             }
191             catch ( NoSuchMethodException e )
192             {
193                 try
194                 {
195                     String name = "get" + Character.toUpperCase( property.charAt( 0 ) ) + property.substring( 1 );
196                     Method method = type.getMethod( name, NO_PARAMS );
197                     method.setAccessible( true );
198                     value = method.invoke( context, NO_ARGS );
199                 }
200                 catch ( NoSuchMethodException e1 )
201                 {
202                     try
203                     {
204                         String name = "is" + Character.toUpperCase( property.charAt( 0 ) ) + property.substring( 1 );
205                         Method method = type.getMethod( name, NO_PARAMS );
206                         method.setAccessible( true );
207                         value = method.invoke( context, NO_ARGS );
208                     }
209                     catch ( NoSuchMethodException e2 )
210                     {
211                         try
212                         {
213                             Method method;
214                             try
215                             {
216                                 method = type.getMethod( "get", STRING_PARAM );
217                             }
218                             catch ( NoSuchMethodException e3 )
219                             {
220                                 method = type.getMethod( "get", OBJECT_PARAM );
221                             }
222                             method.setAccessible( true );
223                             value = method.invoke( context, new Object[]{ property } );
224                         }
225                         catch ( NoSuchMethodException e3 )
226                         {
227                             try
228                             {
229                                 Field field = type.getField( property );
230                                 field.setAccessible( true );
231                                 value = field.get( context );
232                             }
233                             catch ( NoSuchFieldException e4 )
234                             {
235                                 if ( "length".equals( property ) && type.isArray() )
236                                 {
237                                     value = Array.getLength( context );
238                                 }
239                                 else
240                                 {
241                                     throw e4;
242                                 }
243                             }
244                         }
245                     }
246                 }
247             }
248         }
249         catch ( Exception e )
250         {
251             value = null;
252         }
253         return value;
254     }
255 
256 }