View Javadoc

1   /*
2    * Copyright 2004-2005, 2007-2008 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5    * use this file except in compliance with the License. You may obtain a copy of
6    * the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations under
14   * the License.
15   */
16  package net.sf.morph.reflect.reflectors;
17  
18  import java.util.HashMap;
19  import java.util.Iterator;
20  import java.util.Map;
21  import java.util.Set;
22  import java.util.SortedMap;
23  import java.util.TreeMap;
24  import java.util.Map.Entry;
25  
26  import net.sf.composite.util.ObjectUtils;
27  import net.sf.morph.reflect.BeanReflector;
28  import net.sf.morph.reflect.GrowableContainerReflector;
29  import net.sf.morph.reflect.InstantiatingReflector;
30  import net.sf.morph.reflect.ReflectionException;
31  import net.sf.morph.reflect.SizableReflector;
32  import net.sf.morph.transform.copiers.ContainerCopier;
33  import net.sf.morph.util.ContainerUtils;
34  import net.sf.morph.util.StringUtils;
35  
36  /**
37   * Reflector for Maps that allows a map to be treated both as a container and as
38   * a bean.
39   *
40   * @author Matt Sgarlata
41   * @since Nov 27, 2004
42   */
43  public class MapReflector extends BaseReflector implements InstantiatingReflector,
44  		SizableReflector, GrowableContainerReflector, BeanReflector {
45  
46  	/** Implicit <code>entries</code> property */
47  	public static final String IMPLICIT_PROPERTY_ENTRIES = "entries";
48  	/** Implicit <code>keys</code> property */
49  	public static final String IMPLICIT_PROPERTY_KEYS = "keys";
50  	/** Implicit <code>values</code> property */
51  	public static final String IMPLICIT_PROPERTY_VALUES = "values";
52  
53  	/**
54  	 * Indicates that the <code>values</code> of the source map should be
55  	 * copied to the destination container object.
56  	 *
57  	 * @see Map#values()
58  	 */
59  	public static final String EXTRACT_VALUES = "EXTRACT_VALUES";
60  
61  	/**
62  	 * Indicates that the <code>entrySet</code> of the source map should be
63  	 * copied to the destination container object. For example, if the
64  	 * destination container object is a List, then each Map.Entry in the source
65  	 * map will be copied to the destination List
66  	 *
67  	 * @see Map#entrySet()
68  	 */
69  	public static final String EXTRACT_ENTRIES = "EXTRACT_ENTRIES";
70  
71  	/**
72  	 * Indicates that the <code>keySet</code> of the source map should be
73  	 * copied to the destination container object. For example, if the
74  	 * destination container object is a List, then each key in the source map
75  	 * will be copied to the destination List.
76  	 *
77  	 * @see Map#keySet()
78  	 */
79  	public static final String EXTRACT_KEYS = "EXTRACT_KEYS";
80  
81  	/**
82  	 * The default treatment for Maps (which is to extract the values in the
83  	 * Map).
84  	 *
85  	 * @see ContainerCopier#EXTRACT_VALUES
86  	 */
87  	public static final String DEFAULT_MAP_TREATMENT = EXTRACT_VALUES;
88  
89  	private static final Class[] REFLECTABLE_TYPES = new Class[] {
90  		Map.class
91  	};
92  
93  	/**
94  	 * All of the allowed map treatments.
95  	 */
96  	protected static String[] MAP_TREATMENTS = new String[] {
97  		EXTRACT_VALUES, EXTRACT_ENTRIES, EXTRACT_KEYS
98  	};
99  
100 	/**
101 	 * The map treatment this copier is using.
102 	 */
103 	private String mapTreatment;
104 
105 	/**
106 	 * Create a new MapReflector using the default map treatment.
107 	 */
108 	public MapReflector() {
109 		this(DEFAULT_MAP_TREATMENT);
110 	}
111 
112 	/**
113 	 * Create a new MapReflector.
114 	 * @param mapTreatment to use
115 	 */
116 	public MapReflector(String mapTreatment) {
117 		setMapTreatment(mapTreatment);
118 	}
119 
120 	/**
121 	 * Get the specified container as a Map.
122 	 * @param container to get
123 	 * @return Map
124 	 */
125 	protected Map getMap(Object container) {
126 		return (Map) container;
127 	}
128 
129 // container
130 
131 	/**
132 	 * {@inheritDoc}
133 	 */
134 	protected int getSizeImpl(Object container) throws Exception {
135 		return getMap(container).size();
136 	}
137 
138 	/**
139 	 * {@inheritDoc}
140 	 */
141 	protected Class getContainedTypeImpl(Class clazz) throws Exception {
142 		// TODO JDK 1.5 support
143 		return Object.class;
144 	}
145 
146 	/**
147 	 * {@inheritDoc}
148 	 */
149 	public Class[] getReflectableClassesImpl() {
150 		return REFLECTABLE_TYPES;
151 	}
152 
153 	/**
154 	 * {@inheritDoc}
155 	 */
156 	protected Object newInstanceImpl(Class interfaceClass, Object parameters) throws Exception {
157 		if (interfaceClass == Map.class) {
158 			return new HashMap();
159 		}
160 		if (interfaceClass == SortedMap.class) {
161 			return new TreeMap();
162 		}
163 		return super.newInstanceImpl(interfaceClass, parameters);
164 	}
165 
166 	/**
167 	 * {@inheritDoc}
168 	 */
169 	protected boolean addImpl(Object container, Object value) throws Exception {
170 		if (isExtractEntries()) {
171 			if (!(value instanceof Map.Entry)) {
172 				throw new IllegalArgumentException(ObjectUtils.getObjectDescription(value) + " cannot be added to the Map because it is not of type java.util.Map.Entry");
173 			}
174 			Entry entry = (Map.Entry) value;
175 			Object returnVal = getMap(container).put(entry.getKey(), entry.getValue());
176 			return ObjectUtils.equals(value, returnVal);
177 		}
178 		if (isExtractKeys()) {
179 			Object returnVal = getMap(container).put(value, null);
180 			return ObjectUtils.equals(value, returnVal);
181 		}
182 		if (isExtractValues()) {
183 			if (log.isWarnEnabled()) {
184 				log.warn("The " + ObjectUtils.getObjectDescription(this) + " is set to " + getMapTreatment() + " so " + ObjectUtils.getObjectDescription(value) + " will be added to the Map with a null key");
185 			}
186 			Object returnVal = getMap(container).put(null, value);
187 			return ObjectUtils.equals(value, returnVal);
188 		}
189 		throw new ReflectionException("Unknown map treatment '" + getMapTreatment() + "'");
190 	}
191 
192 	/**
193 	 * {@inheritDoc}
194 	 */
195 	protected Iterator getIteratorImpl(Object container) throws Exception {
196 		if (isExtractEntries()) {
197 			return getMap(container).entrySet().iterator();
198 		}
199 		if (isExtractKeys()) {
200 			return getMap(container).keySet().iterator();
201 		}
202 		if (isExtractValues()) {
203 			return getMap(container).values().iterator();
204 		}
205 		// this shouldn't ever happen
206 		throw new ReflectionException("Invalid mapTreatment: " + getMapTreatment());
207 	}
208 
209 // bean
210 
211 	/**
212 	 * {@inheritDoc}
213 	 */
214 	protected String[] getPropertyNamesImpl(Object bean) throws Exception {
215 //		 the getPropertyNames method used to return implicit properties, but it
216 //		 doesn't anymore
217 //		Set keys = new ContainerUtils.createOrderedSet();
218 //		keys.addAll(getMap(bean).keySet());
219 //		keys.add(IMPLICIT_PROPERTY_KEYS);
220 //		keys.add(IMPLICIT_PROPERTY_VALUES);
221 //		keys.add(IMPLICIT_PROPERTY_ENTRIES);
222 //		return (String[]) keys.toArray(new String[keys.size()]);
223 		Set keys = getMap(bean).keySet();
224 		return (String[]) keys.toArray(new String[keys.size()]);
225 	}
226 
227 	/**
228 	 * {@inheritDoc}
229 	 */
230 	protected Class getTypeImpl(Object bean, String propertyName) throws Exception {
231 		return Object.class;
232 	}
233 
234 	/**
235 	 * {@inheritDoc}
236 	 */
237 	protected boolean isReadableImpl(Object bean, String propertyName)
238 		throws Exception {
239 		return true;
240 //		return
241 //			getMap(bean).containsKey(propertyName) ||
242 //			IMPLICIT_PROPERTY_KEYS.equals(propertyName) ||
243 //			IMPLICIT_PROPERTY_ENTRIES.equals(propertyName) ||
244 //			IMPLICIT_PROPERTY_VALUES.equals(propertyName);
245 	}
246 
247 	/**
248 	 * {@inheritDoc}
249 	 */
250 	protected boolean isWriteableImpl(Object bean, String propertyName)
251 		throws Exception {
252 		return true;
253 	}
254 
255 	/**
256 	 * {@inheritDoc}
257 	 */
258 	protected Object getImpl(Object bean, String propertyName) throws Exception {
259 		Object value = getMap(bean).get(propertyName);
260 		if (propertyName.equals(IMPLICIT_PROPERTY_VALUES) && value == null) {
261 			return getMap(bean).values();
262 		}
263 		if (propertyName.equals(IMPLICIT_PROPERTY_KEYS) && value == null) {
264 			return getMap(bean).keySet();
265 		}
266 		if (propertyName.equals(IMPLICIT_PROPERTY_ENTRIES) && value == null) {
267 			return getMap(bean).entrySet();
268 		}
269 		return value;
270 	}
271 
272 	/**
273 	 * {@inheritDoc}
274 	 */
275 	protected void setImpl(Object bean, String propertyName, Object value)
276 		throws Exception {
277 		getMap(bean).put(propertyName, value);
278 	}
279 
280 	/**
281 	 * Get the map treatment in use.
282 	 * @return String
283 	 */
284 	public String getMapTreatment() {
285 		return mapTreatment;
286 	}
287 
288 	/**
289 	 * Sets how maps are treated by this reflector.
290 	 * @param mapTreatment how maps are treated by this reflector
291 	 * @throws ReflectionException if an invalid map treatment is specified
292 	 */
293 	public void setMapTreatment(String mapTreatment) throws
294 		ReflectionException {
295 		if (!ContainerUtils.contains(MAP_TREATMENTS, mapTreatment)) {
296 			throw new ReflectionException("Invalid value for the mapTreatment attribute.  Valid values are: " + StringUtils.englishJoin(MAP_TREATMENTS));
297 		}
298 		this.mapTreatment = mapTreatment;
299 	}
300 
301 	/**
302 	 * Learn whether this reflector extracts map entries.
303 	 * @return boolean
304 	 */
305 	public boolean isExtractEntries() {
306 		return EXTRACT_ENTRIES.equals(getMapTreatment());
307 	}
308 
309 	/**
310 	 * Learn whether this reflector extracts map keys.
311 	 * @return boolean
312 	 */
313 	public boolean isExtractKeys() {
314 		return EXTRACT_KEYS.equals(getMapTreatment());
315 	}
316 
317 	/**
318 	 * Learn whether this reflector extracts map values.
319 	 * @return boolean
320 	 */
321 	public boolean isExtractValues() {
322 		return EXTRACT_VALUES.equals(getMapTreatment());
323 	}
324 
325 }