1 package net.avcompris.commons3.client;
2
3 import static com.google.common.base.Preconditions.checkArgument;
4 import static com.google.common.base.Preconditions.checkNotNull;
5 import static net.avcompris.commons3.core.DateTimeHolderImpl.toDateTimeHolder;
6 import static org.apache.commons.lang3.StringUtils.uncapitalize;
7
8 import java.io.IOException;
9 import java.lang.annotation.Annotation;
10 import java.lang.reflect.InvocationTargetException;
11 import java.lang.reflect.Method;
12
13 import javax.annotation.Nullable;
14
15 import org.joda.time.DateTime;
16
17 import com.fasterxml.jackson.core.JsonParser;
18 import com.fasterxml.jackson.core.JsonToken;
19 import com.fasterxml.jackson.core.ObjectCodec;
20 import com.fasterxml.jackson.databind.DeserializationContext;
21 import com.fasterxml.jackson.databind.JsonNode;
22 import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
23 import com.fasterxml.jackson.databind.node.NullNode;
24
25 import net.avcompris.commons3.databeans.DataBeans;
26 import net.avcompris.commons3.types.DateTimeHolder;
27
28 public final class DataBeanDeserializer<T> extends StdDeserializer<T> {
29
30 private static final long serialVersionUID = 8617288345903422915L;
31
32 private final Class<T> dataBeanClass;
33
34 public DataBeanDeserializer(final Class<T> dataBeanClass) {
35
36 super(checkNotNull(dataBeanClass, "dataBeanClass"));
37
38 checkArgument(dataBeanClass.isInterface(),
39 "dataBeanClass should be an interface, but was: %s", dataBeanClass.getName());
40
41 this.dataBeanClass = dataBeanClass;
42 }
43
44 public static <U> DataBeanDeserializer<U> createDeserializer(final Class<U> clazz) {
45
46 return new DataBeanDeserializer<U>(clazz);
47 }
48
49 private static boolean hasParameterAnnotation(final Method method, final int paramIndex,
50 final Class<? extends Annotation> annotationClass) {
51
52
53
54
55 if (method.isDefault()) {
56
57 final Method outermostMethod;
58
59 try {
60
61 outermostMethod = method.getDeclaringClass().getMethod(method.getName(), method.getParameterTypes());
62
63 } catch (final NoSuchMethodException e) {
64
65 throw new RuntimeException(e);
66 }
67
68 if (!outermostMethod.isDefault()) {
69
70 return hasParameterAnnotation(outermostMethod, paramIndex, annotationClass);
71 }
72 }
73
74 for (final Annotation annotation : method.getParameterAnnotations()[paramIndex]) {
75
76 if (annotationClass.isInstance(annotation)) {
77
78 return true;
79 }
80 }
81
82 return false;
83 }
84
85 @Override
86 public T deserialize(final JsonParser parser, final DeserializationContext context) throws IOException {
87
88 checkNotNull(parser, "parser");
89 checkNotNull(context, "context");
90
91 final T dataBean = DataBeans.instantiate(dataBeanClass);
92
93 final ObjectCodec codec = parser.getCodec();
94
95 final JsonNode node = codec.readTree(parser);
96
97 for (final Method method : dataBeanClass.getMethods()) {
98
99 final String methodName = method.getName();
100
101 if (!methodName.startsWith("set")) {
102 continue;
103 }
104
105 final Class<?>[] paramTypes = method.getParameterTypes();
106
107 if (paramTypes == null || paramTypes.length != 1) {
108 continue;
109 }
110
111 final Class<?> paramType = paramTypes[0];
112
113 final boolean isNullable;
114
115 if (hasParameterAnnotation(method, 0, Nullable.class)) {
116
117 isNullable = true;
118
119 } else {
120
121 isNullable = false;
122 }
123
124 final String fieldName = uncapitalize(methodName.substring(3));
125
126 final JsonNode fieldNode = node.get(fieldName);
127
128 if (fieldNode == null || fieldNode instanceof NullNode) {
129
130 if (isNullable) {
131 continue;
132 }
133
134 throw new IOException("Non-@Nullable field is missing for DataBean: " + dataBeanClass.getName() + "."
135 + fieldName + ": " + fieldNode);
136 }
137
138 final Object value;
139
140 if (Boolean.class.equals(paramType) || boolean.class.equals(paramType)) {
141
142 try {
143
144 value = Boolean.parseBoolean(fieldNode.asText());
145
146 } catch (final NumberFormatException e) {
147 throw new IOException("Cannot parse boolean field for DataBean: " + dataBeanClass.getName() + "."
148 + fieldName + ": " + fieldNode);
149 }
150
151 } else if (Integer.class.equals(paramType) || int.class.equals(paramType)) {
152
153 try {
154
155 value = Integer.parseInt(fieldNode.asText());
156
157 } catch (final NumberFormatException e) {
158 throw new IOException("Cannot parse int field for DataBean: " + dataBeanClass.getName() + "."
159 + fieldName + ": " + fieldNode);
160 }
161
162 } else if (Short.class.equals(paramType) || short.class.equals(paramType)) {
163
164 try {
165
166 value = Short.parseShort(fieldNode.asText());
167
168 } catch (final NumberFormatException e) {
169 throw new IOException("Cannot parse short field for DataBean: " + dataBeanClass.getName() + "."
170 + fieldName + ": " + fieldNode);
171 }
172
173 } else if (Long.class.equals(paramType) || long.class.equals(paramType)) {
174
175 try {
176
177 value = Long.parseLong(fieldNode.asText());
178
179 } catch (final NumberFormatException e) {
180 throw new IOException("Cannot parse long field for DataBean: " + dataBeanClass.getName() + "."
181 + fieldName + ": " + fieldNode);
182 }
183
184 } else if (String.class.equals(paramType)) {
185
186 value = fieldNode.asText();
187
188 } else if (DateTimeHolder.class.equals(paramType)) {
189
190 final DateTime dateTime;
191
192 try {
193
194 dateTime = DateTime.parse(fieldNode.get("dateTime").asText());
195
196 } catch (final IllegalArgumentException e) {
197 throw new IOException("Cannot parse DateTime field for DataBean: " + dataBeanClass.getName() + "."
198 + fieldName + ": " + fieldNode);
199 }
200
201
202
203
204
205 value = toDateTimeHolder(dateTime);
206
207 } else {
208
209 final JsonParser subParser = fieldNode.traverse();
210
211 final JsonToken valueToken = subParser.nextToken();
212
213 if (valueToken == JsonToken.VALUE_NULL) {
214
215 if (isNullable) {
216
217 value = null;
218
219 } else {
220
221 throw new IOException("Non-@Nullable field is missing for DataBean: " + dataBeanClass.getName()
222 + "." + fieldName + ": " + valueToken);
223 }
224
225 } else {
226
227 subParser.setCodec(codec);
228
229 value = context.readValue(subParser, paramType);
230 }
231 }
232
233 method.setAccessible(true);
234
235 try {
236
237 method.invoke(dataBean, value);
238
239 } catch (final InvocationTargetException e) {
240
241 e.printStackTrace();
242
243 final Throwable targetException = e.getTargetException();
244
245 if (targetException == null) {
246 throw new RuntimeException(e);
247 } else if (targetException instanceof Error) {
248 throw (Error) targetException;
249 } else if (targetException instanceof RuntimeException) {
250 throw (RuntimeException) targetException;
251 } else {
252 throw new RuntimeException(targetException);
253 }
254
255 } catch (final IllegalAccessException e) {
256
257 e.printStackTrace();
258
259 throw new RuntimeException(e);
260 }
261 }
262
263 return dataBean;
264 }
265 }