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