19f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li/*
29f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Copyright (C) 2016 Google Inc.
39f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li *
49f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Licensed under the Apache License, Version 2.0 (the "License"); you may not
59f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * use this file except in compliance with the License. You may obtain a copy of
69f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * the License at
79f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li *
89f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * http://www.apache.org/licenses/LICENSE-2.0
99f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li *
109f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * Unless required by applicable law or agreed to in writing, software
119f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
129f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
139f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * License for the specific language governing permissions and limitations under
149f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * the License.
159f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */
169f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
179f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Lipackage com.googlecode.android_scripting.rpc;
189f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
199f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.content.Intent;
209f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.net.Uri;
219f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.os.Bundle;
229f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport android.os.Parcelable;
239f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
249f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.facade.AndroidFacade;
259f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.jsonrpc.RpcReceiver;
269f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.jsonrpc.RpcReceiverManager;
279f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport com.googlecode.android_scripting.util.VisibleForTesting;
289f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
299f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.lang.annotation.Annotation;
309f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.lang.reflect.Constructor;
319f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.lang.reflect.Method;
329f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.lang.reflect.ParameterizedType;
339f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.lang.reflect.Type;
349f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.util.ArrayList;
359f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.util.Collection;
369f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.util.HashMap;
379f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.util.List;
389f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport java.util.Map;
399f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
409f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport org.json.JSONArray;
419f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport org.json.JSONException;
429f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Liimport org.json.JSONObject;
439f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
449f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li/**
459f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * An adapter that wraps {@code Method}.
469f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li *
479f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li * @author igor.v.karp@gmail.com (Igor Karp)
489f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li */
499f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Lipublic final class MethodDescriptor {
509f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private static final Map<Class<?>, Converter<?>> sConverters = populateConverters();
519f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
529f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private final Method mMethod;
539f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private final Class<? extends RpcReceiver> mClass;
549f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
559f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public MethodDescriptor(Class<? extends RpcReceiver> clazz, Method method) {
569f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mClass = clazz;
579f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    mMethod = method;
589f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
599f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
609f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  @Override
619f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public String toString() {
629f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return mMethod.getDeclaringClass().getCanonicalName() + "." + mMethod.getName();
639f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
649f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
659f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /** Collects all methods with {@code RPC} annotation from given class. */
669f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public static Collection<MethodDescriptor> collectFrom(Class<? extends RpcReceiver> clazz) {
679f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    List<MethodDescriptor> descriptors = new ArrayList<MethodDescriptor>();
689f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    for (Method method : clazz.getMethods()) {
699f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (method.isAnnotationPresent(Rpc.class)) {
709f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        descriptors.add(new MethodDescriptor(clazz, method));
719f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
729f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
739f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return descriptors;
749f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
759f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
769f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /**
779f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * Invokes the call that belongs to this object with the given parameters. Wraps the response
789f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * (possibly an exception) in a JSONObject.
799f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *
809f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @param parameters
819f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *          {@code JSONArray} containing the parameters
829f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @return result
839f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @throws Throwable
849f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   */
859f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public Object invoke(RpcReceiverManager manager, final JSONArray parameters) throws Throwable {
869f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
879f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    final Type[] parameterTypes = getGenericParameterTypes();
889f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    final Object[] args = new Object[parameterTypes.length];
899f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    final Annotation annotations[][] = getParameterAnnotations();
909f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
919f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (parameters.length() > args.length) {
929f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      throw new RpcError("Too many parameters specified.");
939f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
949f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
959f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    for (int i = 0; i < args.length; i++) {
969f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      final Type parameterType = parameterTypes[i];
979f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (i < parameters.length()) {
989f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        args[i] = convertParameter(parameters, i, parameterType);
999f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (MethodDescriptor.hasDefaultValue(annotations[i])) {
1009f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        args[i] = MethodDescriptor.getDefaultValue(parameterType, annotations[i]);
1019f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else {
1029f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        throw new RpcError("Argument " + (i + 1) + " is not present");
1039f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
1049f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
1059f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
1069f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return invoke(manager, args);
1079f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
1089f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
1099f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /**
1109f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * Invokes the call that belongs to this object with the given parameters. Wraps the response
1119f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * (possibly an exception) in a JSONObject.
1129f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *
1139f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @param parameters {@code Bundle} containing the parameters
1149f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @return result
1159f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @throws Throwable
1169f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   */
1179f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public Object invoke(RpcReceiverManager manager, final Bundle parameters) throws Throwable {
1189f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    final Annotation annotations[][] = getParameterAnnotations();
1199f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    final Class<?>[] parameterTypes = getMethod().getParameterTypes();
1209f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    final Object[] args = new Object[parameterTypes.length];
1219f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
1229f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    for (int i = 0; i < parameterTypes.length; i++) {
1239f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      Class<?> parameterType = parameterTypes[i];
1249f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      String parameterName = getName(annotations[i]);
1259f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (i < parameterTypes.length) {
1269f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        args[i] = convertParameter(parameters, parameterType, parameterName);
1279f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (MethodDescriptor.hasDefaultValue(annotations[i])) {
1289f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        args[i] = MethodDescriptor.getDefaultValue(parameterType, annotations[i]);
1299f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else {
1309f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        throw new RpcError("Argument " + (i + 1) + " is not present");
1319f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
1329f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
1339f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return invoke(manager, args);
1349f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
1359f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
1369f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private Object invoke(RpcReceiverManager manager, Object[] args) throws Throwable{
1379f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    Object result = null;
1389f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    try {
1399f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      result = manager.invoke(mClass, mMethod, args);
1409f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    } catch (Throwable t) {
1419f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      throw t.getCause();
1429f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
1439f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return result;
1449f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
1459f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
1469f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /**
1479f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * Converts a parameter from JSON into a Java Object.
1489f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *
1499f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @return TODO
1509f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   */
1519f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  // TODO(damonkohler): This signature is a bit weird (auto-refactored). The obvious alternative
1529f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  // would be to work on one supplied parameter and return the converted parameter. However, that's
1539f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  // problematic because you lose the ability to call the getXXX methods on the JSON array.
1549f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  @VisibleForTesting
1559f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  static Object convertParameter(final JSONArray parameters, int index, Type type)
1569f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      throws JSONException, RpcError {
1579f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    try {
1589f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      // Log.d("sl4a", parameters.toString());
1599f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      // Log.d("sl4a", type.toString());
1609f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      // We must handle null and numbers explicitly because we cannot magically cast them. We
1619f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      // also need to convert implicitly from numbers to bools.
1629f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (parameters.isNull(index)) {
1639f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return null;
1649f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (type == Boolean.class) {
1659f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        try {
1669f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          return parameters.getBoolean(index);
1679f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        } catch (JSONException e) {
1689f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          return new Boolean(parameters.getInt(index) != 0);
1699f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        }
1709f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (type == Long.class) {
1719f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return parameters.getLong(index);
1729f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (type == Double.class) {
1739f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return parameters.getDouble(index);
1749f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (type == Integer.class) {
1759f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return parameters.getInt(index);
1769f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (type == Intent.class) {
1779f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return buildIntent(parameters.getJSONObject(index));
1789f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (type == Integer[].class) {
1799f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        JSONArray list = parameters.getJSONArray(index);
1809f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        Integer[] result = new Integer[list.length()];
1819f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        for (int i = 0; i < list.length(); i++) {
1829f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          result[i] = list.getInt(i);
1839f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        }
1849f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return result;
185e0bd13ca95066315343234b1dcc6bffe734ffaa3Jakub Pawlowski      } else if (type == byte[].class) {
186e0bd13ca95066315343234b1dcc6bffe734ffaa3Jakub Pawlowski        JSONArray list = parameters.getJSONArray(index);
187e0bd13ca95066315343234b1dcc6bffe734ffaa3Jakub Pawlowski        byte[] result = new byte[list.length()];
188e0bd13ca95066315343234b1dcc6bffe734ffaa3Jakub Pawlowski        for (int i = 0; i < list.length(); i++) {
189e0bd13ca95066315343234b1dcc6bffe734ffaa3Jakub Pawlowski          result[i] = (byte)list.getInt(i);
190e0bd13ca95066315343234b1dcc6bffe734ffaa3Jakub Pawlowski        }
191e0bd13ca95066315343234b1dcc6bffe734ffaa3Jakub Pawlowski        return result;
1929f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (type == String[].class) {
1939f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        JSONArray list = parameters.getJSONArray(index);
1949f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        String[] result = new String[list.length()];
1959f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        for (int i = 0; i < list.length(); i++) {
1969f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          result[i] = list.getString(i);
1979f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        }
1989f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return result;
1999f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (type == JSONObject.class) {
2009f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          return parameters.getJSONObject(index);
2019f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else {
2029f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        // Magically cast the parameter to the right Java type.
2039f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return ((Class<?>) type).cast(parameters.get(index));
2049f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
2059f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    } catch (ClassCastException e) {
2069f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      throw new RpcError("Argument " + (index + 1) + " should be of type "
2079f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          + ((Class<?>) type).getSimpleName() + ".");
2089f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2099f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2109f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2119f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private Object convertParameter(Bundle bundle, Class<?> type, String name) {
2129f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    Object param = null;
2139f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (type.isAssignableFrom(Boolean.class)) {
2149f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      param = bundle.getBoolean(name, false);
2159f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2169f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (type.isAssignableFrom(Boolean[].class)) {
2179f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      param = bundle.getBooleanArray(name);
2189f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2199f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (type.isAssignableFrom(String.class)) {
2209f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      param = bundle.getString(name);
2219f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2229f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (type.isAssignableFrom(String[].class)) {
2239f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      param = bundle.getStringArray(name);
2249f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2259f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (type.isAssignableFrom(Integer.class)) {
2269f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      param = bundle.getInt(name, 0);
2279f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2289f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (type.isAssignableFrom(Integer[].class)) {
2299f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      param = bundle.getIntArray(name);
2309f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2319f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (type.isAssignableFrom(Bundle.class)) {
2329f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      param = bundle.getBundle(name);
2339f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2349f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (type.isAssignableFrom(Parcelable.class)) {
2359f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      param = bundle.getParcelable(name);
2369f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2379f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (type.isAssignableFrom(Parcelable[].class)) {
2389f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      param = bundle.getParcelableArray(name);
2399f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2409f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (type.isAssignableFrom(Intent.class)) {
2419f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      param = bundle.getParcelable(name);
2429f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2439f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return param;
2449f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2459f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2469f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public static Object buildIntent(JSONObject jsonObject) throws JSONException {
2479f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    Intent intent = new Intent();
2489f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (jsonObject.has("action")) {
2499f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      intent.setAction(jsonObject.getString("action"));
2509f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2519f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (jsonObject.has("data") && jsonObject.has("type")) {
2529f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      intent.setDataAndType(Uri.parse(jsonObject.optString("data", null)),
2539f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          jsonObject.optString("type", null));
2549f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    } else if (jsonObject.has("data")) {
2559f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      intent.setData(Uri.parse(jsonObject.optString("data", null)));
2569f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    } else if (jsonObject.has("type")) {
2579f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      intent.setType(jsonObject.optString("type", null));
2589f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2599f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (jsonObject.has("packagename") && jsonObject.has("classname")) {
2609f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      intent.setClassName(jsonObject.getString("packagename"), jsonObject.getString("classname"));
2619f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2629f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (jsonObject.has("flags")) {
2639f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      intent.setFlags(jsonObject.getInt("flags"));
2649f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2659f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (!jsonObject.isNull("extras")) {
2669f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      AndroidFacade.putExtrasFromJsonObject(jsonObject.getJSONObject("extras"), intent);
2679f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2689f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (!jsonObject.isNull("categories")) {
2699f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      JSONArray categories = jsonObject.getJSONArray("categories");
2709f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      for (int i = 0; i < categories.length(); i++) {
2719f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        intent.addCategory(categories.getString(i));
2729f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
2739f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2749f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return intent;
2759f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2769f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2779f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public Method getMethod() {
2789f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return mMethod;
2799f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2809f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2819f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public Class<? extends RpcReceiver> getDeclaringClass() {
2829f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return mClass;
2839f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2849f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2859f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public String getName() {
2869f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (mMethod.isAnnotationPresent(RpcName.class)) {
2879f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      return mMethod.getAnnotation(RpcName.class).name();
2889f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
2899f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return mMethod.getName();
2909f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2919f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2929f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public Type[] getGenericParameterTypes() {
2939f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return mMethod.getGenericParameterTypes();
2949f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2959f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
2969f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public Annotation[][] getParameterAnnotations() {
2979f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return mMethod.getParameterAnnotations();
2989f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
2999f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3009f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /**
3019f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * Returns a human-readable help text for this RPC, based on annotations in the source code.
3029f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *
3039f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @return derived help string
3049f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   */
3059f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public String getHelp() {
3069f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    StringBuilder helpBuilder = new StringBuilder();
3079f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    Rpc rpcAnnotation = mMethod.getAnnotation(Rpc.class);
3089f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3099f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    helpBuilder.append(mMethod.getName());
3109f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    helpBuilder.append("(");
3119f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    final Class<?>[] parameterTypes = mMethod.getParameterTypes();
3129f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    final Type[] genericParameterTypes = mMethod.getGenericParameterTypes();
3139f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    final Annotation[][] annotations = mMethod.getParameterAnnotations();
3149f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    for (int i = 0; i < parameterTypes.length; i++) {
3159f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (i == 0) {
3169f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        helpBuilder.append("\n  ");
3179f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else {
3189f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        helpBuilder.append(",\n  ");
3199f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
3209f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3219f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      helpBuilder.append(getHelpForParameter(genericParameterTypes[i], annotations[i]));
3229f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
3239f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    helpBuilder.append(")\n\n");
3249f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    helpBuilder.append(rpcAnnotation.description());
3259f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (!rpcAnnotation.returns().equals("")) {
3269f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      helpBuilder.append("\n");
3279f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      helpBuilder.append("\nReturns:\n  ");
3289f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      helpBuilder.append(rpcAnnotation.returns());
3299f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
3309f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3319f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (mMethod.isAnnotationPresent(RpcStartEvent.class)) {
3329f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      String eventName = mMethod.getAnnotation(RpcStartEvent.class).value();
3339f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      helpBuilder.append(String.format("\n\nGenerates \"%s\" events.", eventName));
3349f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
3359f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3369f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (mMethod.isAnnotationPresent(RpcDeprecated.class)) {
3379f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      String replacedBy = mMethod.getAnnotation(RpcDeprecated.class).value();
3389f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      String release = mMethod.getAnnotation(RpcDeprecated.class).release();
3399f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      helpBuilder.append(String.format("\n\nDeprecated in %s! Please use %s instead.", release,
3409f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          replacedBy));
3419f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
3429f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3439f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return helpBuilder.toString();
3449f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
3459f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3469f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /**
3479f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * Returns the help string for one particular parameter. This respects optional parameters.
3489f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *
3499f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @param parameterType
3509f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *          (generic) type of the parameter
3519f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @param annotations
3529f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *          annotations of the parameter, may be null
3539f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @return string describing the parameter based on source code annotations
3549f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   */
3559f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private static String getHelpForParameter(Type parameterType, Annotation[] annotations) {
3569f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    StringBuilder result = new StringBuilder();
3579f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3589f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    appendTypeName(result, parameterType);
3599f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    result.append(" ");
3609f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    result.append(getName(annotations));
3619f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (hasDefaultValue(annotations)) {
3629f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      result.append("[optional");
3639f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (hasExplicitDefaultValue(annotations)) {
3649f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        result.append(", default " + getDefaultValue(parameterType, annotations));
3659f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
3669f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      result.append("]");
3679f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
3689f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3699f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    String description = getDescription(annotations);
3709f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (description.length() > 0) {
3719f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      result.append(": ");
3729f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      result.append(description);
3739f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
3749f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3759f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return result.toString();
3769f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
3779f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3789f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /**
3799f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * Appends the name of the given type to the {@link StringBuilder}.
3809f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *
3819f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @param builder
3829f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *          string builder to append to
3839f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @param type
3849f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *          type whose name to append
3859f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   */
3869f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private static void appendTypeName(final StringBuilder builder, final Type type) {
3879f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (type instanceof Class<?>) {
3889f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      builder.append(((Class<?>) type).getSimpleName());
3899f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    } else {
3909f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      ParameterizedType parametrizedType = (ParameterizedType) type;
3919f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      builder.append(((Class<?>) parametrizedType.getRawType()).getSimpleName());
3929f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      builder.append("<");
3939f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
3949f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      Type[] arguments = parametrizedType.getActualTypeArguments();
3959f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      for (int i = 0; i < arguments.length; i++) {
3969f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        if (i > 0) {
3979f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          builder.append(", ");
3989f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        }
3999f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        appendTypeName(builder, arguments[i]);
4009f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
4019f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      builder.append(">");
4029f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
4039f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
4049f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
4059f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /**
4069f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * Returns parameter descriptors suitable for the RPC call text representation.
4079f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *
4089f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * <p>
4099f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * Uses parameter value, default value or name, whatever is available first.
4109f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *
4119f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @return an array of parameter descriptors
4129f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   */
4139f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public ParameterDescriptor[] getParameterValues(String[] values) {
4149f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    Type[] parameterTypes = mMethod.getGenericParameterTypes();
4159f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    Annotation[][] parametersAnnotations = mMethod.getParameterAnnotations();
4169f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    ParameterDescriptor[] parameters = new ParameterDescriptor[parametersAnnotations.length];
4179f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    for (int index = 0; index < parameters.length; index++) {
4189f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      String value;
4199f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (index < values.length) {
4209f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        value = values[index];
4219f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (hasDefaultValue(parametersAnnotations[index])) {
4229f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        Object defaultValue = getDefaultValue(parameterTypes[index], parametersAnnotations[index]);
4239f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        if (defaultValue == null) {
4249f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          value = null;
4259f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        } else {
4269f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          value = String.valueOf(defaultValue);
4279f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        }
4289f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else {
4299f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        value = getName(parametersAnnotations[index]);
4309f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
4319f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      parameters[index] = new ParameterDescriptor(value, parameterTypes[index]);
4329f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
4339f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return parameters;
4349f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
4359f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
4369f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /**
4379f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * Returns parameter hints.
4389f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *
4399f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @return an array of parameter hints
4409f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   */
4419f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public String[] getParameterHints() {
4429f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    Annotation[][] parametersAnnotations = mMethod.getParameterAnnotations();
4439f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    String[] hints = new String[parametersAnnotations.length];
4449f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    for (int index = 0; index < hints.length; index++) {
4459f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      String name = getName(parametersAnnotations[index]);
4469f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      String description = getDescription(parametersAnnotations[index]);
4479f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      String hint = "No paramenter description.";
4489f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (!name.equals("") && !description.equals("")) {
4499f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        hint = name + ": " + description;
4509f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (!name.equals("")) {
4519f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        hint = name;
4529f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (!description.equals("")) {
4539f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        hint = description;
4549f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
4559f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      hints[index] = hint;
4569f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
4579f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return hints;
4589f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
4599f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
4609f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /**
4619f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * Extracts the formal parameter name from an annotation.
4629f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *
4639f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @param annotations
4649f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *          the annotations of the parameter
4659f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @return the formal name of the parameter
4669f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   */
4679f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private static String getName(Annotation[] annotations) {
4689f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    for (Annotation a : annotations) {
4699f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (a instanceof RpcParameter) {
4709f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return ((RpcParameter) a).name();
4719f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
4729f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
4739f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    throw new IllegalStateException("No parameter name");
4749f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
4759f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
4769f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /**
4779f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * Extracts the parameter description from its annotations.
4789f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *
4799f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @param annotations
4809f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *          the annotations of the parameter
4819f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @return the description of the parameter
4829f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   */
4839f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private static String getDescription(Annotation[] annotations) {
4849f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    for (Annotation a : annotations) {
4859f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (a instanceof RpcParameter) {
4869f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return ((RpcParameter) a).description();
4879f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
4889f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
4899f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    throw new IllegalStateException("No parameter description");
4909f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
4919f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
4929f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /**
4939f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * Returns the default value for a specific parameter.
4949f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *
4959f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @param parameterType
4969f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *          parameterType
4979f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @param annotations
4989f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *          annotations of the parameter
4999f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   */
5009f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public static Object getDefaultValue(Type parameterType, Annotation[] annotations) {
5019f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    for (Annotation a : annotations) {
5029f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (a instanceof RpcDefault) {
5039f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        RpcDefault defaultAnnotation = (RpcDefault) a;
5049f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        Converter<?> converter = converterFor(parameterType, defaultAnnotation.converter());
5059f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return converter.convert(defaultAnnotation.value());
5069f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      } else if (a instanceof RpcOptional) {
5079f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return null;
5089f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
5099f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
5109f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    throw new IllegalStateException("No default value for " + parameterType);
5119f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
5129f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
5139f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  @SuppressWarnings("rawtypes")
5149f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private static Converter<?> converterFor(Type parameterType,
5159f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      Class<? extends Converter> converterClass) {
5169f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    if (converterClass == Converter.class) {
5179f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      Converter<?> converter = sConverters.get(parameterType);
5189f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (converter == null) {
5199f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        throw new IllegalArgumentException("No predefined converter found for " + parameterType);
5209f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
5219f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      return converter;
5229f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
5239f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    try {
5249f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      Constructor<?> constructor = converterClass.getConstructor(new Class<?>[0]);
5259f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      return (Converter<?>) constructor.newInstance(new Object[0]);
5269f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    } catch (Exception e) {
5279f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      throw new IllegalArgumentException("Cannot create converter from "
5289f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          + converterClass.getCanonicalName());
5299f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
5309f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
5319f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
5329f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /**
5339f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * Determines whether or not this parameter has default value.
5349f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *
5359f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @param annotations
5369f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *          annotations of the parameter
5379f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   */
5389f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  public static boolean hasDefaultValue(Annotation[] annotations) {
5399f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    for (Annotation a : annotations) {
5409f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (a instanceof RpcDefault || a instanceof RpcOptional) {
5419f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return true;
5429f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
5439f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
5449f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return false;
5459f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
5469f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
5479f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /**
5489f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * Returns whether the default value is specified for a specific parameter.
5499f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *
5509f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   * @param annotations
5519f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   *          annotations of the parameter
5529f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li   */
5539f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  @VisibleForTesting
5549f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  static boolean hasExplicitDefaultValue(Annotation[] annotations) {
5559f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    for (Annotation a : annotations) {
5569f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      if (a instanceof RpcDefault) {
5579f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return true;
5589f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
5599f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    }
5609f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return false;
5619f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
5629f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li
5639f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  /** Returns the converters for {@code String}, {@code Integer} and {@code Boolean}. */
5649f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  private static Map<Class<?>, Converter<?>> populateConverters() {
5659f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    Map<Class<?>, Converter<?>> converters = new HashMap<Class<?>, Converter<?>>();
5669f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    converters.put(String.class, new Converter<String>() {
5679f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      @Override
5689f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      public String convert(String value) {
5699f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        return value;
5709f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
5719f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    });
5729f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    converters.put(Integer.class, new Converter<Integer>() {
5739f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      @Override
5749f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      public Integer convert(String input) {
5759f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        try {
5769f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          return Integer.decode(input);
5779f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        } catch (NumberFormatException e) {
5789f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          throw new IllegalArgumentException("'" + input + "' is not an integer");
5799f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        }
5809f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
5819f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    });
5829f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    converters.put(Boolean.class, new Converter<Boolean>() {
5839f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      @Override
5849f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      public Boolean convert(String input) {
5859f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        if (input == null) {
5869f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          return null;
5879f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        }
5889f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        input = input.toLowerCase();
5899f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        if (input.equals("true")) {
5909f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          return Boolean.TRUE;
5919f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        }
5929f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        if (input.equals("false")) {
5939f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li          return Boolean.FALSE;
5949f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        }
5959f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li        throw new IllegalArgumentException("'" + input + "' is not a boolean");
5969f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li      }
5979f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    });
5989f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li    return converters;
5999f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li  }
6009f32db87b486c93a0ea71eb1781ee45676b8bf8bXin Li}
601