/* * Copyright 2004-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.webflow.core.collection; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import org.springframework.binding.collection.MapAccessor; import org.springframework.binding.convert.ConversionExecutionException; import org.springframework.binding.convert.ConversionExecutor; import org.springframework.binding.convert.ConversionService; import org.springframework.binding.convert.service.DefaultConversionService; import org.springframework.core.style.StylerUtils; import org.springframework.util.Assert; import org.springframework.web.multipart.MultipartFile; /** * An immutable parameter map storing String-keyed, String-valued parameters in a backing {@link Map} implementation. * This base provides convenient operations for accessing parameters in a typed-manner. * * @author Keith Donald */ public class LocalParameterMap implements ParameterMap, Serializable { private static final DefaultConversionService DEFAULT_CONVERSION_SERVICE = new DefaultConversionService(); /** * The backing map storing the parameters. */ private Map<String, Object> parameters; /** * A helper for accessing parameters. Marked transient and restored on deserialization. */ private transient MapAccessor<String, Object> parameterAccessor; /** * A helper for converting string parameter values. Marked transient and restored on deserialization. */ private transient ConversionService conversionService; /** * Creates a new parameter map from the provided map. * <p> * It is expected that the contents of the backing map adhere to the parameter map contract; that is, map entries * have string keys, string values, and remain unmodifiable. * @param parameters the contents of this parameter map */ public LocalParameterMap(Map<String, Object> parameters) { this(parameters, DEFAULT_CONVERSION_SERVICE); } /** * Creates a new parameter map from the provided map. * <p> * It is expected that the contents of the backing map adhere to the parameter map contract; that is, map entries * have string keys, string values, and remain unmodifiable. * @param parameters the contents of this parameter map * @param conversionService a helper for performing type conversion of map entry values */ public LocalParameterMap(Map<String, Object> parameters, ConversionService conversionService) { initParameters(parameters); this.conversionService = conversionService; } public boolean equals(Object o) { if (!(o instanceof LocalParameterMap)) { return false; } LocalParameterMap other = (LocalParameterMap) o; return parameters.equals(other.parameters); } public int hashCode() { return parameters.hashCode(); } public Map<String, Object> asMap() { return Collections.unmodifiableMap(parameterAccessor.asMap()); } public boolean isEmpty() { return parameters.isEmpty(); } public int size() { return parameters.size(); } public boolean contains(String parameterName) { return parameters.containsKey(parameterName); } public String get(String parameterName) { return get(parameterName, (String) null); } public String get(String parameterName, String defaultValue) { if (!parameters.containsKey(parameterName)) { return defaultValue; } Object value = parameters.get(parameterName); if (value.getClass().isArray()) { parameterAccessor.assertKeyValueInstanceOf(parameterName, value, String[].class); String[] array = (String[]) value; if (array.length == 0) { return null; } else { Object first = ((String[]) value)[0]; parameterAccessor.assertKeyValueInstanceOf(parameterName, first, String.class); return (String) first; } } else { parameterAccessor.assertKeyValueInstanceOf(parameterName, value, String.class); return (String) value; } } public String[] getArray(String parameterName) { if (!parameters.containsKey(parameterName)) { return null; } Object value = parameters.get(parameterName); if (value.getClass().isArray()) { parameterAccessor.assertKeyValueInstanceOf(parameterName, value, String[].class); return (String[]) value; } else { parameterAccessor.assertKeyValueInstanceOf(parameterName, value, String.class); return new String[] { (String) value }; } } public <T> T[] getArray(String parameterName, Class<T> targetElementType) throws ConversionExecutionException { String[] parameters = getArray(parameterName); return parameters != null ? convert(parameters, targetElementType) : null; } public <T> T get(String parameterName, Class<T> targetType) throws ConversionExecutionException { return get(parameterName, targetType, null); } public <T> T get(String parameterName, Class<T> targetType, T defaultValue) throws ConversionExecutionException { if (defaultValue != null) { assertAssignableTo(targetType, defaultValue.getClass()); } String parameter = get(parameterName); return parameter != null ? convert(parameter, targetType) : defaultValue; } public String getRequired(String parameterName) throws IllegalArgumentException { parameterAccessor.assertContainsKey(parameterName); return get(parameterName); } public String[] getRequiredArray(String parameterName) throws IllegalArgumentException { parameterAccessor.assertContainsKey(parameterName); return getArray(parameterName); } public <T> T[] getRequiredArray(String parameterName, Class<T> targetElementType) throws IllegalArgumentException, ConversionExecutionException { String[] parameters = getRequiredArray(parameterName); return convert(parameters, targetElementType); } public <T> T getRequired(String parameterName, Class<T> targetType) throws IllegalArgumentException, ConversionExecutionException { return convert(getRequired(parameterName), targetType); } public <T extends Number> T getNumber(String parameterName, Class<T> targetType) throws ConversionExecutionException { assertAssignableTo(Number.class, targetType); return get(parameterName, targetType); } public <T extends Number> T getNumber(String parameterName, Class<T> targetType, T defaultValue) throws ConversionExecutionException { assertAssignableTo(Number.class, targetType); return get(parameterName, targetType, defaultValue); } public <T extends Number> T getRequiredNumber(String parameterName, Class<T> targetType) throws IllegalArgumentException, ConversionExecutionException { assertAssignableTo(Number.class, targetType); return getRequired(parameterName, targetType); } public Integer getInteger(String parameterName) throws ConversionExecutionException { return get(parameterName, Integer.class); } public Integer getInteger(String parameterName, Integer defaultValue) throws ConversionExecutionException { return get(parameterName, Integer.class, defaultValue); } public Integer getRequiredInteger(String parameterName) throws IllegalArgumentException, ConversionExecutionException { return getRequired(parameterName, Integer.class); } public Long getLong(String parameterName) throws ConversionExecutionException { return get(parameterName, Long.class); } public Long getLong(String parameterName, Long defaultValue) throws ConversionExecutionException { return get(parameterName, Long.class, defaultValue); } public Long getRequiredLong(String parameterName) throws IllegalArgumentException, ConversionExecutionException { return getRequired(parameterName, Long.class); } public Boolean getBoolean(String parameterName) throws ConversionExecutionException { return get(parameterName, Boolean.class); } public Boolean getBoolean(String parameterName, Boolean defaultValue) throws ConversionExecutionException { return get(parameterName, Boolean.class, defaultValue); } public Boolean getRequiredBoolean(String parameterName) throws IllegalArgumentException, ConversionExecutionException { return getRequired(parameterName, Boolean.class); } public MultipartFile getMultipartFile(String parameterName) { return parameterAccessor.get(parameterName, MultipartFile.class); } public MultipartFile getRequiredMultipartFile(String parameterName) throws IllegalArgumentException { return parameterAccessor.getRequired(parameterName, MultipartFile.class); } public AttributeMap<Object> asAttributeMap() { return new LocalAttributeMap<Object>(getMapInternal()); } /** * Initializes this parameter map. * @param parameters the parameters */ protected void initParameters(Map<String, Object> parameters) { this.parameters = parameters; parameterAccessor = new MapAccessor<String, Object>(this.parameters); } /** * Returns the wrapped, modifiable map implementation. */ protected Map<String, Object> getMapInternal() { return parameters; } // internal helpers /** * Convert given String parameter to specified target type. */ @SuppressWarnings("unchecked") private <T> T convert(String parameter, Class<T> targetType) throws ConversionExecutionException { return (T) conversionService.getConversionExecutor(String.class, targetType).execute(parameter); } /** * Convert given array of String parameters to specified target type and return the resulting array. */ @SuppressWarnings("unchecked") private <T> T[] convert(String[] parameters, Class<? extends T> targetElementType) throws ConversionExecutionException { List<T> list = new ArrayList<T>(parameters.length); ConversionExecutor converter = conversionService.getConversionExecutor(String.class, targetElementType); for (String parameter : parameters) { list.add((T) converter.execute(parameter)); } return list.toArray((T[]) Array.newInstance(targetElementType, parameters.length)); } /** * Make sure clazz is assignable from requiredType. */ private void assertAssignableTo(Class<?> clazz, Class<?> requiredType) { Assert.isTrue(clazz.isAssignableFrom(requiredType), "The provided required type must be assignable to [" + clazz + "]"); } // custom serialization private void writeObject(ObjectOutputStream out) throws IOException { out.defaultWriteObject(); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); parameterAccessor = new MapAccessor<String, Object>(parameters); conversionService = DEFAULT_CONVERSION_SERVICE; } public String toString() { return StylerUtils.style(parameters); } }