|
1 // Copyright (c) 2009 The Chromium Authors. All rights reserved. |
|
2 // Use of this source code is governed by a BSD-style license that can be |
|
3 // found in the LICENSE file. |
|
4 |
|
5 package org.chromium.sdk.internal.protocolparser.dynamicimpl; |
|
6 |
|
7 import java.lang.reflect.Constructor; |
|
8 import java.lang.reflect.InvocationHandler; |
|
9 import java.lang.reflect.InvocationTargetException; |
|
10 import java.lang.reflect.Method; |
|
11 import java.lang.reflect.Proxy; |
|
12 import java.util.List; |
|
13 import java.util.Map; |
|
14 import java.util.Set; |
|
15 |
|
16 import org.chromium.sdk.internal.protocolparser.JsonProtocolModelParseException; |
|
17 import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException; |
|
18 import org.json.simple.JSONObject; |
|
19 |
|
20 /** |
|
21 * The instance of this class corresponds to a particular json type. Primarily it serves |
|
22 * as a factory for dynamic proxy/{@link ObjectData}, but also plays a role of type |
|
23 * descriptor object. |
|
24 */ |
|
25 class TypeHandler<T> { |
|
26 private final Class<T> typeClass; |
|
27 private Constructor<? extends T> proxyClassConstructor = null; |
|
28 |
|
29 /** Size of array that holds type-specific instance data. */ |
|
30 private final int fieldArraySize; |
|
31 |
|
32 /** Method implementation for dynamic proxy. */ |
|
33 private final Map<Method, MethodHandler> methodHandlerMap; |
|
34 |
|
35 /** Loaders that should read values and save them in field array on parse time. */ |
|
36 private final List<FieldLoader> fieldLoaders; |
|
37 |
|
38 /** Set of parsers that non-lazyly check that all fields read OK. */ |
|
39 private final EagerFieldParser eagerFieldParser; |
|
40 |
|
41 /** Holds the data about recognizing subtypes. */ |
|
42 private final AlgebraicCasesData algCasesData; |
|
43 |
|
44 /** Full set of allowed field names. Should be used to check that JSON object is well-formed. */ |
|
45 private final Set<String> closedNameSet = null; |
|
46 |
|
47 /** Subtype aspects of the type or null */ |
|
48 private final SubtypeAspect subtypeAspect; |
|
49 |
|
50 TypeHandler(Class<T> typeClass, RefToType<?> jsonSuperClass, int fieldArraySize, |
|
51 Map<Method, MethodHandler> methodHandlerMap, |
|
52 List<FieldLoader> fieldLoaders, |
|
53 List<FieldCondition> fieldConditions, EagerFieldParser eagerFieldParser, |
|
54 AlgebraicCasesData algCasesData) { |
|
55 this.typeClass = typeClass; |
|
56 this.fieldArraySize = fieldArraySize; |
|
57 this.methodHandlerMap = methodHandlerMap; |
|
58 this.fieldLoaders = fieldLoaders; |
|
59 this.eagerFieldParser = eagerFieldParser; |
|
60 this.algCasesData = algCasesData; |
|
61 if (jsonSuperClass == null) { |
|
62 if (!fieldConditions.isEmpty()) { |
|
63 throw new IllegalArgumentException(); |
|
64 } |
|
65 this.subtypeAspect = new AbsentSubtypeAspect(); |
|
66 } else { |
|
67 this.subtypeAspect = new ExistingSubtypeAspect(jsonSuperClass, fieldConditions); |
|
68 } |
|
69 } |
|
70 |
|
71 public Class<T> getTypeClass() { |
|
72 return typeClass; |
|
73 } |
|
74 |
|
75 public ObjectData parse(Object input, ObjectData superObjectData) |
|
76 throws JsonProtocolParseException { |
|
77 try { |
|
78 subtypeAspect.checkSuperObject(superObjectData); |
|
79 |
|
80 Map<?, ?> jsonProperties = null; |
|
81 if (input instanceof JSONObject) { |
|
82 jsonProperties = (JSONObject) input; |
|
83 } |
|
84 |
|
85 ObjectData objectData = new ObjectData(this, input, fieldArraySize, superObjectData); |
|
86 if (!fieldLoaders.isEmpty() && jsonProperties == null) { |
|
87 throw new JsonProtocolParseException("JSON object input expected"); |
|
88 } |
|
89 |
|
90 for (FieldLoader fieldLoader : fieldLoaders) { |
|
91 String fieldName = fieldLoader.getFieldName(); |
|
92 Object value = jsonProperties.get(fieldName); |
|
93 boolean hasValue; |
|
94 if (value == null) { |
|
95 hasValue = jsonProperties.containsKey(fieldName); |
|
96 } else { |
|
97 hasValue = true; |
|
98 } |
|
99 fieldLoader.parse(hasValue, value, objectData); |
|
100 } |
|
101 |
|
102 if (closedNameSet != null) { |
|
103 for (Object fieldNameObject : jsonProperties.keySet()) { |
|
104 if (!closedNameSet.contains(fieldNameObject)) { |
|
105 throw new JsonProtocolParseException("Unexpected field " + fieldNameObject); |
|
106 } |
|
107 } |
|
108 } |
|
109 |
|
110 parseObjectSubtype(objectData, jsonProperties, input); |
|
111 |
|
112 final boolean checkLazyParsedFields = false; |
|
113 if (checkLazyParsedFields) { |
|
114 eagerFieldParser.parseAllFields(objectData); |
|
115 } |
|
116 wrapInProxy(objectData, methodHandlerMap); |
|
117 return objectData; |
|
118 } catch (JsonProtocolParseException e) { |
|
119 throw new JsonProtocolParseException("Failed to parse type " + getTypeClass().getName(), e); |
|
120 } |
|
121 } |
|
122 |
|
123 public T parseRoot(Object input) throws JsonProtocolParseException { |
|
124 ObjectData baseData = parseRootImpl(input); |
|
125 return typeClass.cast(baseData.getProxy()); |
|
126 } |
|
127 |
|
128 public ObjectData parseRootImpl(Object input) throws JsonProtocolParseException { |
|
129 return subtypeAspect.parseFromSuper(input); |
|
130 } |
|
131 |
|
132 SubtypeSupport getSubtypeSupport() { |
|
133 return subtypeAspect; |
|
134 } |
|
135 |
|
136 @SuppressWarnings("unchecked") |
|
137 <S> TypeHandler<S> cast(Class<S> typeClass) { |
|
138 if (this.typeClass != this.typeClass) { |
|
139 throw new RuntimeException(); |
|
140 } |
|
141 return (TypeHandler<S>)this; |
|
142 } |
|
143 |
|
144 static abstract class SubtypeSupport { |
|
145 abstract void setSubtypeCaster(SubtypeCaster subtypeCaster) |
|
146 throws JsonProtocolModelParseException; |
|
147 abstract void checkHasSubtypeCaster() throws JsonProtocolModelParseException; |
|
148 } |
|
149 |
|
150 private void parseObjectSubtype(ObjectData objectData, Map<?, ?> jsonProperties, Object input) |
|
151 throws JsonProtocolParseException { |
|
152 if (algCasesData == null) { |
|
153 return; |
|
154 } |
|
155 if (!algCasesData.isManualChoose()) { |
|
156 if (jsonProperties == null) { |
|
157 throw new JsonProtocolParseException( |
|
158 "JSON object input expected for non-manual subtyping"); |
|
159 } |
|
160 int code = -1; |
|
161 for (int i = 0; i < algCasesData.getSubtypes().size(); i++) { |
|
162 TypeHandler<?> nextSubtype = algCasesData.getSubtypes().get(i).get(); |
|
163 boolean ok = nextSubtype.subtypeAspect.checkConditions(jsonProperties); |
|
164 if (ok) { |
|
165 if (code == -1) { |
|
166 code = i; |
|
167 } else { |
|
168 throw new JsonProtocolParseException("More than one case match"); |
|
169 } |
|
170 } |
|
171 } |
|
172 if (code == -1) { |
|
173 if (!algCasesData.hasDefaultCase()) { |
|
174 throw new JsonProtocolParseException("Not a singe case matches"); |
|
175 } |
|
176 } else { |
|
177 ObjectData fieldData = |
|
178 algCasesData.getSubtypes().get(code).get().parse(input, objectData); |
|
179 objectData.getFieldArray()[algCasesData.getVariantValueFieldPos()] = fieldData; |
|
180 } |
|
181 objectData.getFieldArray()[algCasesData.getVariantCodeFieldPos()] = |
|
182 Integer.valueOf(code); |
|
183 } |
|
184 } |
|
185 |
|
186 /** |
|
187 * Encapsulate subtype aspects of the type. |
|
188 */ |
|
189 private static abstract class SubtypeAspect extends SubtypeSupport { |
|
190 abstract void checkSuperObject(ObjectData superObjectData) throws JsonProtocolParseException; |
|
191 abstract ObjectData parseFromSuper(Object input) throws JsonProtocolParseException; |
|
192 abstract boolean checkConditions(Map<?, ?> jsonProperties) throws JsonProtocolParseException; |
|
193 } |
|
194 |
|
195 private class AbsentSubtypeAspect extends SubtypeAspect { |
|
196 @Override |
|
197 void checkSuperObject(ObjectData superObjectData) throws JsonProtocolParseException { |
|
198 if (superObjectData != null) { |
|
199 throw new JsonProtocolParseException("super object is not expected"); |
|
200 } |
|
201 } |
|
202 @Override |
|
203 boolean checkConditions(Map<?, ?> jsonProperties) throws JsonProtocolParseException { |
|
204 throw new JsonProtocolParseException("Not a subtype: " + typeClass.getName()); |
|
205 } |
|
206 @Override |
|
207 ObjectData parseFromSuper(Object input) throws JsonProtocolParseException { |
|
208 return TypeHandler.this.parse(input, null); |
|
209 } |
|
210 @Override |
|
211 void checkHasSubtypeCaster() { |
|
212 } |
|
213 @Override |
|
214 void setSubtypeCaster(SubtypeCaster subtypeCaster) throws JsonProtocolModelParseException { |
|
215 throw new JsonProtocolModelParseException("Not a subtype: " + typeClass.getName()); |
|
216 } |
|
217 } |
|
218 |
|
219 private class ExistingSubtypeAspect extends SubtypeAspect { |
|
220 private final RefToType<?> jsonSuperClass; |
|
221 |
|
222 /** Set of conditions that check whether this type conforms as subtype. */ |
|
223 private final List<FieldCondition> fieldConditions; |
|
224 |
|
225 /** The helper that casts base type instance to instance of our type */ |
|
226 private SubtypeCaster subtypeCaster = null; |
|
227 |
|
228 private ExistingSubtypeAspect(RefToType<?> jsonSuperClass, |
|
229 List<FieldCondition> fieldConditions) { |
|
230 this.jsonSuperClass = jsonSuperClass; |
|
231 this.fieldConditions = fieldConditions; |
|
232 } |
|
233 |
|
234 @Override |
|
235 boolean checkConditions(Map<?, ?> map) throws JsonProtocolParseException { |
|
236 for (FieldCondition condition : fieldConditions) { |
|
237 String name = condition.getPropertyName(); |
|
238 Object value = map.get(name); |
|
239 boolean hasValue; |
|
240 if (value == null) { |
|
241 hasValue = map.containsKey(name); |
|
242 } else { |
|
243 hasValue = true; |
|
244 } |
|
245 boolean conditionRes = condition.checkValue(hasValue, value); |
|
246 if (!conditionRes) { |
|
247 return false; |
|
248 } |
|
249 } |
|
250 return true; |
|
251 } |
|
252 |
|
253 @Override |
|
254 void checkSuperObject(ObjectData superObjectData) throws JsonProtocolParseException { |
|
255 if (jsonSuperClass == null) { |
|
256 return; |
|
257 } |
|
258 if (!jsonSuperClass.getTypeClass().isAssignableFrom( |
|
259 superObjectData.getTypeHandler().getTypeClass())) { |
|
260 throw new JsonProtocolParseException("Unexpected type of super object"); |
|
261 } |
|
262 } |
|
263 |
|
264 @Override |
|
265 ObjectData parseFromSuper(Object input) throws JsonProtocolParseException { |
|
266 ObjectData base = jsonSuperClass.get().parseRootImpl(input); |
|
267 ObjectData subtypeObject = subtypeCaster.getSubtypeObjectData(base); |
|
268 if (subtypeObject == null) { |
|
269 throw new JsonProtocolParseException("Failed to get subtype object while parsing"); |
|
270 } |
|
271 return subtypeObject; |
|
272 } |
|
273 @Override |
|
274 void checkHasSubtypeCaster() throws JsonProtocolModelParseException { |
|
275 if (this.subtypeCaster == null) { |
|
276 throw new JsonProtocolModelParseException("Subtype caster should have been set in " + |
|
277 typeClass.getName()); |
|
278 } |
|
279 } |
|
280 |
|
281 @Override |
|
282 void setSubtypeCaster(SubtypeCaster subtypeCaster) throws JsonProtocolModelParseException { |
|
283 if (jsonSuperClass == null) { |
|
284 throw new JsonProtocolModelParseException(typeClass.getName() + |
|
285 " does not have supertype declared" + |
|
286 " (accessed from " + subtypeCaster.getBaseType().getName() + ")"); |
|
287 } |
|
288 if (subtypeCaster.getBaseType() != jsonSuperClass.getTypeClass()) { |
|
289 throw new JsonProtocolModelParseException("Wrong base type in " + typeClass.getName() + |
|
290 ", expected " + subtypeCaster.getBaseType().getName()); |
|
291 } |
|
292 if (subtypeCaster.getSubtype() != typeClass) { |
|
293 throw new JsonProtocolModelParseException("Wrong subtype"); |
|
294 } |
|
295 if (this.subtypeCaster != null) { |
|
296 throw new JsonProtocolModelParseException("Subtype caster is already set"); |
|
297 } |
|
298 this.subtypeCaster = subtypeCaster; |
|
299 } |
|
300 } |
|
301 |
|
302 private void wrapInProxy(ObjectData data, Map<Method, MethodHandler> methodHandlerMap) { |
|
303 InvocationHandler handler = new JsonInvocationHandler(data, methodHandlerMap); |
|
304 T proxy = createProxy(handler); |
|
305 data.initProxy(proxy); |
|
306 } |
|
307 |
|
308 @SuppressWarnings("unchecked") |
|
309 private T createProxy(InvocationHandler invocationHandler) { |
|
310 if (proxyClassConstructor == null) { |
|
311 Class<?>[] interfaces = new Class<?>[] { typeClass }; |
|
312 Class<?> proxyClass = Proxy.getProxyClass(getClass().getClassLoader(), interfaces); |
|
313 Constructor<?> c; |
|
314 try { |
|
315 c = proxyClass.getConstructor(InvocationHandler.class); |
|
316 } catch (NoSuchMethodException e) { |
|
317 throw new RuntimeException(e); |
|
318 } |
|
319 proxyClassConstructor = (Constructor<? extends T>) c; |
|
320 } |
|
321 try { |
|
322 return proxyClassConstructor.newInstance(invocationHandler); |
|
323 } catch (InstantiationException e) { |
|
324 throw new RuntimeException(e); |
|
325 } catch (IllegalAccessException e) { |
|
326 throw new RuntimeException(e); |
|
327 } catch (InvocationTargetException e) { |
|
328 throw new RuntimeException(e); |
|
329 } |
|
330 } |
|
331 |
|
332 static abstract class EagerFieldParser { |
|
333 abstract void parseAllFields(ObjectData objectData) throws JsonProtocolParseException; |
|
334 } |
|
335 |
|
336 static abstract class AlgebraicCasesData { |
|
337 abstract int getVariantCodeFieldPos(); |
|
338 abstract int getVariantValueFieldPos(); |
|
339 abstract boolean hasDefaultCase(); |
|
340 abstract List<RefToType<?>> getSubtypes(); |
|
341 abstract boolean isManualChoose(); |
|
342 } |
|
343 } |