diff --git a/rhino/src/main/java/org/mozilla/javascript/Context.java b/rhino/src/main/java/org/mozilla/javascript/Context.java
index 686f1808ee..409346edde 100644
--- a/rhino/src/main/java/org/mozilla/javascript/Context.java
+++ b/rhino/src/main/java/org/mozilla/javascript/Context.java
@@ -347,10 +347,28 @@ public class Context implements Closeable {
* Internationalization API implementation (see https://tc39.github.io/ecma402) can be activated
* using this feature.
*
- * @since 1.7 Release 15
+ * @since 1.7 Release 16
*/
public static final int FEATURE_INTL_402 = 22;
+ /**
+ * Configure whether JavaMembers lazy init off.
+ *
+ *
default is lazy init.
+ *
+ * @since 1.7 Release 16
+ */
+ public static final int FEATURE_JAVAMEMBERS_LAZY_INIT_OFF = 22;
+
+ /**
+ * Configure whether JavaMembers reflect cache off.
+ *
+ *
default is cache on.
+ *
+ * @since 1.7 Release 15
+ */
+ public static final int FEATURE_JAVAMEMBERS_REFLECT_CACHE_OFF = 23;
+
public static final String languageVersionProperty = "language version";
public static final String errorReporterProperty = "error reporter";
diff --git a/rhino/src/main/java/org/mozilla/javascript/JavaMembers.java b/rhino/src/main/java/org/mozilla/javascript/JavaMembers.java
index cefb97100f..799ec2e3e8 100644
--- a/rhino/src/main/java/org/mozilla/javascript/JavaMembers.java
+++ b/rhino/src/main/java/org/mozilla/javascript/JavaMembers.java
@@ -20,10 +20,14 @@
import java.security.Permission;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
/**
* @author Mike Shaver
@@ -37,11 +41,18 @@ class JavaMembers {
private static final Permission allPermission = new AllPermission();
+ private final boolean includePrivate;
+ private final ClassReflectBean cfCache;
+ private final Scriptable javaMemberScope;
+ private final boolean lazyInit;
+ private final boolean reflectCacheOn;
+
JavaMembers(Scriptable scope, Class> cl) {
this(scope, cl, false);
}
JavaMembers(Scriptable scope, Class> cl, boolean includeProtected) {
+ this.javaMemberScope = scope;
try (Context cx = ContextFactory.getGlobal().enterContext()) {
ClassShutter shutter = cx.getClassShutter();
if (shutter != null && !shutter.visibleToScripts(cl.getName())) {
@@ -50,8 +61,10 @@ class JavaMembers {
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);
+ includePrivate = cx.hasFeature(Context.FEATURE_ENHANCED_JAVA_ACCESS);
+ lazyInit = !cx.hasFeature(Context.FEATURE_JAVAMEMBERS_LAZY_INIT_OFF);
+ reflectCacheOn = !cx.hasFeature(Context.FEATURE_JAVAMEMBERS_REFLECT_CACHE_OFF);
+ cfCache = reflect(cx, scope, includeProtected, includePrivate);
}
}
@@ -69,21 +82,15 @@ private static boolean isModularJava() {
}
boolean has(String name, boolean isStatic) {
- Map ht = isStatic ? staticMembers : members;
- Object obj = ht.get(name);
- if (obj != null) {
+ if (cfCache.has(name, isStatic)) {
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);
- }
+ Context cx = Context.getContext();
+ Object member = getMember2(cx, scope, name, isStatic);
if (member == null) {
member =
this.getExplicitFunction(
@@ -94,7 +101,6 @@ Object get(Scriptable scope, String name, Object javaObject, boolean isStatic) {
if (member instanceof Scriptable) {
return member;
}
- Context cx = Context.getContext();
Object rval;
Class> type;
try {
@@ -116,6 +122,257 @@ Object get(Scriptable scope, String name, Object javaObject, boolean isStatic) {
return cx.getWrapFactory().wrap(cx, scope, rval, type);
}
+ private Object getMember2(Context cx, Scriptable scope, String name, boolean isStatic) {
+ Object member = getMember(cx, scope, name, isStatic);
+ if (member == null && !isStatic) {
+ // Try to get static member from instance (LC3)
+ member = getMember(cx, scope, name, true);
+ if (member == null) {
+ return null;
+ }
+ }
+ return member == Scriptable.NOT_FOUND ? null : member;
+ }
+
+ private final Object getMember(
+ final Context cx, final Scriptable scope, final String name, final boolean isStatic) {
+ final Map ht = isStatic ? staticMembers : members;
+ Object member = ht.get(name);
+ if (lazyInit && member == null) {
+ final Object m1 = initFieldAndMethod(cx, name, ht, isStatic);
+ Map props =
+ isStatic ? cfCache.staticBeanProperties : cfCache.instBeanProperties;
+ final String nameComponent = props.get(name);
+ if (nameComponent != null) {
+ member = initBeanProperty(cx, name, nameComponent, ht, isStatic);
+ if (member == null) {
+ member = m1;
+ }
+ } else {
+ member = m1;
+ }
+
+ if (member != null) {
+ ht.put(name, member);
+ }
+ }
+ return member;
+ }
+
+ private static Object[] createBeanProperties(Iterable methods) {
+ final Map cache1 = new HashMap();
+ final Map cache2 = new HashMap();
+ // Now, For each member, make "bean" properties.
+ for (Method m : methods) {
+ String name = m.getName();
+ // Is this a getter?
+ boolean memberIsGetMethod = name.startsWith("get");
+ boolean memberIsSetMethod = name.startsWith("set");
+ boolean memberIsIsMethod = name.startsWith("is");
+ if (memberIsGetMethod || memberIsIsMethod || memberIsSetMethod) {
+ // Double check name component.
+ String nameComponent = name.substring(memberIsIsMethod ? 2 : 3);
+ if (nameComponent.length() == 0) continue;
+
+ // Make the bean property name.
+ String beanPropertyName = nameComponent;
+ char ch0 = nameComponent.charAt(0);
+ if (Character.isUpperCase(ch0)) {
+ if (nameComponent.length() == 1) {
+ beanPropertyName = nameComponent.toLowerCase();
+ } else {
+ char ch1 = nameComponent.charAt(1);
+ if (!Character.isUpperCase(ch1)) {
+ beanPropertyName =
+ Character.toLowerCase(ch0) + nameComponent.substring(1);
+ }
+ }
+ }
+ if (Modifier.isStatic(m.getModifiers())) {
+ cache1.put(beanPropertyName, nameComponent);
+ } else {
+ cache2.put(beanPropertyName, nameComponent);
+ }
+ }
+ }
+ return new Object[] {cache1, cache2};
+ }
+
+ private BeanProperty initBeanProperty(
+ final Context cx,
+ final String beanPropertyName,
+ String nameComponent,
+ Map ht,
+ boolean isStatic) {
+
+ Object v = ht.get(beanPropertyName);
+ if (v != null) {
+ // A private field shouldn't mask a public getter/setter
+ if (!includePrivate
+ || !(v instanceof Member)
+ || !Modifier.isPrivate(((Member) v).getModifiers())) {
+
+ return null;
+ }
+ }
+
+ // Find the getter method, or if there is none, the is-
+ // method.
+ MemberBox getter = null;
+ getter = findGetter(isStatic, ht, "get", nameComponent);
+ // If there was no valid getter, check for an is- method.
+ if (getter == null) {
+ getter = findGetter(isStatic, ht, "is", nameComponent);
+ }
+
+ // setter
+ MemberBox setter = null;
+ NativeJavaMethod setters = null;
+ String setterName = "set".concat(nameComponent);
+ // Is this value a method?
+ Object member = ht.get(setterName);
+ if (member == null && lazyInit) {
+ member = initFieldAndMethod(cx, setterName, ht, isStatic);
+ }
+ if (member instanceof NativeJavaMethod) {
+ NativeJavaMethod njmSet = (NativeJavaMethod) member;
+ if (getter != null) {
+ // We have a getter. Now, do we have a matching
+ // setter?
+ Class> type = getter.method().getReturnType();
+ setter = extractSetMethod(type, njmSet.methods, isStatic);
+ } else {
+ // No getter, find any set method
+ setter = extractSetMethod(njmSet.methods, isStatic);
+ }
+ if (njmSet.methods.length > 1) {
+ setters = njmSet;
+ }
+ }
+ // Make the property.
+ BeanProperty bp = new BeanProperty(getter, setter, setters);
+ return bp;
+ }
+
+ protected NativeJavaMethod toNativeJavaMethod(Context cx, Scriptable scope, Object value) {
+ MemberBox[] methodBoxes;
+ if (value instanceof Method) {
+ methodBoxes = new MemberBox[1];
+ methodBoxes[0] = new MemberBox((Method) value);
+ } else {
+ ArrayList