com.facebook.stetho.inspector.MethodDispatcher.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.stetho.inspector.MethodDispatcher.java

Source

/*
 * Copyright (c) 2014-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */
//
// Copyright 2004-present Facebook. All Rights Reserved.

package com.facebook.stetho.inspector;

import javax.annotation.Nullable;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import com.facebook.stetho.common.ExceptionUtil;
import com.facebook.stetho.common.Util;
import com.facebook.stetho.inspector.jsonrpc.JsonRpcException;
import com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;
import com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;
import com.facebook.stetho.inspector.jsonrpc.protocol.EmptyResult;
import com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcError;
import com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;
import com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;
import com.facebook.stetho.json.ObjectMapper;

import org.json.JSONException;
import org.json.JSONObject;

@ThreadSafe
public class MethodDispatcher {
    @GuardedBy("this")
    private Map<String, MethodDispatchHelper> mMethods;

    private final ObjectMapper mObjectMapper;
    private final Iterable<ChromeDevtoolsDomain> mDomainHandlers;

    public MethodDispatcher(ObjectMapper objectMapper, Iterable<ChromeDevtoolsDomain> domainHandlers) {
        mObjectMapper = objectMapper;
        mDomainHandlers = domainHandlers;
    }

    private synchronized MethodDispatchHelper findMethodDispatcher(String methodName) {
        if (mMethods == null) {
            mMethods = buildDispatchTable(mObjectMapper, mDomainHandlers);
        }
        return mMethods.get(methodName);
    }

    public JSONObject dispatch(JsonRpcPeer peer, String methodName, @Nullable JSONObject params)
            throws JsonRpcException {
        MethodDispatchHelper dispatchHelper = findMethodDispatcher(methodName);
        if (dispatchHelper == null) {
            throw new JsonRpcException(new JsonRpcError(JsonRpcError.ErrorCode.METHOD_NOT_FOUND,
                    "Not implemented: " + methodName, null /* data */));
        }
        try {
            return dispatchHelper.invoke(peer, params);
        } catch (InvocationTargetException e) {
            Throwable cause = e.getCause();
            ExceptionUtil.propagateIfInstanceOf(cause, JsonRpcException.class);
            throw ExceptionUtil.propagate(cause);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (JSONException e) {
            throw new JsonRpcException(
                    new JsonRpcError(JsonRpcError.ErrorCode.INTERNAL_ERROR, e.toString(), null /* data */));
        }
    }

    private static class MethodDispatchHelper {
        private final ObjectMapper mObjectMapper;
        private final ChromeDevtoolsDomain mInstance;
        private final Method mMethod;

        public MethodDispatchHelper(ObjectMapper objectMapper, ChromeDevtoolsDomain instance, Method method) {
            mObjectMapper = objectMapper;
            mInstance = instance;
            mMethod = method;
        }

        public JSONObject invoke(JsonRpcPeer peer, @Nullable JSONObject params)
                throws InvocationTargetException, IllegalAccessException, JSONException, JsonRpcException {
            Object internalResult = mMethod.invoke(mInstance, peer, params);
            if (internalResult == null || internalResult instanceof EmptyResult) {
                return new JSONObject();
            } else {
                JsonRpcResult convertableResult = (JsonRpcResult) internalResult;
                return mObjectMapper.convertValue(convertableResult, JSONObject.class);
            }
        }
    }

    private static Map<String, MethodDispatchHelper> buildDispatchTable(ObjectMapper objectMapper,
            Iterable<ChromeDevtoolsDomain> domainHandlers) {
        Util.throwIfNull(objectMapper);
        HashMap<String, MethodDispatchHelper> methods = new HashMap<String, MethodDispatchHelper>();
        for (ChromeDevtoolsDomain domainHandler : Util.throwIfNull(domainHandlers)) {
            Class<?> handlerClass = domainHandler.getClass();
            String domainName = handlerClass.getSimpleName();

            for (Method method : handlerClass.getDeclaredMethods()) {
                if (isDevtoolsMethod(method)) {
                    MethodDispatchHelper dispatchHelper = new MethodDispatchHelper(objectMapper, domainHandler,
                            method);
                    methods.put(domainName + "." + method.getName(), dispatchHelper);
                }
            }
        }
        return Collections.unmodifiableMap(methods);
    }

    /**
     * Determines if the method is a {@link ChromeDevtoolsMethod}, and validates accordingly
     * if it is.
     *
     * @throws IllegalArgumentException Thrown if it is a {@link ChromeDevtoolsMethod} but
     *     it otherwise fails to satisfy requirements.
     */
    private static boolean isDevtoolsMethod(Method method) throws IllegalArgumentException {
        if (!method.isAnnotationPresent(ChromeDevtoolsMethod.class)) {
            return false;
        } else {
            Class<?> args[] = method.getParameterTypes();
            String methodName = method.getDeclaringClass().getSimpleName() + "." + method.getName();
            Util.throwIfNot(args.length == 2, "%s: expected 2 args, got %s", methodName, args.length);
            Util.throwIfNot(args[0].equals(JsonRpcPeer.class), "%s: expected 1st arg of JsonRpcPeer, got %s",
                    methodName, args[0].getName());
            Util.throwIfNot(args[1].equals(JSONObject.class), "%s: expected 2nd arg of JSONObject, got %s",
                    methodName, args[1].getName());

            Class<?> returnType = method.getReturnType();
            if (!returnType.equals(void.class)) {
                Util.throwIfNot(JsonRpcResult.class.isAssignableFrom(returnType),
                        "%s: expected JsonRpcResult return type, got %s", methodName, returnType.getName());
            }
            return true;
        }
    }
}