001 package org.apache.commons.ognl;
002
003 /*
004 * Licensed to the Apache Software Foundation (ASF) under one
005 * or more contributor license agreements. See the NOTICE file
006 * distributed with this work for additional information
007 * regarding copyright ownership. The ASF licenses this file
008 * to you under the Apache License, Version 2.0 (the
009 * "License"); you may not use this file except in compliance
010 * with the License. You may obtain a copy of the License at
011 *
012 * http://www.apache.org/licenses/LICENSE-2.0
013 *
014 * Unless required by applicable law or agreed to in writing,
015 * software distributed under the License is distributed on an
016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017 * KIND, either express or implied. See the License for the
018 * specific language governing permissions and limitations
019 * under the License.
020 */
021
022 import org.apache.commons.ognl.enhance.ExpressionCompiler;
023 import org.apache.commons.ognl.enhance.UnsupportedCompilationException;
024
025 import java.beans.IndexedPropertyDescriptor;
026 import java.beans.PropertyDescriptor;
027 import java.lang.reflect.Method;
028 import java.util.Iterator;
029
030 /**
031 * $Id: ASTProperty.java 1197445 2011-11-04 09:13:17Z mcucchiara $
032 *
033 * @author Luke Blanshard (blanshlu@netscape.net)
034 * @author Drew Davidson (drew@ognl.org)
035 */
036 public class ASTProperty
037 extends SimpleNode
038 implements NodeType
039 {
040 private boolean indexedAccess = false;
041
042 private Class getterClass;
043
044 private Class setterClass;
045
046 public ASTProperty( int id )
047 {
048 super( id );
049 }
050
051 public void setIndexedAccess( boolean value )
052 {
053 indexedAccess = value;
054 }
055
056 /**
057 * Returns true if this property is itself an index reference.
058 *
059 * @return Returns true if this property is itself an index reference.
060 */
061 public boolean isIndexedAccess()
062 {
063 return indexedAccess;
064 }
065
066 /**
067 * Returns true if this property is described by an IndexedPropertyDescriptor and that if followed by an index
068 * specifier it will call the index get/set methods rather than go through property accessors.
069 *
070 * @param context The context
071 * @param source The object source
072 * @return true, if this property is described by an IndexedPropertyDescriptor
073 * @throws OgnlException if an error occurs
074 */
075 public int getIndexedPropertyType( OgnlContext context, Object source )
076 throws OgnlException
077 {
078 Class type = context.getCurrentType();
079 Class prevType = context.getPreviousType();
080 try
081 {
082 if ( !isIndexedAccess() )
083 {
084 Object property = getProperty( context, source );
085
086 if ( property instanceof String )
087 {
088 return OgnlRuntime.getIndexedPropertyType( context, ( source == null )
089 ? null
090 : OgnlRuntime.getCompiler( context ).getInterfaceClass( source.getClass() ),
091 (String) property );
092 }
093 }
094
095 return OgnlRuntime.INDEXED_PROPERTY_NONE;
096 }
097 finally
098 {
099 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 }