View Javadoc

1   package org.apache.commons.ognl;
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 org.apache.commons.ognl.enhance.ExpressionCompiler;
23  import org.apache.commons.ognl.enhance.UnsupportedCompilationException;
24  
25  import java.beans.IndexedPropertyDescriptor;
26  import java.beans.PropertyDescriptor;
27  import java.lang.reflect.Method;
28  import java.util.Iterator;
29  
30  /**
31   * $Id: ASTProperty.java 1197445 2011-11-04 09:13:17Z mcucchiara $
32   *
33   * @author Luke Blanshard (blanshlu@netscape.net)
34   * @author Drew Davidson (drew@ognl.org)
35   */
36  public class ASTProperty
37      extends SimpleNode
38      implements NodeType
39  {
40      private boolean indexedAccess = false;
41  
42      private Class getterClass;
43  
44      private Class setterClass;
45  
46      public ASTProperty( int id )
47      {
48          super( id );
49      }
50  
51      public void setIndexedAccess( boolean value )
52      {
53          indexedAccess = value;
54      }
55  
56      /**
57       * Returns true if this property is itself an index reference.
58       *
59       * @return Returns true if this property is itself an index reference.
60       */
61      public boolean isIndexedAccess()
62      {
63          return indexedAccess;
64      }
65  
66      /**
67       * Returns true if this property is described by an IndexedPropertyDescriptor and that if followed by an index
68       * specifier it will call the index get/set methods rather than go through property accessors.
69       *
70       * @param context The context
71       * @param source  The object source
72       * @return true, if this property is described by an IndexedPropertyDescriptor
73       * @throws OgnlException if an error occurs
74       */
75      public int getIndexedPropertyType( OgnlContext context, Object source )
76          throws OgnlException
77      {
78          Class type = context.getCurrentType();
79          Class prevType = context.getPreviousType();
80          try
81          {
82              if ( !isIndexedAccess() )
83              {
84                  Object property = getProperty( context, source );
85  
86                  if ( property instanceof String )
87                  {
88                      return OgnlRuntime.getIndexedPropertyType( context, ( source == null )
89                          ? null
90                          : OgnlRuntime.getCompiler( context ).getInterfaceClass( source.getClass() ),
91                                                                 (String) property );
92                  }
93              }
94  
95              return OgnlRuntime.INDEXED_PROPERTY_NONE;
96          }
97          finally
98          {
99              context.setCurrentObject( source );
100             context.setCurrentType( type );
101             context.setPreviousType( prevType );
102         }
103     }
104 
105     public Object getProperty( OgnlContext context, Object source )
106         throws OgnlException
107     {
108         return children[0].getValue( context, context.getRoot() );
109     }
110 
111     protected Object getValueBody( OgnlContext context, Object source )
112         throws OgnlException
113     {
114         Object property = getProperty( context, source );
115 
116         Object result = OgnlRuntime.getProperty( context, source, property );
117 
118         if ( result == null )
119         {
120             result =
121                 OgnlRuntime.getNullHandler( OgnlRuntime.getTargetClass( source ) ).nullPropertyValue( context, source,
122                                                                                                       property );
123         }
124 
125         return result;
126     }
127 
128     protected void setValueBody( OgnlContext context, Object target, Object value )
129         throws OgnlException
130     {
131         OgnlRuntime.setProperty( context, target, getProperty( context, target ), value );
132     }
133 
134     public boolean isNodeSimpleProperty( OgnlContext context )
135         throws OgnlException
136     {
137         return ( children != null ) && ( children.length == 1 ) && ( (SimpleNode) children[0] ).isConstant( context );
138     }
139 
140     public Class getGetterClass()
141     {
142         return getterClass;
143     }
144 
145     public Class getSetterClass()
146     {
147         return setterClass;
148     }
149 
150     public String toGetSourceString( OgnlContext context, Object target )
151     {
152         if ( context.getCurrentObject() == null )
153         {
154             throw new UnsupportedCompilationException( "Current target is null." );
155         }
156         String result = "";
157         Method m = null;
158 
159         try
160         {
161             /*
162              * System.out.println("astproperty is indexed? : " + isIndexedAccess() + " child: " +
163              * _children[0].getClass().getName() + " target: " + target.getClass().getName() + " current object: " +
164              * context.getCurrentObject().getClass().getName());
165              */
166 
167             Node child = children[0];
168             if ( isIndexedAccess() )
169             {
170                 Object value = child.getValue( context, context.getRoot() );
171 
172                 if ( value == null || DynamicSubscript.class.isAssignableFrom( value.getClass() ) )
173                 {
174                     throw new UnsupportedCompilationException(
175                         "Value passed as indexed property was null or not supported." );
176                 }
177                 // Get root cast string if the child is a type that needs it (like a nested ASTProperty)
178 
179                 String srcString = getSourceString( context, child );
180 
181                 if ( context.get( "_indexedMethod" ) != null )
182                 {
183                     m = (Method) context.remove( "_indexedMethod" );
184                     getterClass = m.getReturnType();
185 
186                     Object indexedValue = OgnlRuntime.callMethod( context, target, m.getName(), new Object[]{ value } );
187 
188                     context.setCurrentType( getterClass );
189                     context.setCurrentObject( indexedValue );
190                     context.setCurrentAccessor(
191                         OgnlRuntime.getCompiler( context ).getSuperOrInterfaceClass( m, m.getDeclaringClass() ) );
192 
193                     return "." + m.getName() + "(" + srcString + ")";
194                 }
195                 else
196                 {
197                     PropertyAccessor propertyAccessor = OgnlRuntime.getPropertyAccessor( target.getClass() );
198 
199                     // System.out.println("child value : " + _children[0].getValue(context, context.getCurrentObject())
200                     // + " using propaccessor " + p.getClass().getName()
201                     // + " and srcString " + srcString + " on target: " + target);
202 
203                     Object currentObject = context.getCurrentObject();
204                     if ( ASTConst.class.isInstance( child ) && Number.class.isInstance( currentObject ) )
205                     {
206                         context.setCurrentType( OgnlRuntime.getPrimitiveWrapperClass( currentObject.getClass() ) );
207                     }
208                     Object indexValue = propertyAccessor.getProperty( context, target, value );
209                     result = propertyAccessor.getSourceAccessor( context, target, srcString );
210                     getterClass = context.getCurrentType();
211                     context.setCurrentObject( indexValue );
212 
213                     return result;
214                 }
215             }
216 
217             String name = ( (ASTConst) child ).getValue().toString();
218 
219             target = getTarget( context, target, name );
220 
221             PropertyDescriptor pd = OgnlRuntime.getPropertyDescriptor( context.getCurrentObject().getClass(), name );
222 
223             if ( pd != null && pd.getReadMethod() != null && !context.getMemberAccess().isAccessible( context,
224                                                                                                       context.getCurrentObject(),
225                                                                                                       pd.getReadMethod(),
226                                                                                                       name ) )
227             {
228                 throw new UnsupportedCompilationException( "Member access forbidden for property " + name + " on class "
229                                                                + context.getCurrentObject().getClass() );
230             }
231 
232             if ( this.getIndexedPropertyType( context, context.getCurrentObject() ) > 0 && pd != null )
233             {
234                 // if an indexed method accessor need to use special property descriptors to find methods
235 
236                 if ( pd instanceof IndexedPropertyDescriptor )
237                 {
238                     m = ( (IndexedPropertyDescriptor) pd ).getIndexedReadMethod();
239                 }
240                 else
241                 {
242                     if ( pd instanceof ObjectIndexedPropertyDescriptor )
243                     {
244                         m = ( (ObjectIndexedPropertyDescriptor) pd ).getIndexedReadMethod();
245                     }
246                     else
247                     {
248                         throw new OgnlException( "property '" + name + "' is not an indexed property" );
249                     }
250                 }
251 
252                 if ( parent == null )
253                 {
254                     // the above pd will be the wrong result sometimes, such as methods like getValue(int) vs String[]
255                     // getValue()
256                     m = OgnlRuntime.getReadMethod( context.getCurrentObject().getClass(), name );
257 
258                     result = m.getName() + "()";
259                     getterClass = m.getReturnType();
260                 }
261                 else
262                 {
263                     context.put( "_indexedMethod", m );
264                 }
265             }
266             else
267             {
268 
269                 /*
270                  * System.out.println("astproperty trying to get " + name + " on object target: " +
271                  * context.getCurrentObject().getClass().getName() + " current type " + context.getCurrentType() +
272                  * " current accessor " + context.getCurrentAccessor() + " prev type " + context.getPreviousType() +
273                  * " prev accessor " + context.getPreviousAccessor());
274                  */
275 
276                 PropertyAccessor pa = OgnlRuntime.getPropertyAccessor( context.getCurrentObject().getClass() );
277 
278                 if ( context.getCurrentObject().getClass().isArray() )
279                 {
280                     if ( pd == null )
281                     {
282                         pd = OgnlRuntime.getProperty( context.getCurrentObject().getClass(), name );
283 
284                         if ( pd != null && pd.getReadMethod() != null )
285                         {
286                             m = pd.getReadMethod();
287                             result = pd.getName();
288                         }
289                         else
290                         {
291                             getterClass = int.class;
292                             context.setCurrentAccessor( context.getCurrentObject().getClass() );
293                             context.setCurrentType( int.class );
294                             result = "." + name;
295                         }
296                     }
297                 }
298                 else
299                 {
300                     if ( pd != null && pd.getReadMethod() != null )
301                     {
302                         m = pd.getReadMethod();
303                         result = "." + m.getName() + "()";
304                     }
305                     else if ( pa != null )
306                     {
307                         Object currObj = context.getCurrentObject();
308                         Class currType = context.getCurrentType();
309                         Class prevType = context.getPreviousType();
310 
311                         String srcString = child.toGetSourceString( context, context.getRoot() );
312 
313                         if ( ASTConst.class.isInstance( child ) && String.class.isInstance(
314                             context.getCurrentObject() ) )
315                         {
316                             srcString = "\"" + srcString + "\"";
317                         }
318                         context.setCurrentObject( currObj );
319                         context.setCurrentType( currType );
320                         context.setPreviousType( prevType );
321 
322                         result = pa.getSourceAccessor( context, context.getCurrentObject(), srcString );
323 
324                         getterClass = context.getCurrentType();
325                     }
326                 }
327             }
328 
329         }
330         catch ( Throwable t )
331         {
332             throw OgnlOps.castToRuntime( t );
333         }
334 
335         // set known property types for NodeType interface when possible
336 
337         if ( m != null )
338         {
339             getterClass = m.getReturnType();
340 
341             context.setCurrentType( m.getReturnType() );
342             context.setCurrentAccessor(
343                 OgnlRuntime.getCompiler( context ).getSuperOrInterfaceClass( m, m.getDeclaringClass() ) );
344         }
345 
346         context.setCurrentObject( target );
347 
348         return result;
349     }
350 
351     Object getTarget( OgnlContext context, Object target, String name )
352         throws OgnlException
353     {
354         Class<?> clazz = context.getCurrentObject().getClass();
355         if ( !Iterator.class.isAssignableFrom( clazz ) || ( Iterator.class.isAssignableFrom( clazz ) && !name.contains(
356             "next" ) ) )
357         {
358             Object currObj = target;
359 
360             try
361             {
362                 target = getValue( context, context.getCurrentObject() );
363             }
364             catch ( NoSuchPropertyException e )
365             {
366                 try
367                 {
368                     target = getValue( context, context.getRoot() );
369                 }
370                 catch ( NoSuchPropertyException ex )
371                 {
372                     // ignore
373                 }
374             }
375             finally
376             {
377                 context.setCurrentObject( currObj );
378             }
379         }
380         return target;
381     }
382 
383     Method getIndexedWriteMethod( PropertyDescriptor pd )
384     {
385         if ( IndexedPropertyDescriptor.class.isInstance( pd ) )
386         {
387             return ( (IndexedPropertyDescriptor) pd ).getIndexedWriteMethod();
388         }
389         else if ( ObjectIndexedPropertyDescriptor.class.isInstance( pd ) )
390         {
391             return ( (ObjectIndexedPropertyDescriptor) pd ).getIndexedWriteMethod();
392         }
393 
394         return null;
395     }
396 
397     public String toSetSourceString( OgnlContext context, Object target )
398     {
399         String result = "";
400         Method m = null;
401 
402         if ( context.getCurrentObject() == null )
403         {
404             throw new UnsupportedCompilationException( "Current target is null." );
405         }
406         /*
407          * System.out.println("astproperty(setter) is indexed? : " + isIndexedAccess() + " child: " +
408          * _children[0].getClass().getName() + " target: " + target.getClass().getName() + " children length: " +
409          * _children.length);
410          */
411 
412         try
413         {
414 
415             Node child = children[0];
416             if ( isIndexedAccess() )
417             {
418                 Object value = child.getValue( context, context.getRoot() );
419 
420                 if ( value == null )
421                 {
422                     throw new UnsupportedCompilationException(
423                         "Value passed as indexed property is null, can't enhance statement to bytecode." );
424                 }
425 
426                 String srcString = getSourceString( context, child );
427 
428                 // System.out.println("astproperty setter using indexed value " + value + " and srcString: " +
429                 // srcString);
430 
431                 if ( context.get( "_indexedMethod" ) != null )
432                 {
433                     m = (Method) context.remove( "_indexedMethod" );
434                     PropertyDescriptor pd = (PropertyDescriptor) context.remove( "_indexedDescriptor" );
435 
436                     boolean lastChild = lastChild( context );
437                     if ( lastChild )
438                     {
439                         m = getIndexedWriteMethod( pd );
440 
441                         if ( m == null )
442                         {
443                             throw new UnsupportedCompilationException(
444                                 "Indexed property has no corresponding write method." );
445                         }
446                     }
447 
448                     setterClass = m.getParameterTypes()[0];
449 
450                     Object indexedValue = null;
451                     if ( !lastChild )
452                     {
453                         indexedValue = OgnlRuntime.callMethod( context, target, m.getName(), new Object[]{ value } );
454                     }
455                     context.setCurrentType( setterClass );
456                     context.setCurrentAccessor(
457                         OgnlRuntime.getCompiler( context ).getSuperOrInterfaceClass( m, m.getDeclaringClass() ) );
458 
459                     if ( !lastChild )
460                     {
461                         context.setCurrentObject( indexedValue );
462                         return "." + m.getName() + "(" + srcString + ")";
463                     }
464                     else
465                     {
466                         return "." + m.getName() + "(" + srcString + ", $3)";
467                     }
468                 }
469                 else
470                 {
471                     PropertyAccessor propertyAccessor = OgnlRuntime.getPropertyAccessor( target.getClass() );
472 
473                     Object currentObject = context.getCurrentObject();
474                     if ( ASTConst.class.isInstance( child ) && Number.class.isInstance( currentObject ) )
475                     {
476                         context.setCurrentType( OgnlRuntime.getPrimitiveWrapperClass( currentObject.getClass() ) );
477                     }
478                     Object indexValue = propertyAccessor.getProperty( context, target, value );
479                     result = lastChild( context )
480                         ? propertyAccessor.getSourceSetter( context, target, srcString )
481                         : propertyAccessor.getSourceAccessor( context, target, srcString );
482 
483                     /*
484                      * System.out.println("ASTProperty using propertyaccessor and isLastChild? " + lastChild(context) +
485                      * " generated source of: " + result + " using accessor class: " + p.getClass().getName());
486                      */
487 
488                     // result = p.getSourceAccessor(context, target, srcString);
489                     getterClass = context.getCurrentType();
490                     context.setCurrentObject( indexValue );
491 
492                     /*
493                      * PropertyAccessor p = OgnlRuntime.getPropertyAccessor(target.getClass()); if
494                      * (ASTConst.class.isInstance(_children[0]) && Number.class.isInstance(context.getCurrentObject()))
495                      * {
496                      * context.setCurrentType(OgnlRuntime.getPrimitiveWrapperClass(context.getCurrentObject().getClass(
497                      * ))); } result = p.getSourceSetter(context, target, srcString); context.setCurrentObject(value);
498                      * context.setCurrentType(getterClass);
499                      */
500                     return result;
501                 }
502             }
503 
504             String name = ( (ASTConst) child ).getValue().toString();
505 
506             // System.out.println(" astprop(setter) : trying to set " + name + " on object target " +
507             // context.getCurrentObject().getClass().getName());
508 
509             target = getTarget( context, target, name );
510 
511             PropertyDescriptor pd = OgnlRuntime.getPropertyDescriptor(
512                 OgnlRuntime.getCompiler( context ).getInterfaceClass( context.getCurrentObject().getClass() ), name );
513 
514             if ( pd != null )
515             {
516                 Method pdMethod = lastChild( context ) ? pd.getWriteMethod() : pd.getReadMethod();
517 
518                 if ( pdMethod != null && !context.getMemberAccess().isAccessible( context, context.getCurrentObject(),
519                                                                                   pdMethod, name ) )
520                 {
521                     throw new UnsupportedCompilationException(
522                         "Member access forbidden for property " + name + " on class "
523                             + context.getCurrentObject().getClass() );
524                 }
525             }
526 
527             if ( pd != null && this.getIndexedPropertyType( context, context.getCurrentObject() ) > 0 )
528             {
529                 // if an indexed method accessor need to use special property descriptors to find methods
530 
531                 if ( pd instanceof IndexedPropertyDescriptor )
532                 {
533                     IndexedPropertyDescriptor ipd = (IndexedPropertyDescriptor) pd;
534                     m = lastChild( context ) ? ipd.getIndexedWriteMethod() : ipd.getIndexedReadMethod();
535                 }
536                 else
537                 {
538                     if ( pd instanceof ObjectIndexedPropertyDescriptor )
539                     {
540                         ObjectIndexedPropertyDescriptor opd = (ObjectIndexedPropertyDescriptor) pd;
541 
542                         m = lastChild( context ) ? opd.getIndexedWriteMethod() : opd.getIndexedReadMethod();
543                     }
544                     else
545                     {
546                         throw new OgnlException( "property '" + name + "' is not an indexed property" );
547                     }
548                 }
549 
550                 if ( parent == null )
551                 {
552                     // the above pd will be the wrong result sometimes, such as methods like getValue(int) vs String[]
553                     // getValue()
554 
555                     m = OgnlRuntime.getWriteMethod( context.getCurrentObject().getClass(), name );
556                     Class parm = m.getParameterTypes()[0];
557                     String cast = parm.isArray() ? ExpressionCompiler.getCastString( parm ) : parm.getName();
558 
559                     result = m.getName() + "((" + cast + ")$3)";
560                     setterClass = parm;
561                 }
562                 else
563                 {
564                     context.put( "_indexedMethod", m );
565                     context.put( "_indexedDescriptor", pd );
566                 }
567 
568             }
569             else
570             {
571                 PropertyAccessor pa = OgnlRuntime.getPropertyAccessor( context.getCurrentObject().getClass() );
572 
573                 /*
574                  * System.out.println("astproperty trying to set " + name + " on object target: " +
575                  * context.getCurrentObject().getClass().getName() + " using propertyaccessor type: " + pa);
576                  */
577 
578                 if ( target != null )
579                 {
580                     setterClass = target.getClass();
581                 }
582                 if ( parent != null && pd != null && pa == null )
583                 {
584                     m = pd.getReadMethod();
585                     result = m.getName() + "()";
586                 }
587                 else
588                 {
589                     if ( context.getCurrentObject().getClass().isArray() )
590                     {
591                         result = "";
592                     }
593                     else if ( pa != null )
594                     {
595                         Object currObj = context.getCurrentObject();
596                         // Class currType = context.getCurrentType();
597                         // Class prevType = context.getPreviousType();
598 
599                         String srcString = child.toGetSourceString( context, context.getRoot() );
600 
601                         if ( ASTConst.class.isInstance( child ) && String.class.isInstance(
602                             context.getCurrentObject() ) )
603                         {
604                             srcString = "\"" + srcString + "\"";
605                         }
606 
607                         context.setCurrentObject( currObj );
608                         // context.setCurrentType(currType);
609                         // context.setPreviousType(prevType);
610 
611                         if ( !lastChild( context ) )
612                         {
613                             result = pa.getSourceAccessor( context, context.getCurrentObject(), srcString );
614                         }
615                         else
616                         {
617                             result = pa.getSourceSetter( context, context.getCurrentObject(), srcString );
618                         }
619 
620                         getterClass = context.getCurrentType();
621                     }
622                 }
623             }
624 
625         }
626         catch ( Throwable t )
627         {
628             throw OgnlOps.castToRuntime( t );
629         }
630 
631         context.setCurrentObject( target );
632 
633         if ( m != null )
634         {
635             context.setCurrentType( m.getReturnType() );
636             context.setCurrentAccessor(
637                 OgnlRuntime.getCompiler( context ).getSuperOrInterfaceClass( m, m.getDeclaringClass() ) );
638         }
639 
640         return result;
641     }
642 
643     public <R, P> R accept( NodeVisitor<? extends R, ? super P> visitor, P data )
644         throws OgnlException
645     {
646         return visitor.visit( this, data );
647     }
648 
649     private static String getSourceString( OgnlContext context, Node child )
650     {
651         String srcString = child.toGetSourceString( context, context.getRoot() );
652         srcString = ExpressionCompiler.getRootExpression( child, context.getRoot(), context ) + srcString;
653 
654         if ( ASTChain.class.isInstance( child ) )
655         {
656             String cast = (String) context.remove( ExpressionCompiler.PRE_CAST );
657             if ( cast != null )
658             {
659                 srcString = cast + srcString;
660             }
661         }
662 
663         if ( ASTConst.class.isInstance( child ) && String.class.isInstance( context.getCurrentObject() ) )
664         {
665             srcString = "\"" + srcString + "\"";
666         }
667         // System.out.println("indexed getting with child srcString: " + srcString + " value class: " +
668         // value.getClass() + " and child: " + _children[0].getClass());
669         return srcString;
670     }
671 
672 }