// Copyright (c) 2011 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.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.chromium.sdk.internal.protocolparser.JsonParseMethod;
import org.chromium.sdk.internal.protocolparser.JsonParserRoot;
import org.chromium.sdk.internal.protocolparser.JsonProtocolModelParseException;
import org.chromium.sdk.internal.protocolparser.JsonProtocolParseException;
import org.chromium.sdk.internal.protocolparser.dynamicimpl.JavaCodeGenerator.ClassScope;
import static org.chromium.sdk.util.BasicUtil.*;
import org.json.simple.JSONObject;
/**
* Dynamic implementation of user 'root' interface to parser.
* @param <R> 'root' interface type
* @see JsonParserRoot
*/
class ParserRootImpl<R> {
private final Class<R> rootClass;
private final InvocationHandlerImpl invocationHandler;
private final R instance;
ParserRootImpl(Class<R> rootClass, Map<Class<?>, TypeHandler<?>> type2TypeHandler)
throws JsonProtocolModelParseException {
this.rootClass = rootClass;
ParseInterfaceSession session = new ParseInterfaceSession(type2TypeHandler);
session.run(rootClass);
this.invocationHandler = session.createInvocationHandler();
Object result = Proxy.newProxyInstance(rootClass.getClassLoader(),
new Class<?>[] { rootClass }, invocationHandler);
this.instance = (R) result;
}
R getInstance() {
return instance;
}
private static class ParseInterfaceSession {
private final Map<Class<?>, TypeHandler<?>> type2TypeHandler;
private final Set<Class<?>> visitedInterfaces = new HashSet<Class<?>>(1);
private final Map<Method, MethodDelegate> methodMap = new HashMap<Method, MethodDelegate>();
ParseInterfaceSession(Map<Class<?>, TypeHandler<?>> type2TypeHandler) {
this.type2TypeHandler = type2TypeHandler;
}
void run(Class<?> clazz) throws JsonProtocolModelParseException {
parseInterfaceRecursive(clazz);
for (Method method : BaseHandlersLibrary.OBJECT_METHODS) {
methodMap.put(method, new SelfCallDelegate(method));
}
}
private void parseInterfaceRecursive(Class<?> clazz) throws JsonProtocolModelParseException {
if (containsSafe(visitedInterfaces, clazz)) {
return;
}
visitedInterfaces.add(clazz);
if (!clazz.isInterface()) {
throw new JsonProtocolModelParseException(
"Parser root type must be an interface: " + clazz);
}
JsonParserRoot jsonParserRoot = clazz.getAnnotation(JsonParserRoot.class);
if (jsonParserRoot == null) {
throw new JsonProtocolModelParseException(
JsonParserRoot.class.getCanonicalName() + " annotation is expected in " + clazz);
}
for (Method m : clazz.getMethods()) {
JsonParseMethod jsonParseMethod = m.getAnnotation(JsonParseMethod.class);
if (jsonParseMethod == null) {
throw new JsonProtocolModelParseException(
JsonParseMethod.class.getCanonicalName() + " annotation is expected in " + clazz);
}
Class<?>[] exceptionTypes = m.getExceptionTypes();
if (exceptionTypes.length > 1) {
throw new JsonProtocolModelParseException("Too many exception declared in " + m);
}
if (exceptionTypes.length < 1 || exceptionTypes[0] != JsonProtocolParseException.class) {
throw new JsonProtocolModelParseException(
JsonProtocolParseException.class.getCanonicalName() +
" exception must be declared in " + m);
}
Type returnType = m.getGenericReturnType();
TypeHandler<?> typeHandler = type2TypeHandler.get(returnType);
if (typeHandler == null) {
throw new JsonProtocolModelParseException("Unknown return type in " + m);
}
Type[] arguments = m.getGenericParameterTypes();
if (arguments.length != 1) {
throw new JsonProtocolModelParseException("Exactly one argument is expected in " + m);
}
Type argument = arguments[0];
MethodDelegate delegate;
if (argument == JSONObject.class) {
delegate = new ParseDelegate(typeHandler);
} else if (argument == Object.class) {
delegate = new ParseDelegate(typeHandler);
} else {
throw new JsonProtocolModelParseException("Unrecognized argument type in " + m);
}
methodMap.put(m, delegate);
}
for (Type baseType : clazz.getGenericInterfaces()) {
if (baseType instanceof Class == false) {
throw new JsonProtocolModelParseException("Base interface must be class in " + clazz);
}
Class<?> baseClass = (Class<?>) baseType;
parseInterfaceRecursive(baseClass);
}
}
InvocationHandlerImpl createInvocationHandler() {
return new InvocationHandlerImpl(methodMap);
}
}
private static class InvocationHandlerImpl implements InvocationHandler {
private final Map<Method, MethodDelegate> map;
InvocationHandlerImpl(Map<Method, MethodDelegate> map) {
this.map = map;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return getSafe(map, method).invoke(proxy, this, args);
}
public void writeStaticMethodJava(ClassScope scope) {
for (Map.Entry<Method, MethodDelegate> en : map.entrySet()) {
en.getValue().writeStaticMethodJava(scope, en.getKey());
}
}
}
private static abstract class MethodDelegate {
abstract Object invoke(Object proxy, InvocationHandlerImpl invocationHandlerImpl,
Object[] args) throws Throwable;
abstract void writeStaticMethodJava(ClassScope scope, Method key);
}
private static class ParseDelegate extends MethodDelegate {
private final TypeHandler<?> typeHandler;
ParseDelegate(TypeHandler<?> typeHandler) {
this.typeHandler = typeHandler;
}
@Override
Object invoke(Object proxy, InvocationHandlerImpl invocationHandlerImpl, Object[] args)
throws JsonProtocolParseException {
Object obj = args[0];
return typeHandler.parseRoot(obj);
}
@Override
void writeStaticMethodJava(ClassScope scope, Method method) {
MethodHandler.writeMethodDeclarationJava(scope, method, STATIC_METHOD_PARAM_NAME_LIST);
scope.append(JavaCodeGenerator.Util.THROWS_CLAUSE + " {\n");
scope.indentRight();
scope.startLine("return " + scope.getTypeImplReference(typeHandler) + ".parse(" +
STATIC_METHOD_PARAM_NAME + ");\n");
scope.indentLeft();
scope.startLine("}\n");
scope.append("\n");
}
private static final String STATIC_METHOD_PARAM_NAME = "obj";
private static final List<String> STATIC_METHOD_PARAM_NAME_LIST =
Collections.singletonList(STATIC_METHOD_PARAM_NAME);
}
private static class SelfCallDelegate extends MethodDelegate {
private final Method method;
SelfCallDelegate(Method method) {
this.method = method;
}
@Override
Object invoke(Object proxy, InvocationHandlerImpl invocationHandlerImpl, Object[] args)
throws Throwable {
try {
return method.invoke(invocationHandlerImpl, args);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
@Override
void writeStaticMethodJava(ClassScope scope, Method method) {
}
}
public Class<R> getType() {
return rootClass;
}
public void writeStaticMethodJava(ClassScope rootClassScope) {
invocationHandler.writeStaticMethodJava(rootClassScope);
}
}