// Copyright (c) 2009 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.sdk.internal.protocolparser.dynamicimpl;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.chromium.sdk.internal.protocolparser.JsonProtocolModelParseException;
import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException;
import org.chromium.sdk.internal.protocolparser.JsonType;
import org.chromium.sdk.internal.protocolparser.dynamicimpl.DynamicParserImpl.VolatileFieldBinding;
import org.chromium.sdk.internal.protocolparser.dynamicimpl.JavaCodeGenerator.ClassScope;
import org.chromium.sdk.internal.protocolparser.dynamicimpl.JavaCodeGenerator.FileScope;
import org.chromium.sdk.internal.protocolparser.dynamicimpl.JavaCodeGenerator.MethodScope;
import org.chromium.sdk.internal.protocolparser.dynamicimpl.JavaCodeGenerator.Util;
import org.json.simple.JSONObject;
/**
* The instance of this class corresponds to a particular json type. Primarily it serves
* as a factory for dynamic proxy/{@link ObjectData}, but also plays a role of type
* descriptor object.
*/
class TypeHandler<T> {
private final Class<T> typeClass;
private Constructor<? extends T> proxyClassConstructor = null;
/** Size of array that holds type-specific instance data. */
private final int fieldArraySize;
private final List<VolatileFieldBinding> volatileFields;
/** Method implementation for dynamic proxy. */
private final Map<Method, MethodHandler> methodHandlerMap;
/** Loaders that should read values and save them in field array on parse time. */
private final List<FieldLoader> fieldLoaders;
/** Set of parsers that non-lazyly check that all fields read OK. */
private final EagerFieldParser eagerFieldParser;
/** Holds the data about recognizing subtypes. */
private final AlgebraicCasesData algCasesData;
/** Full set of allowed field names. Should be used to check that JSON object is well-formed. */
private Set<String> closedNameSet = null;
/** Subtype aspects of the type or null */
private final SubtypeAspect subtypeAspect;
private final boolean requiresJsonObject;
private final boolean checkLazyParsedFields;
TypeHandler(Class<T> typeClass, RefToType<?> jsonSuperClass, int fieldArraySize,
List<VolatileFieldBinding> volatileFields,
Map<Method, MethodHandler> methodHandlerMap,
List<FieldLoader> fieldLoaders,
List<FieldCondition> fieldConditions, EagerFieldParser eagerFieldParser,
AlgebraicCasesData algCasesData, boolean requiresJsonObject,
boolean checkLazyParsedFields) {
this.typeClass = typeClass;
this.fieldArraySize = fieldArraySize;
this.volatileFields = volatileFields;
this.methodHandlerMap = methodHandlerMap;
this.fieldLoaders = fieldLoaders;
this.eagerFieldParser = eagerFieldParser;
this.algCasesData = algCasesData;
this.requiresJsonObject = requiresJsonObject;
this.checkLazyParsedFields = checkLazyParsedFields;
if (jsonSuperClass == null) {
if (!fieldConditions.isEmpty()) {
throw new IllegalArgumentException();
}
this.subtypeAspect = new AbsentSubtypeAspect();
} else {
this.subtypeAspect = new ExistingSubtypeAspect(jsonSuperClass, fieldConditions);
}
}
public Class<T> getTypeClass() {
return typeClass;
}
public ObjectData parse(Object input, ObjectData superObjectData)
throws JsonProtocolParseException {
try {
subtypeAspect.checkSuperObject(superObjectData);
Map<?, ?> jsonProperties = null;
if (input instanceof JSONObject) {
jsonProperties = (JSONObject) input;
}
ObjectData objectData = new ObjectData(this, input, fieldArraySize, volatileFields.size(),
superObjectData);
if (requiresJsonObject && jsonProperties == null) {
throw new JsonProtocolParseException("JSON object input expected");
}
for (FieldLoader fieldLoader : fieldLoaders) {
String fieldName = fieldLoader.getFieldName();
Object value = jsonProperties.get(fieldName);
boolean hasValue;
if (value == null) {
hasValue = jsonProperties.containsKey(fieldName);
} else {
hasValue = true;
}
fieldLoader.parse(hasValue, value, objectData);
}
if (closedNameSet != null) {
for (Object fieldNameObject : jsonProperties.keySet()) {
if (!closedNameSet.contains(fieldNameObject)) {
throw new JsonProtocolParseException("Unexpected field " + fieldNameObject);
}
}
}
parseObjectSubtype(objectData, jsonProperties, input);
if (checkLazyParsedFields) {
eagerFieldParser.parseAllFields(objectData);
}
wrapInProxy(objectData, methodHandlerMap);
return objectData;
} catch (JsonProtocolParseException e) {
throw new JsonProtocolParseException("Failed to parse type " + getTypeClass().getName(), e);
}
}
public T parseRoot(Object input) throws JsonProtocolParseException {
ObjectData baseData = parseRootImpl(input);
return typeClass.cast(baseData.getProxy());
}
public ObjectData parseRootImpl(Object input) throws JsonProtocolParseException {
return subtypeAspect.parseFromSuper(input);
}
SubtypeSupport getSubtypeSupport() {
return subtypeAspect;
}
@SuppressWarnings("unchecked")
<S> TypeHandler<S> cast(Class<S> typeClass) {
if (this.typeClass != typeClass) {
throw new RuntimeException();
}
return (TypeHandler<S>)this;
}
void buildClosedNameSet() {
if (!this.subtypeAspect.isRoot()) {
return;
}
List<Set<String>> namesChain = new ArrayList<Set<String>>(3);
buildClosedNameSetRecursive(namesChain);
}
private void buildClosedNameSetRecursive(List<Set<String>> namesChain) {
Set<String> thisSet = new HashSet<String>();
eagerFieldParser.addAllFieldNames(thisSet);
for (FieldLoader loader : fieldLoaders) {
thisSet.add(loader.getFieldName());
}
if (algCasesData == null) {
JsonType jsonAnnotation = typeClass.getAnnotation(JsonType.class);
if (jsonAnnotation.allowsOtherProperties()) {
return;
}
for (Set<String> set : namesChain) {
thisSet.addAll(set);
}
closedNameSet = thisSet;
} else {
namesChain.add(thisSet);
for (RefToType<?> subtype : algCasesData.getSubtypes()) {
subtype.get().buildClosedNameSetRecursive(namesChain);
}
namesChain.remove(namesChain.size() - 1);
}
}
String getShortName() {
String name = typeClass.getName();
int dotPos = name.lastIndexOf('.');
if (dotPos != -1) {
name = name.substring(dotPos + 1);
}
return "[" + name + "]";
}
static abstract class SubtypeSupport {
abstract void setSubtypeCaster(SubtypeCaster subtypeCaster)
throws JsonProtocolModelParseException;
abstract void checkHasSubtypeCaster() throws JsonProtocolModelParseException;
abstract void writeGetSuperMethodJava(ClassScope scope);
abstract boolean checkConditions(Map<?, ?> jsonProperties) throws JsonProtocolParseException;
}
private void parseObjectSubtype(ObjectData objectData, Map<?, ?> jsonProperties, Object input)
throws JsonProtocolParseException {
if (algCasesData == null) {
return;
}
algCasesData.parseObjectSubtype(objectData, jsonProperties, input);
}
/**
* Encapsulate subtype aspects of the type.
*/
private static abstract class SubtypeAspect extends SubtypeSupport {
abstract void checkSuperObject(ObjectData superObjectData) throws JsonProtocolParseException;
abstract ObjectData parseFromSuper(Object input) throws JsonProtocolParseException;
abstract boolean isRoot();
abstract void writeSuperFieldJava(ClassScope scope);
abstract void writeSuperConstructorParamJava(ClassScope scope);
abstract void writeSuperConstructorInitializationJava(MethodScope scope);
abstract void writeHelperMethodsJava(ClassScope classScope);
abstract void writeParseMethodJava(ClassScope classScope, String valueTypeName,
String inputRef);
}
private class AbsentSubtypeAspect extends SubtypeAspect {
@Override
void checkSuperObject(ObjectData superObjectData) throws JsonProtocolParseException {
if (superObjectData != null) {
throw new JsonProtocolParseException("super object is not expected");
}
}
@Override
boolean checkConditions(Map<?, ?> jsonProperties) throws JsonProtocolParseException {
throw new JsonProtocolParseException("Not a subtype: " + typeClass.getName());
}
@Override
ObjectData parseFromSuper(Object input) throws JsonProtocolParseException {
return TypeHandler.this.parse(input, null);
}
@Override
void checkHasSubtypeCaster() {
}
@Override
void setSubtypeCaster(SubtypeCaster subtypeCaster) throws JsonProtocolModelParseException {
throw new JsonProtocolModelParseException("Not a subtype: " + typeClass.getName());
}
@Override
boolean isRoot() {
return true;
}
@Override void writeGetSuperMethodJava(ClassScope scope) {
}
@Override void writeSuperFieldJava(ClassScope scope) {
}
@Override void writeSuperConstructorParamJava(ClassScope scope) {
}
@Override void writeSuperConstructorInitializationJava(MethodScope scope) {
}
@Override void writeHelperMethodsJava(ClassScope classScope) {
}
@Override void writeParseMethodJava(ClassScope scope, String valueTypeName, String inputRef) {
scope.startLine("return new " + valueTypeName + "(" + inputRef + ");\n");
}
}
private class ExistingSubtypeAspect extends SubtypeAspect {
private final RefToType<?> jsonSuperClass;
/** Set of conditions that check whether this type conforms as subtype. */
private final List<FieldCondition> fieldConditions;
/** The helper that casts base type instance to instance of our type */
private SubtypeCaster subtypeCaster = null;
private ExistingSubtypeAspect(RefToType<?> jsonSuperClass,
List<FieldCondition> fieldConditions) {
this.jsonSuperClass = jsonSuperClass;
this.fieldConditions = fieldConditions;
}
@Override
boolean checkConditions(Map<?, ?> map) throws JsonProtocolParseException {
for (FieldCondition condition : fieldConditions) {
String name = condition.getPropertyName();
Object value = map.get(name);
boolean hasValue;
if (value == null) {
hasValue = map.containsKey(name);
} else {
hasValue = true;
}
boolean conditionRes = condition.checkValue(hasValue, value);
if (!conditionRes) {
return false;
}
}
return true;
}
@Override
void checkSuperObject(ObjectData superObjectData) throws JsonProtocolParseException {
if (jsonSuperClass == null) {
return;
}
if (!jsonSuperClass.getTypeClass().isAssignableFrom(
superObjectData.getTypeHandler().getTypeClass())) {
throw new JsonProtocolParseException("Unexpected type of super object");
}
}
@Override
ObjectData parseFromSuper(Object input) throws JsonProtocolParseException {
ObjectData base = jsonSuperClass.get().parseRootImpl(input);
ObjectData subtypeObject = subtypeCaster.getSubtypeObjectData(base);
if (subtypeObject == null) {
throw new JsonProtocolParseException("Failed to get subtype object while parsing");
}
return subtypeObject;
}
@Override
void checkHasSubtypeCaster() throws JsonProtocolModelParseException {
if (this.subtypeCaster == null) {
throw new JsonProtocolModelParseException("Subtype caster should have been set in " +
typeClass.getName());
}
}
@Override
void setSubtypeCaster(SubtypeCaster subtypeCaster) throws JsonProtocolModelParseException {
if (jsonSuperClass == null) {
throw new JsonProtocolModelParseException(typeClass.getName() +
" does not have supertype declared" +
" (accessed from " + subtypeCaster.getBaseType().getName() + ")");
}
if (subtypeCaster.getBaseType() != jsonSuperClass.getTypeClass()) {
throw new JsonProtocolModelParseException("Wrong base type in " + typeClass.getName() +
", expected " + subtypeCaster.getBaseType().getName());
}
if (subtypeCaster.getSubtype() != typeClass) {
throw new JsonProtocolModelParseException("Wrong subtype");
}
if (this.subtypeCaster != null) {
throw new JsonProtocolModelParseException("Subtype caster is already set");
}
this.subtypeCaster = subtypeCaster;
}
@Override
boolean isRoot() {
return false;
}
@Override
void writeGetSuperMethodJava(ClassScope scope) {
scope.startLine("@Override public " +
jsonSuperClass.get().getTypeClass().getCanonicalName() + " getSuper() {\n");
scope.startLine(" return superTypeValue;\n");
scope.startLine("}\n");
}
@Override
void writeSuperFieldJava(ClassScope scope) {
scope.startLine("private final " + jsonSuperClass.get().getTypeClass().getCanonicalName() +
" superTypeValue;\n");
}
@Override
void writeSuperConstructorParamJava(ClassScope scope) {
scope.append(", " + jsonSuperClass.get().getTypeClass().getCanonicalName() +
" superValueParam");
}
@Override
void writeSuperConstructorInitializationJava(MethodScope scope) {
scope.startLine("this.superTypeValue = superValueParam;\n");
}
@Override
void writeHelperMethodsJava(ClassScope classScope) {
classScope.startLine("public static boolean checkSubtypeConditions(" +
"org.json.simple.JSONObject input)" + Util.THROWS_CLAUSE + " {\n");
MethodScope methodScope = classScope.newMethodScope();
methodScope.indentRight();
for (FieldCondition condition : fieldConditions) {
String name = condition.getPropertyName();
methodScope.startLine("{\n");
methodScope.startLine(" Object value = input.get(\"" + name + "\");\n");
methodScope.startLine(" boolean hasValue;\n");
methodScope.startLine(" if (value == null) {\n");
methodScope.startLine(" hasValue = input.containsKey(\"" + name + "\");\n");
methodScope.startLine(" } else {\n");
methodScope.startLine(" hasValue = true;\n");
methodScope.startLine(" }\n");
condition.writeCheckJava(methodScope, "value", "hasValue", "conditionRes");
methodScope.startLine(" if (!conditionRes) {\n");
methodScope.startLine(" return false;\n");
methodScope.startLine(" }\n");
methodScope.startLine("}\n");
}
methodScope.startLine("return true;\n");
methodScope.indentLeft();
methodScope.startLine("}\n");
}
@Override
void writeParseMethodJava(ClassScope scope, String valueTypeName, String inputRef) {
String superTypeName = scope.getTypeImplReference(jsonSuperClass.get());
scope.startLine(superTypeName + " superTypeValue = " + superTypeName + ".parse(" +
inputRef + ");\n");
this.subtypeCaster.writeJava(scope, valueTypeName, "superTypeValue", "result");
scope.startLine("if (result == null) {\n");
scope.startLine(" throw new " + Util.BASE_PACKAGE + ".JsonProtocolParseException(" +
"\"Failed to get subtype object while parsing\");\n");
scope.startLine("}\n");
scope.startLine("return result;\n");
}
}
private void wrapInProxy(ObjectData data, Map<Method, MethodHandler> methodHandlerMap) {
InvocationHandler handler = new JsonInvocationHandler(data, methodHandlerMap);
T proxy = createProxy(handler);
data.initProxy(proxy);
}
@SuppressWarnings("unchecked")
private T createProxy(InvocationHandler invocationHandler) {
if (proxyClassConstructor == null) {
Class<?>[] interfaces = new Class<?>[] { typeClass };
Class<?> proxyClass = Proxy.getProxyClass(typeClass.getClassLoader(), interfaces);
Constructor<?> c;
try {
c = proxyClass.getConstructor(InvocationHandler.class);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
proxyClassConstructor = (Constructor<? extends T>) c;
}
try {
return proxyClassConstructor.newInstance(invocationHandler);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
static abstract class EagerFieldParser {
abstract void parseAllFields(ObjectData objectData) throws JsonProtocolParseException;
abstract void addAllFieldNames(Set<? super String> output);
}
static abstract class AlgebraicCasesData {
abstract void parseObjectSubtype(ObjectData objectData, Map<?, ?> jsonProperties, Object input)
throws JsonProtocolParseException;
abstract List<RefToType<?>> getSubtypes();
abstract void writeConstructorCodeJava(MethodScope methodScope);
abstract void writeFiledsJava(ClassScope classScope);
}
public void writeStaticClassJava(FileScope fileScope) {
fileScope.startLine("// Type " + this.getTypeClass().getName() + "\n");
String valueImplClassName = fileScope.getTypeImplShortName(this);
String typeClassName = this.getTypeClass().getCanonicalName();
fileScope.startLine("public static class " + valueImplClassName + " extends " +
Util.BASE_PACKAGE + ".implutil.GeneratedCodeLibrary.");
if (requiresJsonObject) {
fileScope.append("JsonValueBase");
} else {
fileScope.append("ObjectValueBase");
}
fileScope.append(" implements " + typeClassName + " {\n");
ClassScope classScope = fileScope.newClassScope();
classScope.indentRight();
classScope.startLine("public static " + valueImplClassName + " parse(Object input)" +
Util.THROWS_CLAUSE + " {\n");
classScope.indentRight();
subtypeAspect.writeParseMethodJava(classScope, valueImplClassName, "input");
classScope.indentLeft();
classScope.startLine("}");
classScope.append("\n");
classScope.startLine(valueImplClassName + "(Object input");
subtypeAspect.writeSuperConstructorParamJava(classScope);
classScope.append(")" + Util.THROWS_CLAUSE + " {\n");
{
MethodScope methodScope = classScope.newMethodScope();
methodScope.indentRight();
methodScope.startLine("super(input);\n");
subtypeAspect.writeSuperConstructorInitializationJava(methodScope);
for (FieldLoader fieldLoader : fieldLoaders) {
String valueRef = methodScope.newMethodScopedName("value");
String hasValueRef = methodScope.newMethodScopedName("hasValue");
String fieldName = fieldLoader.getFieldName();
methodScope.append("\n");
Util.writeReadValueAndHasValue(methodScope, fieldName, "underlying", valueRef,
hasValueRef);
fieldLoader.writeFieldLoadJava(methodScope, valueRef, hasValueRef);
}
if (algCasesData != null) {
algCasesData.writeConstructorCodeJava(methodScope);
}
methodScope.indentLeft();
}
classScope.startLine("}\n");
for (VolatileFieldBinding field : volatileFields) {
field.writeFieldDeclarationJava(classScope);
}
for (FieldLoader loader : this.fieldLoaders) {
loader.writeFieldDeclarationJava(classScope);
}
if (algCasesData != null) {
algCasesData.writeFiledsJava(classScope);
}
subtypeAspect.writeSuperFieldJava(classScope);
for (Map.Entry<Method, MethodHandler> en : this.methodHandlerMap.entrySet()) {
Method m = en.getKey();
MethodHandler methodHandler = en.getValue();
methodHandler.writeMethodImplementationJava(classScope, m);
}
BaseHandlersLibrary.writeBaseMethodsJava(classScope, this);
subtypeAspect.writeHelperMethodsJava(classScope);
classScope.indentLeft();
classScope.startLine("}\n");
fileScope.append("\n");
}
public void writeMapFillJava(ClassScope scope, String mapReference) {
scope.startLine("// Type " + this.getTypeClass().getName() + "\n");
scope.startLine(mapReference + ".put(" + this.getTypeClass().getCanonicalName() +
".class, new " + Util.BASE_PACKAGE + ".implutil.GeneratedCodeLibrary.AbstractType() {\n");
scope.startLine(" @Override public Object parseJson(org.json.simple.JSONObject json)" +
Util.THROWS_CLAUSE + " {\n");
scope.startLine(" return " + scope.getTypeImplShortName(this) + ".parse(json);\n");
scope.startLine(" }\n");
scope.startLine(" @Override public Object parseAnything(Object object)" +
Util.THROWS_CLAUSE + " {\n");
scope.startLine(" return " + scope.getTypeImplShortName(this) + ".parse(object);\n");
scope.startLine(" }\n");
scope.startLine("});\n");
}
}