1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.apache.struts2.json;
22
23 import java.beans.BeanInfo;
24 import java.beans.IntrospectionException;
25 import java.beans.Introspector;
26 import java.beans.PropertyDescriptor;
27 import java.lang.reflect.Array;
28 import java.lang.reflect.InvocationTargetException;
29 import java.lang.reflect.Method;
30 import java.lang.reflect.Modifier;
31 import java.lang.reflect.ParameterizedType;
32 import java.lang.reflect.Type;
33 import java.math.BigDecimal;
34 import java.math.BigInteger;
35 import java.text.DateFormat;
36 import java.text.ParseException;
37 import java.text.SimpleDateFormat;
38 import java.util.ArrayList;
39 import java.util.Collection;
40 import java.util.Date;
41 import java.util.HashMap;
42 import java.util.HashSet;
43 import java.util.Iterator;
44 import java.util.List;
45 import java.util.Locale;
46 import java.util.Map;
47 import java.util.Set;
48
49 import org.apache.struts2.json.annotations.JSON;
50
51 import com.opensymphony.xwork2.util.logging.Logger;
52 import com.opensymphony.xwork2.util.logging.LoggerFactory;
53
54 /***
55 * Isolate the process of populating JSON objects from the Interceptor class
56 * itself.
57 */
58 public class JSONPopulator {
59
60 private static final Logger LOG = LoggerFactory.getLogger(JSONPopulator.class);
61
62 private String dateFormat = JSONUtil.RFC3339_FORMAT;
63
64 public JSONPopulator() {
65 }
66
67 public JSONPopulator(String dateFormat) {
68 this.dateFormat = dateFormat;
69 }
70
71 public String getDateFormat() {
72 return dateFormat;
73 }
74
75 public void setDateFormat(String dateFormat) {
76 this.dateFormat = dateFormat;
77 }
78
79 @SuppressWarnings("unchecked")
80 public void populateObject(Object object, final Map elements) throws IllegalAccessException,
81 InvocationTargetException, NoSuchMethodException, IntrospectionException,
82 IllegalArgumentException, JSONException, InstantiationException {
83 Class clazz = object.getClass();
84
85 BeanInfo info = Introspector.getBeanInfo(clazz);
86 PropertyDescriptor[] props = info.getPropertyDescriptors();
87
88
89 for (int i = 0; i < props.length; ++i) {
90 PropertyDescriptor prop = props[i];
91 String name = prop.getName();
92
93 if (elements.containsKey(name)) {
94 Object value = elements.get(name);
95 Method method = prop.getWriteMethod();
96
97 if (method != null) {
98 JSON json = method.getAnnotation(JSON.class);
99 if ((json != null) && !json.deserialize()) {
100 continue;
101 }
102
103
104 if (Modifier.isPublic(method.getModifiers())) {
105 Class[] paramTypes = method.getParameterTypes();
106 Type[] genericTypes = method.getGenericParameterTypes();
107
108 if (paramTypes.length == 1) {
109 Object convertedValue = this.convert(paramTypes[0], genericTypes[0], value,
110 method);
111 method.invoke(object, new Object[] { convertedValue });
112 }
113 }
114 }
115 }
116 }
117 }
118
119 @SuppressWarnings("unchecked")
120 public Object convert(Class clazz, Type type, Object value, Method method)
121 throws IllegalArgumentException, JSONException, IllegalAccessException,
122 InvocationTargetException, InstantiationException, NoSuchMethodException, IntrospectionException {
123
124 if (value == null) {
125
126
127 return clazz.isPrimitive() ? convertPrimitive(clazz, value, method) : null;
128 } else if (isJSONPrimitive(clazz))
129 return convertPrimitive(clazz, value, method);
130 else if (Collection.class.isAssignableFrom(clazz))
131 return convertToCollection(clazz, type, value, method);
132 else if (Map.class.isAssignableFrom(clazz))
133 return convertToMap(clazz, type, value, method);
134 else if (clazz.isArray())
135 return convertToArray(clazz, type, value, method);
136 else if (value instanceof Map) {
137
138 Object convertedValue = clazz.newInstance();
139 this.populateObject(convertedValue, (Map) value);
140 return convertedValue;
141 } else if (BigDecimal.class.equals(clazz)) {
142 return new BigDecimal(value != null ? value.toString() : "0");
143 } else if (BigInteger.class.equals(clazz)) {
144 return new BigInteger(value != null ? value.toString() : "0");
145 } else
146 throw new JSONException("Incompatible types for property " + method.getName());
147 }
148
149 private static boolean isJSONPrimitive(Class clazz) {
150 return clazz.isPrimitive() || clazz.equals(String.class) || clazz.equals(Date.class)
151 || clazz.equals(Boolean.class) || clazz.equals(Byte.class) || clazz.equals(Character.class)
152 || clazz.equals(Double.class) || clazz.equals(Float.class) || clazz.equals(Integer.class)
153 || clazz.equals(Long.class) || clazz.equals(Short.class) || clazz.equals(Locale.class)
154 || clazz.isEnum();
155 }
156
157 @SuppressWarnings("unchecked")
158 private Object convertToArray(Class clazz, Type type, Object value, Method accessor)
159 throws JSONException, IllegalArgumentException, IllegalAccessException,
160 InvocationTargetException, InstantiationException, NoSuchMethodException, IntrospectionException {
161 if (value == null)
162 return null;
163 else if (value instanceof List) {
164 Class arrayType = clazz.getComponentType();
165 List values = (List) value;
166 Object newArray = Array.newInstance(arrayType, values.size());
167
168
169 for (int j = 0; j < values.size(); j++) {
170 Object listValue = values.get(j);
171
172 if (arrayType.equals(Object.class)) {
173
174 Array.set(newArray, j, listValue);
175 } else if (isJSONPrimitive(arrayType)) {
176
177 Array.set(newArray, j, this.convertPrimitive(arrayType, listValue, accessor));
178 } else if (listValue instanceof Map) {
179
180 Object newObject = null;
181 if (Map.class.isAssignableFrom(arrayType)) {
182 newObject = convertToMap(arrayType, type, listValue, accessor);
183 } else if (List.class.isAssignableFrom(arrayType)) {
184 newObject = convertToCollection(arrayType, type, listValue, accessor);
185 } else {
186 newObject = arrayType.newInstance();
187 this.populateObject(newObject, (Map) listValue);
188 }
189
190 Array.set(newArray, j, newObject);
191 } else
192 throw new JSONException("Incompatible types for property " + accessor.getName());
193 }
194
195 return newArray;
196 } else
197 throw new JSONException("Incompatible types for property " + accessor.getName());
198 }
199
200 @SuppressWarnings("unchecked")
201 private Object convertToCollection(Class clazz, Type type, Object value, Method accessor)
202 throws JSONException, IllegalArgumentException, IllegalAccessException,
203 InvocationTargetException, InstantiationException, NoSuchMethodException, IntrospectionException {
204 if (value == null)
205 return null;
206 else if (value instanceof List) {
207 Class itemClass = Object.class;
208 Type itemType = null;
209 if ((type != null) && (type instanceof ParameterizedType)) {
210 ParameterizedType ptype = (ParameterizedType) type;
211 itemType = ptype.getActualTypeArguments()[0];
212 if (itemType.getClass().equals(Class.class)) {
213 itemClass = (Class) itemType;
214 } else {
215 itemClass = (Class) ((ParameterizedType) itemType).getRawType();
216 }
217 }
218 List values = (List) value;
219
220 Collection newCollection = null;
221 try {
222 newCollection = (Collection) clazz.newInstance();
223 } catch (InstantiationException ex) {
224
225 if (Set.class.isAssignableFrom(clazz)) {
226 newCollection = new HashSet();
227 } else {
228 newCollection = new ArrayList();
229 }
230 }
231
232
233 for (int j = 0; j < values.size(); j++) {
234 Object listValue = values.get(j);
235
236 if (itemClass.equals(Object.class)) {
237
238 newCollection.add(listValue);
239 } else if (isJSONPrimitive(itemClass)) {
240
241 newCollection.add(this.convertPrimitive(itemClass, listValue, accessor));
242 } else if (Map.class.isAssignableFrom(itemClass)) {
243 Object newObject = convertToMap(itemClass, itemType, listValue, accessor);
244 newCollection.add(newObject);
245 } else if (List.class.isAssignableFrom(itemClass)) {
246 Object newObject = convertToCollection(itemClass, itemType, listValue, accessor);
247 newCollection.add(newObject);
248 } else if (listValue instanceof Map) {
249
250 Object newObject = itemClass.newInstance();
251 this.populateObject(newObject, (Map) listValue);
252 newCollection.add(newObject);
253 } else
254 throw new JSONException("Incompatible types for property " + accessor.getName());
255 }
256
257 return newCollection;
258 } else
259 throw new JSONException("Incompatible types for property " + accessor.getName());
260 }
261
262 @SuppressWarnings("unchecked")
263 private Object convertToMap(Class clazz, Type type, Object value, Method accessor) throws JSONException,
264 IllegalArgumentException, IllegalAccessException, InvocationTargetException,
265 InstantiationException, NoSuchMethodException, IntrospectionException {
266 if (value == null)
267 return null;
268 else if (value instanceof Map) {
269 Class itemClass = Object.class;
270 Type itemType = null;
271 if ((type != null) && (type instanceof ParameterizedType)) {
272 ParameterizedType ptype = (ParameterizedType) type;
273 itemType = ptype.getActualTypeArguments()[1];
274 if (itemType.getClass().equals(Class.class)) {
275 itemClass = (Class) itemType;
276 } else {
277 itemClass = (Class) ((ParameterizedType) itemType).getRawType();
278 }
279 }
280 Map values = (Map) value;
281
282 Map newMap = null;
283 try {
284 newMap = (Map) clazz.newInstance();
285 } catch (InstantiationException ex) {
286
287 newMap = new HashMap();
288 }
289
290
291 Iterator iter = values.entrySet().iterator();
292 while (iter.hasNext()) {
293 Map.Entry entry = (Map.Entry) iter.next();
294 String key = (String) entry.getKey();
295 Object v = entry.getValue();
296
297 if (itemClass.equals(Object.class)) {
298
299 newMap.put(key, v);
300 } else if (isJSONPrimitive(itemClass)) {
301
302 newMap.put(key, this.convertPrimitive(itemClass, v, accessor));
303 } else if (Map.class.isAssignableFrom(itemClass)) {
304 Object newObject = convertToMap(itemClass, itemType, v, accessor);
305 newMap.put(key, newObject);
306 } else if (List.class.isAssignableFrom(itemClass)) {
307 Object newObject = convertToCollection(itemClass, itemType, v, accessor);
308 newMap.put(key, newObject);
309 } else if (v instanceof Map) {
310
311 Object newObject = itemClass.newInstance();
312 this.populateObject(newObject, (Map) v);
313 newMap.put(key, newObject);
314 } else
315 throw new JSONException("Incompatible types for property " + accessor.getName());
316 }
317
318 return newMap;
319 } else
320 throw new JSONException("Incompatible types for property " + accessor.getName());
321 }
322
323 /***
324 * Converts numbers to the desired class, if possible
325 *
326 * @throws JSONException
327 */
328 @SuppressWarnings("unchecked")
329 private Object convertPrimitive(Class clazz, Object value, Method method) throws JSONException {
330 if (value == null) {
331 if (Short.TYPE.equals(clazz) || Short.class.equals(clazz))
332 return (short) 0;
333 else if (Byte.TYPE.equals(clazz) || Byte.class.equals(clazz))
334 return (byte) 0;
335 else if (Integer.TYPE.equals(clazz) || Integer.class.equals(clazz))
336 return 0;
337 else if (Long.TYPE.equals(clazz) || Long.class.equals(clazz))
338 return 0L;
339 else if (Float.TYPE.equals(clazz) || Float.class.equals(clazz))
340 return 0f;
341 else if (Double.TYPE.equals(clazz) || Double.class.equals(clazz))
342 return 0d;
343 else if (Boolean.TYPE.equals(clazz) || Boolean.class.equals(clazz))
344 return Boolean.FALSE;
345 else
346 return null;
347 } else if (value instanceof Number) {
348 Number number = (Number) value;
349
350 if (Short.TYPE.equals(clazz))
351 return number.shortValue();
352 else if (Short.class.equals(clazz))
353 return new Short(number.shortValue());
354 else if (Byte.TYPE.equals(clazz))
355 return number.byteValue();
356 else if (Byte.class.equals(clazz))
357 return new Byte(number.byteValue());
358 else if (Integer.TYPE.equals(clazz))
359 return number.intValue();
360 else if (Integer.class.equals(clazz))
361 return new Integer(number.intValue());
362 else if (Long.TYPE.equals(clazz))
363 return number.longValue();
364 else if (Long.class.equals(clazz))
365 return new Long(number.longValue());
366 else if (Float.TYPE.equals(clazz))
367 return number.floatValue();
368 else if (Float.class.equals(clazz))
369 return new Float(number.floatValue());
370 else if (Double.TYPE.equals(clazz))
371 return number.doubleValue();
372 else if (Double.class.equals(clazz))
373 return new Double(number.doubleValue());
374 else if (String.class.equals(clazz))
375 return value.toString();
376 } else if (clazz.equals(Date.class)) {
377 try {
378 JSON json = method.getAnnotation(JSON.class);
379
380 DateFormat formatter = new SimpleDateFormat(
381 (json != null) && (json.format().length() > 0) ? json.format() : this.dateFormat);
382 return formatter.parse((String) value);
383 } catch (ParseException e) {
384 LOG.error(e.getMessage(), e);
385 throw new JSONException("Unable to parse date from: " + value);
386 }
387 } else if (clazz.isEnum()) {
388 String sValue = (String) value;
389 return Enum.valueOf(clazz, sValue);
390 } else if (value instanceof String) {
391 String sValue = (String) value;
392 if (Boolean.TYPE.equals(clazz))
393 return Boolean.parseBoolean(sValue);
394 else if (Boolean.class.equals(clazz))
395 return Boolean.valueOf(sValue);
396 else if (Short.TYPE.equals(clazz))
397 return Short.parseShort(sValue);
398 else if (Short.class.equals(clazz))
399 return Short.valueOf(sValue);
400 else if (Byte.TYPE.equals(clazz))
401 return Byte.parseByte(sValue);
402 else if (Byte.class.equals(clazz))
403 return Byte.valueOf(sValue);
404 else if (Integer.TYPE.equals(clazz))
405 return Integer.parseInt(sValue);
406 else if (Integer.class.equals(clazz))
407 return Integer.valueOf(sValue);
408 else if (Long.TYPE.equals(clazz))
409 return Long.parseLong(sValue);
410 else if (Long.class.equals(clazz))
411 return Long.valueOf(sValue);
412 else if (Float.TYPE.equals(clazz))
413 return Float.parseFloat(sValue);
414 else if (Float.class.equals(clazz))
415 return Float.valueOf(sValue);
416 else if (Double.TYPE.equals(clazz))
417 return Double.parseDouble(sValue);
418 else if (Double.class.equals(clazz))
419 return Double.valueOf(sValue);
420 else if (Character.TYPE.equals(clazz) || Character.class.equals(clazz)) {
421 char charValue = 0;
422 if (sValue.length() > 0) {
423 charValue = sValue.charAt(0);
424 }
425 if (Character.TYPE.equals(clazz))
426 return charValue;
427 else
428 return new Character(charValue);
429 } else if (clazz.equals(Locale.class)) {
430 String[] components = sValue.split("_", 2);
431 if (components.length == 2) {
432 return new Locale(components[0], components[1]);
433 } else {
434 return new Locale(sValue);
435 }
436 } else if (Enum.class.isAssignableFrom(clazz)) {
437 return Enum.valueOf(clazz, sValue);
438 }
439 }
440
441 return value;
442 }
443
444 }