1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
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
206 throw new ReflectionException("Invalid mapTreatment: " + getMapTreatment());
207 }
208
209
210
211 /**
212 * {@inheritDoc}
213 */
214 protected String[] getPropertyNamesImpl(Object bean) throws Exception {
215
216
217
218
219
220
221
222
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
241
242
243
244
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 }