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}