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 }