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}