001/*
002 * Copyright 2002-2017 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.context.expression;
018
019import java.util.Map;
020
021import org.springframework.asm.MethodVisitor;
022import org.springframework.expression.AccessException;
023import org.springframework.expression.EvaluationContext;
024import org.springframework.expression.TypedValue;
025import org.springframework.expression.spel.CodeFlow;
026import org.springframework.expression.spel.CompilablePropertyAccessor;
027
028/**
029 * EL property accessor that knows how to traverse the keys
030 * of a standard {@link java.util.Map}.
031 *
032 * @author Juergen Hoeller
033 * @author Andy Clement
034 * @since 3.0
035 */
036public class MapAccessor implements CompilablePropertyAccessor {
037
038        @Override
039        public Class<?>[] getSpecificTargetClasses() {
040                return new Class<?>[] {Map.class};
041        }
042
043        @Override
044        public boolean canRead(EvaluationContext context, Object target, String name) throws AccessException {
045                Map<?, ?> map = (Map<?, ?>) target;
046                return map.containsKey(name);
047        }
048
049        @Override
050        public TypedValue read(EvaluationContext context, Object target, String name) throws AccessException {
051                Map<?, ?> map = (Map<?, ?>) target;
052                Object value = map.get(name);
053                if (value == null && !map.containsKey(name)) {
054                        throw new MapAccessException(name);
055                }
056                return new TypedValue(value);
057        }
058
059        @Override
060        public boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException {
061                return true;
062        }
063
064        @Override
065        @SuppressWarnings("unchecked")
066        public void write(EvaluationContext context, Object target, String name, Object newValue) throws AccessException {
067                Map<Object, Object> map = (Map<Object, Object>) target;
068                map.put(name, newValue);
069        }
070
071        @Override
072        public boolean isCompilable() {
073                return true;
074        }
075
076        @Override
077        public Class<?> getPropertyType() {
078                return Object.class;
079        }
080
081        @Override
082        public void generateCode(String propertyName, MethodVisitor mv, CodeFlow cf) {
083                String descriptor = cf.lastDescriptor();
084                if (descriptor == null || !descriptor.equals("Ljava/util/Map")) {
085                        if (descriptor == null) {
086                                cf.loadTarget(mv);
087                        }
088                        CodeFlow.insertCheckCast(mv, "Ljava/util/Map");
089                }
090                mv.visitLdcInsn(propertyName);
091                mv.visitMethodInsn(INVOKEINTERFACE, "java/util/Map", "get","(Ljava/lang/Object;)Ljava/lang/Object;",true);
092        }
093
094
095        /**
096         * Exception thrown from {@code read} in order to reset a cached
097         * PropertyAccessor, allowing other accessors to have a try.
098         */
099        @SuppressWarnings("serial")
100        private static class MapAccessException extends AccessException {
101
102                private final String key;
103
104                public MapAccessException(String key) {
105                        super(null);
106                        this.key = key;
107                }
108
109                @Override
110                public String getMessage() {
111                        return "Map does not contain a value for key '" + this.key + "'";
112                }
113        }
114
115}