001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.expression.spel.support; 018 019import java.lang.reflect.Array; 020import java.lang.reflect.Field; 021import java.lang.reflect.Member; 022import java.lang.reflect.Method; 023import java.lang.reflect.Modifier; 024import java.util.Arrays; 025import java.util.Collections; 026import java.util.Comparator; 027import java.util.HashSet; 028import java.util.Map; 029import java.util.Set; 030import java.util.concurrent.ConcurrentHashMap; 031 032import org.springframework.asm.MethodVisitor; 033import org.springframework.core.MethodParameter; 034import org.springframework.core.convert.Property; 035import org.springframework.core.convert.TypeDescriptor; 036import org.springframework.expression.AccessException; 037import org.springframework.expression.EvaluationContext; 038import org.springframework.expression.EvaluationException; 039import org.springframework.expression.PropertyAccessor; 040import org.springframework.expression.TypedValue; 041import org.springframework.expression.spel.CodeFlow; 042import org.springframework.expression.spel.CompilablePropertyAccessor; 043import org.springframework.util.ReflectionUtils; 044import org.springframework.util.StringUtils; 045 046/** 047 * A powerful {@link PropertyAccessor} that uses reflection to access properties 048 * for reading and possibly also for writing on a target instance. 049 * 050 * <p>A property can be referenced through a public getter method (when being read) 051 * or a public setter method (when being written), and also as a public field. 052 * 053 * @author Andy Clement 054 * @author Juergen Hoeller 055 * @author Phillip Webb 056 * @since 3.0 057 * @see StandardEvaluationContext 058 * @see SimpleEvaluationContext 059 * @see DataBindingPropertyAccessor 060 */ 061public class ReflectivePropertyAccessor implements PropertyAccessor { 062 063 private static final Set<Class<?>> ANY_TYPES = Collections.emptySet(); 064 065 private static final Set<Class<?>> BOOLEAN_TYPES; 066 067 static { 068 Set<Class<?>> booleanTypes = new HashSet<Class<?>>(4); 069 booleanTypes.add(Boolean.class); 070 booleanTypes.add(Boolean.TYPE); 071 BOOLEAN_TYPES = Collections.unmodifiableSet(booleanTypes); 072 } 073 074 075 private final boolean allowWrite; 076 077 private final Map<PropertyCacheKey, InvokerPair> readerCache = 078 new ConcurrentHashMap<PropertyCacheKey, InvokerPair>(64); 079 080 private final Map<PropertyCacheKey, Member> writerCache = 081 new ConcurrentHashMap<PropertyCacheKey, Member>(64); 082 083 private final Map<PropertyCacheKey, TypeDescriptor> typeDescriptorCache = 084 new ConcurrentHashMap<PropertyCacheKey, TypeDescriptor>(64); 085 086 private final Map<Class<?>, Method[]> sortedMethodsCache = 087 new ConcurrentHashMap<Class<?>, Method[]>(64); 088 089 private volatile InvokerPair lastReadInvokerPair; 090 091 092 /** 093 * Create a new property accessor for reading as well writing. 094 * @see #ReflectivePropertyAccessor(boolean) 095 */ 096 public ReflectivePropertyAccessor() { 097 this.allowWrite = true; 098 } 099 100 /** 101 * Create a new property accessor for reading and possibly also writing. 102 * @param allowWrite whether to allow write operations on a target instance 103 * @since 4.3.15 104 * @see #canWrite 105 */ 106 public ReflectivePropertyAccessor(boolean allowWrite) { 107 this.allowWrite = allowWrite; 108 } 109 110 111 /** 112 * Returns {@code null} which means this is a general purpose accessor. 113 */ 114 @Override 115 public Class<?>[] getSpecificTargetClasses() { 116 return null; 117 } 118 119 @Override 120 public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { 121 if (target == null) { 122 return false; 123 } 124 125 Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass()); 126 if (type.isArray() && name.equals("length")) { 127 return true; 128 } 129 130 PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class); 131 if (this.readerCache.containsKey(cacheKey)) { 132 return true; 133 } 134 135 Method method = findGetterForProperty(name, type, target); 136 if (method != null) { 137 // Treat it like a property... 138 // The readerCache will only contain gettable properties (let's not worry about setters for now). 139 Property property = new Property(type, method, null); 140 TypeDescriptor typeDescriptor = new TypeDescriptor(property); 141 this.readerCache.put(cacheKey, new InvokerPair(method, typeDescriptor)); 142 this.typeDescriptorCache.put(cacheKey, typeDescriptor); 143 return true; 144 } 145 else { 146 Field field = findField(name, type, target); 147 if (field != null) { 148 TypeDescriptor typeDescriptor = new TypeDescriptor(field); 149 this.readerCache.put(cacheKey, new InvokerPair(field, typeDescriptor)); 150 this.typeDescriptorCache.put(cacheKey, typeDescriptor); 151 return true; 152 } 153 } 154 155 return false; 156 } 157 158 @Override 159 public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { 160 if (target == null) { 161 throw new AccessException("Cannot read property of null target"); 162 } 163 Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass()); 164 165 if (type.isArray() && name.equals("length")) { 166 if (target instanceof Class) { 167 throw new AccessException("Cannot access length on array class itself"); 168 } 169 return new TypedValue(Array.getLength(target)); 170 } 171 172 PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class); 173 InvokerPair invoker = this.readerCache.get(cacheKey); 174 this.lastReadInvokerPair = invoker; 175 176 if (invoker == null || invoker.member instanceof Method) { 177 Method method = (Method) (invoker != null ? invoker.member : null); 178 if (method == null) { 179 method = findGetterForProperty(name, type, target); 180 if (method != null) { 181 // Treat it like a property... 182 // The readerCache will only contain gettable properties (let's not worry about setters for now). 183 Property property = new Property(type, method, null); 184 TypeDescriptor typeDescriptor = new TypeDescriptor(property); 185 invoker = new InvokerPair(method, typeDescriptor); 186 this.lastReadInvokerPair = invoker; 187 this.readerCache.put(cacheKey, invoker); 188 } 189 } 190 if (method != null) { 191 try { 192 ReflectionUtils.makeAccessible(method); 193 Object value = method.invoke(target); 194 return new TypedValue(value, invoker.typeDescriptor.narrow(value)); 195 } 196 catch (Exception ex) { 197 throw new AccessException("Unable to access property '" + name + "' through getter method", ex); 198 } 199 } 200 } 201 202 if (invoker == null || invoker.member instanceof Field) { 203 Field field = (Field) (invoker == null ? null : invoker.member); 204 if (field == null) { 205 field = findField(name, type, target); 206 if (field != null) { 207 invoker = new InvokerPair(field, new TypeDescriptor(field)); 208 this.lastReadInvokerPair = invoker; 209 this.readerCache.put(cacheKey, invoker); 210 } 211 } 212 if (field != null) { 213 try { 214 ReflectionUtils.makeAccessible(field); 215 Object value = field.get(target); 216 return new TypedValue(value, invoker.typeDescriptor.narrow(value)); 217 } 218 catch (Exception ex) { 219 throw new AccessException("Unable to access field '" + name + "'", ex); 220 } 221 } 222 } 223 224 throw new AccessException("Neither getter method nor field found for property '" + name + "'"); 225 } 226 227 @Override 228 public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException { 229 if (!this.allowWrite || target == null) { 230 return false; 231 } 232 233 Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass()); 234 PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class); 235 if (this.writerCache.containsKey(cacheKey)) { 236 return true; 237 } 238 239 Method method = findSetterForProperty(name, type, target); 240 if (method != null) { 241 // Treat it like a property 242 Property property = new Property(type, null, method); 243 TypeDescriptor typeDescriptor = new TypeDescriptor(property); 244 this.writerCache.put(cacheKey, method); 245 this.typeDescriptorCache.put(cacheKey, typeDescriptor); 246 return true; 247 } 248 else { 249 Field field = findField(name, type, target); 250 if (field != null) { 251 this.writerCache.put(cacheKey, field); 252 this.typeDescriptorCache.put(cacheKey, new TypeDescriptor(field)); 253 return true; 254 } 255 } 256 257 return false; 258 } 259 260 @Override 261 public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException { 262 if (!this.allowWrite) { 263 throw new AccessException("PropertyAccessor for property '" + name + 264 "' on target [" + target + "] does not allow write operations"); 265 } 266 267 if (target == null) { 268 throw new AccessException("Cannot write property on null target"); 269 } 270 Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass()); 271 272 Object possiblyConvertedNewValue = newValue; 273 TypeDescriptor typeDescriptor = getTypeDescriptor(context, target, name); 274 if (typeDescriptor != null) { 275 try { 276 possiblyConvertedNewValue = context.getTypeConverter().convertValue( 277 newValue, TypeDescriptor.forObject(newValue), typeDescriptor); 278 } 279 catch (EvaluationException evaluationException) { 280 throw new AccessException("Type conversion failure", evaluationException); 281 } 282 } 283 284 PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class); 285 Member cachedMember = this.writerCache.get(cacheKey); 286 287 if (cachedMember == null || cachedMember instanceof Method) { 288 Method method = (Method) cachedMember; 289 if (method == null) { 290 method = findSetterForProperty(name, type, target); 291 if (method != null) { 292 cachedMember = method; 293 this.writerCache.put(cacheKey, cachedMember); 294 } 295 } 296 if (method != null) { 297 try { 298 ReflectionUtils.makeAccessible(method); 299 method.invoke(target, possiblyConvertedNewValue); 300 return; 301 } 302 catch (Exception ex) { 303 throw new AccessException("Unable to access property '" + name + "' through setter method", ex); 304 } 305 } 306 } 307 308 if (cachedMember == null || cachedMember instanceof Field) { 309 Field field = (Field) cachedMember; 310 if (field == null) { 311 field = findField(name, type, target); 312 if (field != null) { 313 cachedMember = field; 314 this.writerCache.put(cacheKey, cachedMember); 315 } 316 } 317 if (field != null) { 318 try { 319 ReflectionUtils.makeAccessible(field); 320 field.set(target, possiblyConvertedNewValue); 321 return; 322 } 323 catch (Exception ex) { 324 throw new AccessException("Unable to access field '" + name + "'", ex); 325 } 326 } 327 } 328 329 throw new AccessException("Neither setter method nor field found for property '" + name + "'"); 330 } 331 332 /** 333 * Get the last read invoker pair. 334 * @deprecated as of 4.3.15 since it is not used within the framework anymore 335 */ 336 @Deprecated 337 public Member getLastReadInvokerPair() { 338 InvokerPair lastReadInvoker = this.lastReadInvokerPair; 339 return (lastReadInvoker != null ? lastReadInvoker.member : null); 340 } 341 342 343 private TypeDescriptor getTypeDescriptor(EvaluationContext context, Object target, String name) { 344 if (target == null) { 345 return null; 346 } 347 Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass()); 348 349 if (type.isArray() && name.equals("length")) { 350 return TypeDescriptor.valueOf(Integer.TYPE); 351 } 352 PropertyCacheKey cacheKey = new PropertyCacheKey(type, name, target instanceof Class); 353 TypeDescriptor typeDescriptor = this.typeDescriptorCache.get(cacheKey); 354 if (typeDescriptor == null) { 355 // Attempt to populate the cache entry 356 try { 357 if (canRead(context, target, name) || canWrite(context, target, name)) { 358 typeDescriptor = this.typeDescriptorCache.get(cacheKey); 359 } 360 } 361 catch (AccessException ex) { 362 // Continue with null type descriptor 363 } 364 } 365 return typeDescriptor; 366 } 367 368 private Method findGetterForProperty(String propertyName, Class<?> clazz, Object target) { 369 Method method = findGetterForProperty(propertyName, clazz, target instanceof Class); 370 if (method == null && target instanceof Class) { 371 method = findGetterForProperty(propertyName, target.getClass(), false); 372 } 373 return method; 374 } 375 376 private Method findSetterForProperty(String propertyName, Class<?> clazz, Object target) { 377 Method method = findSetterForProperty(propertyName, clazz, target instanceof Class); 378 if (method == null && target instanceof Class) { 379 method = findSetterForProperty(propertyName, target.getClass(), false); 380 } 381 return method; 382 } 383 384 /** 385 * Find a getter method for the specified property. 386 */ 387 protected Method findGetterForProperty(String propertyName, Class<?> clazz, boolean mustBeStatic) { 388 Method method = findMethodForProperty(getPropertyMethodSuffixes(propertyName), 389 "get", clazz, mustBeStatic, 0, ANY_TYPES); 390 if (method == null) { 391 method = findMethodForProperty(getPropertyMethodSuffixes(propertyName), 392 "is", clazz, mustBeStatic, 0, BOOLEAN_TYPES); 393 } 394 return method; 395 } 396 397 /** 398 * Find a setter method for the specified property. 399 */ 400 protected Method findSetterForProperty(String propertyName, Class<?> clazz, boolean mustBeStatic) { 401 return findMethodForProperty(getPropertyMethodSuffixes(propertyName), 402 "set", clazz, mustBeStatic, 1, ANY_TYPES); 403 } 404 405 private Method findMethodForProperty(String[] methodSuffixes, String prefix, Class<?> clazz, 406 boolean mustBeStatic, int numberOfParams, Set<Class<?>> requiredReturnTypes) { 407 408 Method[] methods = getSortedMethods(clazz); 409 for (String methodSuffix : methodSuffixes) { 410 for (Method method : methods) { 411 if (isCandidateForProperty(method, clazz) && method.getName().equals(prefix + methodSuffix) && 412 method.getParameterTypes().length == numberOfParams && 413 (!mustBeStatic || Modifier.isStatic(method.getModifiers())) && 414 (requiredReturnTypes.isEmpty() || requiredReturnTypes.contains(method.getReturnType()))) { 415 return method; 416 } 417 } 418 } 419 return null; 420 } 421 422 /** 423 * Return class methods ordered with non-bridge methods appearing higher. 424 */ 425 private Method[] getSortedMethods(Class<?> clazz) { 426 Method[] methods = this.sortedMethodsCache.get(clazz); 427 if (methods == null) { 428 methods = clazz.getMethods(); 429 Arrays.sort(methods, new Comparator<Method>() { 430 @Override 431 public int compare(Method o1, Method o2) { 432 return (o1.isBridge() == o2.isBridge()) ? 0 : (o1.isBridge() ? 1 : -1); 433 } 434 }); 435 this.sortedMethodsCache.put(clazz, methods); 436 } 437 return methods; 438 } 439 440 /** 441 * Determine whether the given {@code Method} is a candidate for property access 442 * on an instance of the given target class. 443 * <p>The default implementation considers any method as a candidate, even for 444 * non-user-declared properties on the {@link Object} base class. 445 * @param method the Method to evaluate 446 * @param targetClass the concrete target class that is being introspected 447 * @since 4.3.15 448 */ 449 protected boolean isCandidateForProperty(Method method, Class<?> targetClass) { 450 return true; 451 } 452 453 /** 454 * Return the method suffixes for a given property name. The default implementation 455 * uses JavaBean conventions with additional support for properties of the form 'xY' 456 * where the method 'getXY()' is used in preference to the JavaBean convention of 457 * 'getxY()'. 458 */ 459 protected String[] getPropertyMethodSuffixes(String propertyName) { 460 String suffix = getPropertyMethodSuffix(propertyName); 461 if (suffix.length() > 0 && Character.isUpperCase(suffix.charAt(0))) { 462 return new String[] {suffix}; 463 } 464 return new String[] {suffix, StringUtils.capitalize(suffix)}; 465 } 466 467 /** 468 * Return the method suffix for a given property name. The default implementation 469 * uses JavaBean conventions. 470 */ 471 protected String getPropertyMethodSuffix(String propertyName) { 472 if (propertyName.length() > 1 && Character.isUpperCase(propertyName.charAt(1))) { 473 return propertyName; 474 } 475 return StringUtils.capitalize(propertyName); 476 } 477 478 private Field findField(String name, Class<?> clazz, Object target) { 479 Field field = findField(name, clazz, target instanceof Class); 480 if (field == null && target instanceof Class) { 481 field = findField(name, target.getClass(), false); 482 } 483 return field; 484 } 485 486 /** 487 * Find a field of a certain name on a specified class. 488 */ 489 protected Field findField(String name, Class<?> clazz, boolean mustBeStatic) { 490 Field[] fields = clazz.getFields(); 491 for (Field field : fields) { 492 if (field.getName().equals(name) && (!mustBeStatic || Modifier.isStatic(field.getModifiers()))) { 493 return field; 494 } 495 } 496 // We'll search superclasses and implemented interfaces explicitly, 497 // although it shouldn't be necessary - however, see SPR-10125. 498 if (clazz.getSuperclass() != null) { 499 Field field = findField(name, clazz.getSuperclass(), mustBeStatic); 500 if (field != null) { 501 return field; 502 } 503 } 504 for (Class<?> implementedInterface : clazz.getInterfaces()) { 505 Field field = findField(name, implementedInterface, mustBeStatic); 506 if (field != null) { 507 return field; 508 } 509 } 510 return null; 511 } 512 513 /** 514 * Attempt to create an optimized property accessor tailored for a property of a 515 * particular name on a particular class. The general ReflectivePropertyAccessor 516 * will always work but is not optimal due to the need to lookup which reflective 517 * member (method/field) to use each time read() is called. This method will just 518 * return the ReflectivePropertyAccessor instance if it is unable to build a more 519 * optimal accessor. 520 * <p>Note: An optimal accessor is currently only usable for read attempts. 521 * Do not call this method if you need a read-write accessor. 522 * @see OptimalPropertyAccessor 523 */ 524 public PropertyAccessor createOptimalAccessor(EvaluationContext context, Object target, String name) { 525 // Don't be clever for arrays or a null target... 526 if (target == null) { 527 return this; 528 } 529 Class<?> clazz = (target instanceof Class ? (Class<?>) target : target.getClass()); 530 if (clazz.isArray()) { 531 return this; 532 } 533 534 PropertyCacheKey cacheKey = new PropertyCacheKey(clazz, name, target instanceof Class); 535 InvokerPair invocationTarget = this.readerCache.get(cacheKey); 536 537 if (invocationTarget == null || invocationTarget.member instanceof Method) { 538 Method method = (Method) (invocationTarget != null ? invocationTarget.member : null); 539 if (method == null) { 540 method = findGetterForProperty(name, clazz, target); 541 if (method != null) { 542 invocationTarget = new InvokerPair(method, new TypeDescriptor(new MethodParameter(method, -1))); 543 ReflectionUtils.makeAccessible(method); 544 this.readerCache.put(cacheKey, invocationTarget); 545 } 546 } 547 if (method != null) { 548 return new OptimalPropertyAccessor(invocationTarget); 549 } 550 } 551 552 if (invocationTarget == null || invocationTarget.member instanceof Field) { 553 Field field = (invocationTarget != null ? (Field) invocationTarget.member : null); 554 if (field == null) { 555 field = findField(name, clazz, target instanceof Class); 556 if (field != null) { 557 invocationTarget = new InvokerPair(field, new TypeDescriptor(field)); 558 ReflectionUtils.makeAccessible(field); 559 this.readerCache.put(cacheKey, invocationTarget); 560 } 561 } 562 if (field != null) { 563 return new OptimalPropertyAccessor(invocationTarget); 564 } 565 } 566 567 return this; 568 } 569 570 571 /** 572 * Captures the member (method/field) to call reflectively to access a property value 573 * and the type descriptor for the value returned by the reflective call. 574 */ 575 private static class InvokerPair { 576 577 final Member member; 578 579 final TypeDescriptor typeDescriptor; 580 581 public InvokerPair(Member member, TypeDescriptor typeDescriptor) { 582 this.member = member; 583 this.typeDescriptor = typeDescriptor; 584 } 585 } 586 587 588 private static final class PropertyCacheKey implements Comparable<PropertyCacheKey> { 589 590 private final Class<?> clazz; 591 592 private final String property; 593 594 private boolean targetIsClass; 595 596 public PropertyCacheKey(Class<?> clazz, String name, boolean targetIsClass) { 597 this.clazz = clazz; 598 this.property = name; 599 this.targetIsClass = targetIsClass; 600 } 601 602 @Override 603 public boolean equals(Object other) { 604 if (this == other) { 605 return true; 606 } 607 if (!(other instanceof PropertyCacheKey)) { 608 return false; 609 } 610 PropertyCacheKey otherKey = (PropertyCacheKey) other; 611 return (this.clazz == otherKey.clazz && this.property.equals(otherKey.property) && 612 this.targetIsClass == otherKey.targetIsClass); 613 } 614 615 @Override 616 public int hashCode() { 617 return (this.clazz.hashCode() * 29 + this.property.hashCode()); 618 } 619 620 @Override 621 public String toString() { 622 return "PropertyCacheKey [clazz=" + this.clazz.getName() + ", property=" + this.property + 623 ", targetIsClass=" + this.targetIsClass + "]"; 624 } 625 626 @Override 627 public int compareTo(PropertyCacheKey other) { 628 int result = this.clazz.getName().compareTo(other.clazz.getName()); 629 if (result == 0) { 630 result = this.property.compareTo(other.property); 631 } 632 return result; 633 } 634 } 635 636 637 /** 638 * An optimized form of a PropertyAccessor that will use reflection but only knows 639 * how to access a particular property on a particular class. This is unlike the 640 * general ReflectivePropertyResolver which manages a cache of methods/fields that 641 * may be invoked to access different properties on different classes. This optimal 642 * accessor exists because looking up the appropriate reflective object by class/name 643 * on each read is not cheap. 644 */ 645 public static class OptimalPropertyAccessor implements CompilablePropertyAccessor { 646 647 /** 648 * The member being accessed. 649 */ 650 public final Member member; 651 652 private final TypeDescriptor typeDescriptor; 653 654 private final boolean needsToBeMadeAccessible; 655 656 OptimalPropertyAccessor(InvokerPair target) { 657 this.member = target.member; 658 this.typeDescriptor = target.typeDescriptor; 659 this.needsToBeMadeAccessible = (!Modifier.isPublic(this.member.getModifiers()) || 660 !Modifier.isPublic(this.member.getDeclaringClass().getModifiers())); 661 } 662 663 @Override 664 public Class<?>[] getSpecificTargetClasses() { 665 throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor"); 666 } 667 668 @Override 669 public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException { 670 if (target == null) { 671 return false; 672 } 673 Class<?> type = (target instanceof Class ? (Class<?>) target : target.getClass()); 674 if (type.isArray()) { 675 return false; 676 } 677 678 if (this.member instanceof Method) { 679 Method method = (Method) this.member; 680 String getterName = "get" + StringUtils.capitalize(name); 681 if (getterName.equals(method.getName())) { 682 return true; 683 } 684 getterName = "is" + StringUtils.capitalize(name); 685 return getterName.equals(method.getName()); 686 } 687 else { 688 Field field = (Field) this.member; 689 return field.getName().equals(name); 690 } 691 } 692 693 @Override 694 public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException { 695 if (this.member instanceof Method) { 696 Method method = (Method) this.member; 697 try { 698 if (this.needsToBeMadeAccessible && !method.isAccessible()) { 699 method.setAccessible(true); 700 } 701 Object value = method.invoke(target); 702 return new TypedValue(value, this.typeDescriptor.narrow(value)); 703 } 704 catch (Exception ex) { 705 throw new AccessException("Unable to access property '" + name + "' through getter method", ex); 706 } 707 } 708 else { 709 Field field = (Field) this.member; 710 try { 711 if (this.needsToBeMadeAccessible && !field.isAccessible()) { 712 field.setAccessible(true); 713 } 714 Object value = field.get(target); 715 return new TypedValue(value, this.typeDescriptor.narrow(value)); 716 } 717 catch (Exception ex) { 718 throw new AccessException("Unable to access field '" + name + "'", ex); 719 } 720 } 721 } 722 723 @Override 724 public boolean canWrite(EvaluationContext context, Object target, String name) { 725 throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor"); 726 } 727 728 @Override 729 public void write(EvaluationContext context, Object target, String name, Object newValue) { 730 throw new UnsupportedOperationException("Should not be called on an OptimalPropertyAccessor"); 731 } 732 733 @Override 734 public boolean isCompilable() { 735 return (Modifier.isPublic(this.member.getModifiers()) && 736 Modifier.isPublic(this.member.getDeclaringClass().getModifiers())); 737 } 738 739 @Override 740 public Class<?> getPropertyType() { 741 if (this.member instanceof Method) { 742 return ((Method) this.member).getReturnType(); 743 } 744 else { 745 return ((Field) this.member).getType(); 746 } 747 } 748 749 @Override 750 public void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf) { 751 boolean isStatic = Modifier.isStatic(this.member.getModifiers()); 752 String descriptor = cf.lastDescriptor(); 753 String classDesc = this.member.getDeclaringClass().getName().replace('.', '/'); 754 755 if (!isStatic) { 756 if (descriptor == null) { 757 cf.loadTarget(mv); 758 } 759 if (descriptor == null || !classDesc.equals(descriptor.substring(1))) { 760 mv.visitTypeInsn(CHECKCAST, classDesc); 761 } 762 } 763 else { 764 if (descriptor != null) { 765 // A static field/method call will not consume what is on the stack, 766 // it needs to be popped off. 767 mv.visitInsn(POP); 768 } 769 } 770 771 if (this.member instanceof Method) { 772 mv.visitMethodInsn((isStatic ? INVOKESTATIC : INVOKEVIRTUAL), classDesc, this.member.getName(), 773 CodeFlow.createSignatureDescriptor((Method) this.member), false); 774 } 775 else { 776 mv.visitFieldInsn((isStatic ? GETSTATIC : GETFIELD), classDesc, this.member.getName(), 777 CodeFlow.toJvmDescriptor(((Field) this.member).getType())); 778 } 779 } 780 } 781 782}