1// Copyright 2014 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#include "content/browser/android/java/java_method.h"
6
7#include "base/android/jni_android.h"
8#include "base/android/jni_string.h"
9#include "base/lazy_instance.h"
10#include "base/memory/singleton.h"
11#include "content/browser/android/java/jni_helper.h"
12
13using base::android::AttachCurrentThread;
14using base::android::ConvertJavaStringToUTF8;
15using base::android::GetClass;
16using base::android::MethodID;
17using base::android::ScopedJavaGlobalRef;
18using base::android::ScopedJavaLocalRef;
19
20namespace content {
21namespace {
22
23const char kGetName[] = "getName";
24const char kGetDeclaringClass[] = "getDeclaringClass";
25const char kGetModifiers[] = "getModifiers";
26const char kGetParameterTypes[] = "getParameterTypes";
27const char kGetReturnType[] = "getReturnType";
28const char kIntegerReturningBoolean[] = "(I)Z";
29const char kIsStatic[] = "isStatic";
30const char kJavaLangClass[] = "java/lang/Class";
31const char kJavaLangReflectMethod[] = "java/lang/reflect/Method";
32const char kJavaLangReflectModifier[] = "java/lang/reflect/Modifier";
33const char kReturningInteger[] = "()I";
34const char kReturningJavaLangClass[] = "()Ljava/lang/Class;";
35const char kReturningJavaLangClassArray[] = "()[Ljava/lang/Class;";
36const char kReturningJavaLangString[] = "()Ljava/lang/String;";
37
38struct ModifierClassTraits :
39      public base::internal::LeakyLazyInstanceTraits<ScopedJavaGlobalRef<
40                                                         jclass> > {
41  static ScopedJavaGlobalRef<jclass>* New(void* instance) {
42    JNIEnv* env = AttachCurrentThread();
43    // Use placement new to initialize our instance in our preallocated space.
44    return new (instance) ScopedJavaGlobalRef<jclass>(
45        GetClass(env, kJavaLangReflectModifier));
46  }
47};
48
49base::LazyInstance<ScopedJavaGlobalRef<jclass>, ModifierClassTraits>
50    g_java_lang_reflect_modifier_class = LAZY_INSTANCE_INITIALIZER;
51
52std::string BinaryNameToJNISignature(const std::string& binary_name,
53                                     JavaType* type) {
54  DCHECK(type);
55  *type = JavaType::CreateFromBinaryName(binary_name);
56  return type->JNISignature();
57}
58
59}  // namespace
60
61JavaMethod::JavaMethod(const base::android::JavaRef<jobject>& method)
62    : java_method_(method),
63      have_calculated_num_parameters_(false),
64      id_(NULL) {
65  JNIEnv* env = AttachCurrentThread();
66  // On construction, we do nothing except get the name. Everything else is
67  // done lazily.
68  ScopedJavaLocalRef<jstring> name(env, static_cast<jstring>(
69      env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName(
70          env,
71          kJavaLangReflectMethod,
72          kGetName,
73          kReturningJavaLangString))));
74  name_ = ConvertJavaStringToUTF8(name);
75}
76
77JavaMethod::~JavaMethod() {
78}
79
80size_t JavaMethod::num_parameters() const {
81  EnsureNumParametersIsSetUp();
82  return num_parameters_;
83}
84
85bool JavaMethod::is_static() const {
86  EnsureTypesAndIDAreSetUp();
87  return is_static_;
88}
89
90const JavaType& JavaMethod::parameter_type(size_t index) const {
91  EnsureTypesAndIDAreSetUp();
92  return parameter_types_[index];
93}
94
95const JavaType& JavaMethod::return_type() const {
96  EnsureTypesAndIDAreSetUp();
97  return return_type_;
98}
99
100jmethodID JavaMethod::id() const {
101  EnsureTypesAndIDAreSetUp();
102  return id_;
103}
104
105void JavaMethod::EnsureNumParametersIsSetUp() const {
106  if (have_calculated_num_parameters_) {
107    return;
108  }
109  have_calculated_num_parameters_ = true;
110
111  // The number of parameters will be used frequently when determining
112  // whether to call this method. We don't get the ID etc until actually
113  // required.
114  JNIEnv* env = AttachCurrentThread();
115  ScopedJavaLocalRef<jarray> parameters(env, static_cast<jarray>(
116      env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName(
117          env,
118          kJavaLangReflectMethod,
119          kGetParameterTypes,
120          kReturningJavaLangClassArray))));
121  num_parameters_ = env->GetArrayLength(parameters.obj());
122}
123
124void JavaMethod::EnsureTypesAndIDAreSetUp() const {
125  if (id_) {
126    return;
127  }
128
129  // Get the parameters
130  JNIEnv* env = AttachCurrentThread();
131  ScopedJavaLocalRef<jobjectArray> parameters(env, static_cast<jobjectArray>(
132      env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName(
133          env,
134          kJavaLangReflectMethod,
135          kGetParameterTypes,
136          kReturningJavaLangClassArray))));
137  // Usually, this will already have been called.
138  EnsureNumParametersIsSetUp();
139  DCHECK_EQ(num_parameters_,
140            static_cast<size_t>(env->GetArrayLength(parameters.obj())));
141
142  // Java gives us the argument type using an extended version of the 'binary
143  // name'. See
144  // http://download.oracle.com/javase/1.4.2/docs/api/java/lang/Class.html#getName().
145  // If we build the signature now, there's no need to store the binary name
146  // of the arguments. We just store the simple type.
147  std::string signature("(");
148
149  // Form the signature and record the parameter types.
150  parameter_types_.resize(num_parameters_);
151  for (size_t i = 0; i < num_parameters_; ++i) {
152    ScopedJavaLocalRef<jobject> parameter(env, env->GetObjectArrayElement(
153        parameters.obj(), i));
154    ScopedJavaLocalRef<jstring> name(env, static_cast<jstring>(
155        env->CallObjectMethod(parameter.obj(), GetMethodIDFromClassName(
156            env,
157            kJavaLangClass,
158            kGetName,
159            kReturningJavaLangString))));
160    std::string name_utf8 = ConvertJavaStringToUTF8(name);
161    signature += BinaryNameToJNISignature(name_utf8, &parameter_types_[i]);
162  }
163  signature += ")";
164
165  // Get the return type
166  ScopedJavaLocalRef<jclass> clazz(env, static_cast<jclass>(
167      env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName(
168          env,
169          kJavaLangReflectMethod,
170          kGetReturnType,
171          kReturningJavaLangClass))));
172  ScopedJavaLocalRef<jstring> name(env, static_cast<jstring>(
173      env->CallObjectMethod(clazz.obj(), GetMethodIDFromClassName(
174          env,
175          kJavaLangClass,
176          kGetName,
177          kReturningJavaLangString))));
178  signature += BinaryNameToJNISignature(ConvertJavaStringToUTF8(name),
179                                        &return_type_);
180
181  // Determine whether the method is static.
182  jint modifiers = env->CallIntMethod(
183      java_method_.obj(), GetMethodIDFromClassName(env,
184                                                   kJavaLangReflectMethod,
185                                                   kGetModifiers,
186                                                   kReturningInteger));
187  is_static_ = env->CallStaticBooleanMethod(
188      g_java_lang_reflect_modifier_class.Get().obj(),
189      MethodID::Get<MethodID::TYPE_STATIC>(
190          env, g_java_lang_reflect_modifier_class.Get().obj(), kIsStatic,
191          kIntegerReturningBoolean),
192      modifiers);
193
194  // Get the ID for this method.
195  ScopedJavaLocalRef<jclass> declaring_class(env, static_cast<jclass>(
196      env->CallObjectMethod(java_method_.obj(), GetMethodIDFromClassName(
197          env,
198          kJavaLangReflectMethod,
199          kGetDeclaringClass,
200          kReturningJavaLangClass))));
201  id_ = is_static_ ?
202      MethodID::Get<MethodID::TYPE_STATIC>(
203          env, declaring_class.obj(), name_.c_str(), signature.c_str()) :
204      MethodID::Get<MethodID::TYPE_INSTANCE>(
205          env, declaring_class.obj(), name_.c_str(), signature.c_str());
206  java_method_.Reset();
207}
208
209}  // namespace content
210