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