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}