
org.mozilla.javascript.JavaMembers Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of rhino Show documentation
Show all versions of rhino Show documentation
Rhino is an open-source implementation of JavaScript written entirely in Java. It is typically
embedded into Java applications to provide scripting to end users.
The newest version!
/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.javascript;
import static java.lang.reflect.Modifier.isProtected;
import static java.lang.reflect.Modifier.isPublic;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessControlContext;
import java.security.AllPermission;
import java.security.Permission;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
/**
* @author Mike Shaver
* @author Norris Boyd
* @see NativeJavaObject
* @see NativeJavaClass
*/
class JavaMembers {
private static final boolean STRICT_REFLECTIVE_ACCESS = isModularJava();
private static final Permission allPermission = new AllPermission();
JavaMembers(Scriptable scope, Class> cl) {
this(scope, cl, false);
}
JavaMembers(Scriptable scope, Class> cl, boolean includeProtected) {
try (Context cx = ContextFactory.getGlobal().enterContext()) {
ClassShutter shutter = cx.getClassShutter();
if (shutter != null && !shutter.visibleToScripts(cl.getName())) {
throw Context.reportRuntimeErrorById("msg.access.prohibited", cl.getName());
}
this.members = new HashMap<>();
this.staticMembers = new HashMap<>();
this.cl = cl;
boolean includePrivate = cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS);
reflect(cx, scope, includeProtected, includePrivate);
}
}
/**
* This method returns true if we are on a "modular" version of Java (Java 11 or up). It does
* not use the SourceVersion class because this is not present on Android.
*/
private static boolean isModularJava() {
try {
Class.class.getMethod("getModule");
return true;
} catch (NoSuchMethodException e) {
return false;
}
}
boolean has(String name, boolean isStatic) {
Map ht = isStatic ? staticMembers : members;
Object obj = ht.get(name);
if (obj != null) {
return true;
}
return findExplicitFunction(name, isStatic) != null;
}
Object get(Scriptable scope, String name, Object javaObject, boolean isStatic) {
Map ht = isStatic ? staticMembers : members;
Object member = ht.get(name);
if (!isStatic && member == null) {
// Try to get static member from instance (LC3)
member = staticMembers.get(name);
}
if (member == null) {
member =
this.getExplicitFunction(
scope, name,
javaObject, isStatic);
if (member == null) return Scriptable.NOT_FOUND;
}
if (member instanceof Scriptable) {
return member;
}
Context cx = Context.getContext();
Object rval;
Class> type;
try {
if (member instanceof BeanProperty) {
BeanProperty bp = (BeanProperty) member;
if (bp.getter == null) return Scriptable.NOT_FOUND;
rval = bp.getter.invoke(javaObject, Context.emptyArgs);
type = bp.getter.method().getReturnType();
} else {
Field field = (Field) member;
rval = field.get(isStatic ? null : javaObject);
type = field.getType();
}
} catch (Exception ex) {
throw Context.throwAsScriptRuntimeEx(ex);
}
// Need to wrap the object before we return it.
scope = ScriptableObject.getTopLevelScope(scope);
return cx.getWrapFactory().wrap(cx, scope, rval, type);
}
void put(Scriptable scope, String name, Object javaObject, Object value, boolean isStatic) {
Map ht = isStatic ? staticMembers : members;
Object member = ht.get(name);
if (!isStatic && member == null) {
// Try to get static member from instance (LC3)
member = staticMembers.get(name);
}
if (member == null) throw reportMemberNotFound(name);
if (member instanceof FieldAndMethods) {
FieldAndMethods fam = (FieldAndMethods) ht.get(name);
member = fam.field;
}
// Is this a bean property "set"?
if (member instanceof BeanProperty) {
BeanProperty bp = (BeanProperty) member;
if (bp.setter == null) {
throw reportMemberNotFound(name);
}
// If there's only one setter or if the value is null, use the
// main setter. Otherwise, let the NativeJavaMethod decide which
// setter to use:
if (bp.setters == null || value == null) {
Class> setType = bp.setter.argTypes[0];
Object[] args = {Context.jsToJava(value, setType)};
try {
bp.setter.invoke(javaObject, args);
} catch (Exception ex) {
throw Context.throwAsScriptRuntimeEx(ex);
}
} else {
Object[] args = {value};
bp.setters.call(
Context.getContext(),
ScriptableObject.getTopLevelScope(scope),
scope,
args);
}
} else {
if (!(member instanceof Field)) {
String str =
(member == null) ? "msg.java.internal.private" : "msg.java.method.assign";
throw Context.reportRuntimeErrorById(str, name);
}
Field field = (Field) member;
Object javaValue = Context.jsToJava(value, field.getType());
try {
field.set(javaObject, javaValue);
} catch (IllegalAccessException accessEx) {
if ((field.getModifiers() & Modifier.FINAL) != 0) {
// treat Java final the same as JavaScript [[READONLY]]
return;
}
throw Context.throwAsScriptRuntimeEx(accessEx);
} catch (IllegalArgumentException argEx) {
throw Context.reportRuntimeErrorById(
"msg.java.internal.field.type",
value.getClass().getName(),
field,
javaObject.getClass().getName());
}
}
}
Object[] getIds(boolean isStatic) {
Map map = isStatic ? staticMembers : members;
return map.keySet().toArray(new Object[0]);
}
static String javaSignature(Class> type) {
if (!type.isArray()) {
return type.getName();
}
int arrayDimension = 0;
do {
++arrayDimension;
type = type.getComponentType();
} while (type.isArray());
String name = type.getName();
String suffix = "[]";
if (arrayDimension == 1) {
return name.concat(suffix);
}
int length = name.length() + arrayDimension * suffix.length();
StringBuilder sb = new StringBuilder(length);
sb.append(name);
while (arrayDimension != 0) {
--arrayDimension;
sb.append(suffix);
}
return sb.toString();
}
static String liveConnectSignature(Class>[] argTypes) {
int N = argTypes.length;
if (N == 0) {
return "()";
}
StringBuilder sb = new StringBuilder();
sb.append('(');
for (int i = 0; i != N; ++i) {
if (i != 0) {
sb.append(',');
}
sb.append(javaSignature(argTypes[i]));
}
sb.append(')');
return sb.toString();
}
private MemberBox findExplicitFunction(String name, boolean isStatic) {
int sigStart = name.indexOf('(');
if (sigStart < 0) {
return null;
}
Map ht = isStatic ? staticMembers : members;
MemberBox[] methodsOrCtors = null;
boolean isCtor = (isStatic && sigStart == 0);
if (isCtor) {
// Explicit request for an overloaded constructor
methodsOrCtors = ctors.methods;
} else {
// Explicit request for an overloaded method
String trueName = name.substring(0, sigStart);
Object obj = ht.get(trueName);
if (!isStatic && obj == null) {
// Try to get static member from instance (LC3)
obj = staticMembers.get(trueName);
}
if (obj instanceof NativeJavaMethod) {
NativeJavaMethod njm = (NativeJavaMethod) obj;
methodsOrCtors = njm.methods;
}
}
if (methodsOrCtors != null) {
for (MemberBox methodsOrCtor : methodsOrCtors) {
Class>[] type = methodsOrCtor.argTypes;
String sig = liveConnectSignature(type);
if (sigStart + sig.length() == name.length()
&& name.regionMatches(sigStart, sig, 0, sig.length())) {
return methodsOrCtor;
}
}
}
return null;
}
private Object getExplicitFunction(
Scriptable scope, String name, Object javaObject, boolean isStatic) {
Map ht = isStatic ? staticMembers : members;
Object member = null;
MemberBox methodOrCtor = findExplicitFunction(name, isStatic);
if (methodOrCtor != null) {
Scriptable prototype = ScriptableObject.getFunctionPrototype(scope);
if (methodOrCtor.isCtor()) {
NativeJavaConstructor fun = new NativeJavaConstructor(methodOrCtor);
fun.setPrototype(prototype);
member = fun;
ht.put(name, fun);
} else {
String trueName = methodOrCtor.getName();
member = ht.get(trueName);
if (member instanceof NativeJavaMethod
&& ((NativeJavaMethod) member).methods.length > 1) {
NativeJavaMethod fun = new NativeJavaMethod(methodOrCtor, name);
fun.setPrototype(prototype);
ht.put(name, fun);
member = fun;
}
}
}
return member;
}
/**
* Retrieves mapping of methods to accessible methods for a class. In case the class is not
* public, retrieves methods with same signature as its public methods from public superclasses
* and interfaces (if they exist). Basically upcasts every method to the nearest accessible
* method.
*/
private Method[] discoverAccessibleMethods(
Class> clazz, boolean includeProtected, boolean includePrivate) {
Map map = new HashMap<>();
discoverAccessibleMethods(clazz, map, includeProtected, includePrivate);
return map.values().toArray(new Method[0]);
}
@SuppressWarnings("deprecation")
private void discoverAccessibleMethods(
Class> clazz,
Map map,
boolean includeProtected,
boolean includePrivate) {
if (isPublic(clazz.getModifiers()) || includePrivate) {
try {
if (includeProtected || includePrivate) {
while (clazz != null) {
try {
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
int mods = method.getModifiers();
if (isPublic(mods) || isProtected(mods) || includePrivate) {
Method registered = registerMethod(map, method);
// We don't want to replace the deprecated method here
// because it is not available on Android.
if (includePrivate && !registered.isAccessible()) {
registered.setAccessible(true);
}
}
}
Class>[] interfaces = clazz.getInterfaces();
for (Class> intface : interfaces) {
discoverAccessibleMethods(
intface, map, includeProtected, includePrivate);
}
clazz = clazz.getSuperclass();
} catch (SecurityException e) {
// Some security settings (i.e., applets) disallow
// access to Class.getDeclaredMethods. Fall back to
// Class.getMethods.
discoverPublicMethods(clazz, map);
break; // getMethods gets superclass methods, no
// need to loop any more
}
}
} else {
discoverPublicMethods(clazz, map);
}
return;
} catch (SecurityException e) {
Context.reportWarning(
"Could not discover accessible methods of class "
+ clazz.getName()
+ " due to lack of privileges, "
+ "attemping superclasses/interfaces.");
// Fall through and attempt to discover superclass/interface
// methods
}
}
Class>[] interfaces = clazz.getInterfaces();
for (Class> intface : interfaces) {
discoverAccessibleMethods(intface, map, includeProtected, includePrivate);
}
Class> superclass = clazz.getSuperclass();
if (superclass != null) {
discoverAccessibleMethods(superclass, map, includeProtected, includePrivate);
}
}
void discoverPublicMethods(Class> clazz, Map map) {
Method[] methods = clazz.getMethods();
for (Method method : methods) {
registerMethod(map, method);
}
}
static Method registerMethod(Map map, Method method) {
MethodSignature sig = new MethodSignature(method);
// Array may contain methods with same parameter signature but different return value!
// (which is allowed in bytecode, but not in JLS) we will take the best method
return map.merge(sig, method, JavaMembers::getMoreConcreteMethod);
}
private static Method getMoreConcreteMethod(Method oldValue, Method newValue) {
if (oldValue.getReturnType().equals(newValue.getReturnType())) {
return oldValue; // same return type. Do not overwrite existing method
} else if (oldValue.getReturnType().isAssignableFrom(newValue.getReturnType())) {
return newValue; // more concrete return type. Replace method
} else {
return oldValue;
}
}
static final class MethodSignature {
private final String name;
private final Class>[] args;
private MethodSignature(String name, Class>[] args) {
this.name = name;
this.args = args;
}
MethodSignature(Method method) {
this(method.getName(), method.getParameterTypes());
}
@Override
public boolean equals(Object o) {
if (o instanceof MethodSignature) {
MethodSignature ms = (MethodSignature) o;
return ms.name.equals(name) && Arrays.equals(args, ms.args);
}
return false;
}
@Override
public int hashCode() {
return name.hashCode() ^ args.length;
}
}
@SuppressWarnings("unchecked")
private void reflect(
Context cx, Scriptable scope, boolean includeProtected, boolean includePrivate) {
// We reflect methods first, because we want overloaded field/method
// names to be allocated to the NativeJavaMethod before the field
// gets in the way.
Method[] methods = discoverAccessibleMethods(cl, includeProtected, includePrivate);
for (Method method : methods) {
int mods = method.getModifiers();
boolean isStatic = Modifier.isStatic(mods);
Map ht = isStatic ? staticMembers : members;
String name = method.getName();
Object value = ht.get(name);
if (value == null) {
ht.put(name, method);
} else {
ArrayList
© 2015 - 2025 Weber Informatics LLC | Privacy Policy