1 package net.avcompris.commons3.web.it.utils;
2
3 import static com.google.common.base.Preconditions.checkArgument;
4 import static com.google.common.base.Preconditions.checkNotNull;
5 import static com.google.common.collect.Sets.newHashSet;
6 import static org.apache.commons.lang3.StringUtils.uncapitalize;
7
8 import java.io.IOException;
9 import java.lang.reflect.Array;
10 import java.lang.reflect.InvocationHandler;
11 import java.lang.reflect.Method;
12 import java.lang.reflect.Proxy;
13 import java.util.Set;
14
15 import org.apache.commons.lang3.NotImplementedException;
16 import org.json.simple.JSONArray;
17 import org.json.simple.JSONObject;
18 import org.json.simple.parser.JSONParser;
19 import org.json.simple.parser.ParseException;
20
21 import net.avcompris.commons3.core.DateTimeHolderImpl;
22 import net.avcompris.commons3.types.DateTimeHolder;
23
24 public abstract class JSONUtils {
25
26 public static <T> T parseJSON(final String json, final Class<T> clazz) throws IOException, ParseException {
27
28 checkNotNull(json, "json");
29 checkNotNull(clazz, "clazz");
30
31
32
33
34
35
36
37
38
39
40
41 final JSONParser parser = new JSONParser();
42
43 final JSONObject jsonObject = (JSONObject) parser.parse(json);
44
45 return parseJSON(jsonObject, clazz);
46 }
47
48 public static <T> T parseJSON(final JSONObject json, final Class<T> clazz) throws IOException {
49
50 checkNotNull(json, "json");
51 checkNotNull(clazz, "clazz");
52
53 checkArgument(clazz.isInterface(), "clazz should be an interface: %s", clazz.getName());
54
55 final ClassLoader classLoader =
56 clazz.getClassLoader();
57
58 final Object proxy = Proxy.newProxyInstance(classLoader, new Class<?>[] {
59 clazz
60 }, new InvocationHandler() {
61
62 @Override
63 public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
64
65 final int parameterCount = method.getParameterCount();
66
67 final String methodName = method.getName();
68
69 if (parameterCount == 0) {
70
71 if ("getClass".contentEquals(methodName)) {
72
73 throw new NotImplementedException("method: " + method);
74
75 } else if ("hashCode".contentEquals(methodName)) {
76
77 return json.hashCode();
78
79 } else if ("toString".contentEquals(methodName)) {
80
81 return json.toJSONString();
82 }
83
84 final String suffix;
85
86 if (methodName.startsWith("get")) {
87
88 suffix = methodName.substring(3);
89
90 } else if (methodName.startsWith("is")) {
91
92 suffix = methodName.substring(2);
93
94 } else {
95
96 throw new NotImplementedException("method: " + method);
97 }
98
99 final String propertyName = uncapitalize(suffix);
100
101 final Object value = json.get(propertyName);
102
103 final Class<?> returnType = method.getReturnType();
104
105 if (returnType == null) {
106 throw new NotImplementedException("method: " + method);
107 }
108
109 if (boolean.class.equals(returnType)) {
110
111 if (value instanceof Boolean) {
112
113 return (boolean) value;
114
115 } else {
116
117 throw new NotImplementedException("property \"" + propertyName + "\" should be a boolean: "
118 + value + ", but was: " + value.getClass().getName());
119 }
120
121 } else if (int.class.equals(returnType)) {
122
123 if (value instanceof Long) {
124
125 return ((Long) value).intValue();
126
127 } else if (value instanceof Integer) {
128
129 return (int) value;
130
131 } else {
132
133 throw new NotImplementedException(
134 "property \"" + propertyName + "\" should be a long or an int: " + value
135 + ", but was: " + value.getClass().getName());
136 }
137
138 } else if (String.class.equals(returnType)) {
139
140 if (value == null) {
141
142 return null;
143
144 } else if (value instanceof String) {
145
146 return (String) value;
147
148 } else {
149
150 throw new NotImplementedException("property \"" + propertyName + "\" should be a String: "
151 + value + ", but was: " + value.getClass().getName());
152 }
153
154 } else if (DateTimeHolder.class.equals(returnType)) {
155
156 if (value == null) {
157
158 return null;
159
160 } else if (value instanceof JSONObject) {
161
162 @SuppressWarnings("unused")
163 final String dateTime = (String) ((JSONObject) value).get("dateTime");
164
165 final long timestamp = (Long) ((JSONObject) value).get("timestamp");
166
167 return DateTimeHolderImpl.toDateTimeHolder(timestamp);
168
169 } else {
170
171 throw new NotImplementedException(
172 "property \"" + propertyName + "\" should be a DateTimeHolder: " + value);
173 }
174
175 } else if (returnType.isEnum()) {
176
177 if (value == null) {
178
179 return null;
180
181 } else if (value instanceof String) {
182
183 final String name = (String) value;
184
185 for (final Object constant : returnType.getEnumConstants()) {
186
187 final Enum<?> enumConstant = (Enum<?>) constant;
188
189 if (name.equals(enumConstant.name())) {
190
191 return enumConstant;
192 }
193 }
194
195 throw new IOException("Illegal Enum value for \"" + propertyName + "\": " + value
196 + " (Enum class: " + returnType.getName() + ")");
197
198 } else {
199
200 throw new NotImplementedException("property \"" + propertyName + "\" should be a String: "
201 + value + ", but was: " + value.getClass().getName());
202 }
203
204 } else if (returnType.isInterface()) {
205
206 if (value == null) {
207
208 return null;
209
210 } else if (value instanceof JSONObject) {
211
212 return parseJSON((JSONObject) value, returnType);
213
214 } else {
215
216 throw new NotImplementedException("property \"" + propertyName
217 + "\" should be a JSONObject, but was: " + value.getClass().getName());
218
219 }
220
221 } else if (returnType.isArray()) {
222
223 if (value == null) {
224
225 return null;
226
227 } else if (value instanceof JSONArray) {
228
229 final JSONArray jsonArray = (JSONArray) value;
230
231 final int count = jsonArray.size();
232
233 final Class<?> componentType = returnType.getComponentType();
234
235 final Object array = Array.newInstance(componentType, count);
236
237 for (int i = 0; i < count; ++i) {
238
239 final Object item = parseJSON((JSONObject) jsonArray.get(i), componentType);
240
241 Array.set(array, i, item);
242 }
243
244 return array;
245
246 } else {
247
248 throw new NotImplementedException("property \"" + propertyName
249 + "\" should be an array, but was: " + value.getClass().getName());
250 }
251 }
252
253 } else if (parameterCount == 1
254 && "equals".contentEquals(methodName)
255 && Object.class.equals(method.getParameterTypes()[0])) {
256
257 final Object arg = args[0];
258
259 if (arg == null || !clazz.isInstance(arg)) {
260 return false;
261 }
262
263 final Set<Method> getters1 = extractGetters(clazz);
264 final Set<Method> getters2 = extractGetters(arg.getClass());
265
266 if (getters1.size() != getters2.size()) {
267 return false;
268 }
269
270 for (final Method getter : getters1) {
271
272 final Object result1 = getter.invoke(proxy);
273 final Object result2 = getter.invoke(arg);
274
275 if (result1 == null && result2 == null) {
276 continue;
277 } else if (result1 == null || result2 == null) {
278 return false;
279 }
280
281 final Class<?> returnType = getter.getReturnType();
282
283 if (returnType.isArray()) {
284
285 final int size1 = Array.getLength(result1);
286 final int size2 = Array.getLength(result2);
287
288 if (size1 != size2) {
289 return false;
290 }
291
292 for (int i = 0; i < size1; ++i) {
293
294 final Object item1 = Array.get(result1, i);
295 final Object item2 = Array.get(result2, i);
296
297 if (item1 == null && item2 == null) {
298 continue;
299 } else if (item1 == null || item2 == null) {
300 return false;
301 } else if (!item1.equals(item2)) {
302 return false;
303 }
304 }
305
306 } else if (!result1.equals(result2)) {
307 return false;
308 }
309 }
310
311 return true;
312 }
313
314 throw new NotImplementedException("method: " + method);
315 }
316 });
317
318 return clazz.cast(proxy);
319 }
320
321 private static Set<Method> extractGetters(final Class<?> clazz) {
322
323 final Set<Method> getters = newHashSet();
324
325 for (final Method method : clazz.getMethods()) {
326
327 final int parameterCount = method.getParameterCount();
328
329 final String methodName = method.getName();
330
331 if (parameterCount != 0
332 || "notify".contentEquals(methodName)
333 || "notifyAll".contentEquals(methodName)
334 || "wait".contentEquals(methodName)
335 || "getClass".contentEquals(methodName)
336 || "hashCode".contentEquals(methodName)
337 || "toString".contentEquals(methodName)) {
338
339 continue;
340 }
341
342 getters.add(method);
343 }
344
345 return getters;
346 }
347
348
349
350
351
352
353
354
355
356
357 }