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 overloadedMethods = (ArrayList) value; + int N = overloadedMethods.size(); + if (N < 2) Kit.codeBug(); + methodBoxes = new MemberBox[N]; + for (int i = 0; i != N; ++i) { + Method method = (Method) overloadedMethods.get(i); + methodBoxes[i] = new MemberBox(method); + } + } + NativeJavaMethod fun = new NativeJavaMethod(methodBoxes); + if (scope != null) { + ScriptRuntime.setFunctionProtoAndParent(fun, cx, scope, false); + } + return fun; + } + + protected void initField(final Context cx, final Scriptable scope, final Field field) { + String name = field.getName(); + int mods = field.getModifiers(); + try { + boolean isStatic = Modifier.isStatic(mods); + Map ht = isStatic ? staticMembers : members; + Object member = ht.get(name); + if (member == null) { + ht.put(name, field); + } else if (member instanceof NativeJavaMethod) { + final FieldAndMethods fam = + initFieldAndMethods(scope, field, name, isStatic, member); + ht.put(name, fam); + } else if (member instanceof Field) { + Field oldField = (Field) member; + // If this newly reflected field shadows an inherited field, + // then replace it. Otherwise, since access to the field + // would be ambiguous from Java, no field should be + // reflected. + // For now, the first field found wins, unless another field + // explicitly shadows it. + if (oldField.getDeclaringClass().isAssignableFrom(field.getDeclaringClass())) { + ht.put(name, field); + } + } else { + // "unknown member type" + Kit.codeBug(); + } + } catch (SecurityException e) { + // skip this field + Context.reportWarning( + "Could not access field " + + name + + " of class " + + cl.getName() + + " due to lack of privileges."); + } + } + + private Object initFieldAndMethod( + final Context cx, + final String name, + final Map ht, + final boolean isStatic) { + Object member; + member = isStatic ? cfCache.getStaticField(name) : cfCache.getInstField(name); + final Map mbers = cfCache.getMembers(isStatic); + final Object value = mbers.get(name); + NativeJavaMethod jm = value == null ? null : toNativeJavaMethod(cx, javaMemberScope, value); + if (jm != null) { + if (member != null) { + Field fld = (Field) member; + member = + initFieldAndMethods( + javaMemberScope, + fld, + fld.getName(), + Modifier.isStatic(fld.getModifiers()), + jm); + } else { + member = jm; + } + } + if (member != null) { + ht.put(name, member); + } + return member; + } + + private FieldAndMethods initFieldAndMethods( + Scriptable scope, Field field, String name, boolean isStatic, Object member) { + NativeJavaMethod method = (NativeJavaMethod) member; + FieldAndMethods fam = new FieldAndMethods(scope, method.methods, field); + Map fmht = isStatic ? staticFieldAndMethods : fieldAndMethods; + if (fmht == null) { + fmht = new HashMap(); + if (isStatic) { + staticFieldAndMethods = fmht; + } else { + fieldAndMethods = fmht; + } + } + fmht.put(name, fam); + return fam; + } + + protected static NativeJavaMethod createConstructorMethod(Class cl, boolean includePrivate) { + NativeJavaMethod ctors; + Constructor[] constructors = getAccessibleConstructors(cl, includePrivate); + MemberBox[] ctorMembers = new MemberBox[constructors.length]; + for (int i = 0; i != constructors.length; ++i) { + ctorMembers[i] = new MemberBox(constructors[i]); + } + ctors = new NativeJavaMethod(ctorMembers, cl.getSimpleName()); + return ctors; + } + void put(Scriptable scope, String name, Object javaObject, Object value, boolean isStatic) { Map ht = isStatic ? staticMembers : members; Object member = ht.get(name); @@ -181,8 +438,7 @@ void put(Scriptable scope, String name, Object javaObject, Object value, boolean } Object[] getIds(boolean isStatic) { - Map map = isStatic ? staticMembers : members; - return map.keySet().toArray(new Object[0]); + return cfCache.getIds(isStatic); } static String javaSignature(Class type) { @@ -298,19 +554,210 @@ private Object getExplicitFunction( return member; } + private static final class ClassReflectBean { + + private final NativeJavaMethod constructorMethod; + // private final Map methodMap; + private final Map mSignatureMap; + private final Field[] fields; + private final Map fieldMap; + + private final Map staticFieldMap; + private final Map instFieldMap; + + private final Map staticBeanProperties; + private final Map instBeanProperties; + private final Map members; + private final Map staticMembers; + + private final Set staticMemberNames; + private final Set instMemberNames; + private final Set staticFieldAndMethods; + private final Set instFieldAndMethods; + + Map getMembers(final boolean isStatic) { + return isStatic ? staticMembers : members; + } + + boolean has(final String key, final boolean isStatic) { + return isStatic ? staticMemberNames.contains(key) : instMemberNames.contains(key); + } + + Object[] getIds(final boolean isStatic) { + Map map = isStatic ? staticMembers : members; + return map.keySet().toArray(new Object[map.size()]); + } + + @SuppressWarnings("unchecked") + ClassReflectBean( + NativeJavaMethod constructorMethod, + Map mSignatureMap, + Field[] fields) { + super(); + this.staticMemberNames = new HashSet(); + this.instMemberNames = new HashSet(); + this.constructorMethod = constructorMethod; + this.mSignatureMap = mSignatureMap; + this.fields = fields; + this.members = new HashMap(); + this.staticMembers = new HashMap(); + // methodMap = new HashMap(); + for (Map.Entry entry : mSignatureMap.entrySet()) { + final Method m = entry.getValue(); + // methodMap.put(m.getName(),entry.getKey()); + final MethodSignature key = entry.getKey(); + if (key.staticMethod) { + staticMemberNames.add(key.name); + } else { + instMemberNames.add(key.name); + } + } + Set staticMethodNames = new HashSet(staticMemberNames); + Set instMethodNames = new HashSet(instMemberNames); + fieldMap = new HashMap(); + for (Field fld : fields) { + final String name = fld.getName(); + Field oldField = (Field) fieldMap.get(name); + if (oldField == null) { + fieldMap.put(name, fld); + continue; + } + // If this newly reflected field shadows an inherited field, + // then replace it. Otherwise, since access to the field + // would be ambiguous from Java, no field should be + // reflected. + // For now, the first field found wins, unless another field + // explicitly shadows it. + if (oldField.getDeclaringClass().isAssignableFrom(fld.getDeclaringClass()) + || (fld.isAccessible() && !oldField.isAccessible())) { + fieldMap.put(name, fld); + } + } + + instFieldMap = new HashMap(); + staticFieldMap = new HashMap(); + for (Map.Entry entry : fieldMap.entrySet()) { + final Field fld = entry.getValue(); + final String key = entry.getKey(); + if (Modifier.isStatic(fld.getModifiers())) { + staticFieldMap.put(key, fld); + staticMemberNames.add(key); + } else { + instFieldMap.put(key, fld); + instMemberNames.add(key); + } + } + + staticFieldAndMethods = intersection(staticMethodNames, staticFieldMap.keySet()); + instFieldAndMethods = intersection(instMethodNames, instFieldMap.keySet()); + + final Object[] beanProperties = createBeanProperties(mSignatureMap.values()); + staticBeanProperties = (Map) beanProperties[0]; + instBeanProperties = (Map) beanProperties[1]; + staticMemberNames.addAll(staticBeanProperties.keySet()); + instMemberNames.addAll(instBeanProperties.keySet()); + + for (Map.Entry entry : mSignatureMap.entrySet()) { + Method method = entry.getValue(); + String name = method.getName(); + final MethodSignature m = entry.getKey(); + // int mods = method.getModifiers(); + final boolean isStatic = m.staticMethod; + Map ht = isStatic ? staticMembers : members; + Object value = ht.get(name); + if (value == null) { + ht.put(name, method); + } else { + ArrayList overloadedMethods; + if (value instanceof ArrayList) { + overloadedMethods = (ArrayList) value; + } else { + if (!(value instanceof Method)) Kit.codeBug(); + // value should be instance of Method as at this stage + // staticMembers and members can only contain methods + overloadedMethods = new ArrayList<>(); + overloadedMethods.add(value); + ht.put(name, overloadedMethods); + } + overloadedMethods.add(method); + } + } + } + + Field getStaticField(final String key) { + return staticFieldMap.get(key); + } + + Field getInstField(final String key) { + return instFieldMap.get(key); + } + + private static final Set intersection(Collection a, Collection b) { + final Set ret = new HashSet(); + for (T v : a) { + if (b.contains(v)) { + ret.add(v); + } + } + return ret; + } + } // ClassReflectBean + + private static final Map cache = new ConcurrentHashMap(); + + private static final Map CACHE_PROTECTED = + new ConcurrentHashMap(); + private static final Map CACHE_PRIVATE = + new ConcurrentHashMap(); + private static final Map CACHE_DEFAULT = + new ConcurrentHashMap(); + + private static Map getCache( + final Class clazz, final boolean includeProtected, final boolean includePrivate) { + if (includeProtected) { + return CACHE_PROTECTED; + } + if (includePrivate) { + return CACHE_PRIVATE; + } + return CACHE_DEFAULT; + } + + private ClassReflectBean createClassReflectBean( + Class clazz, boolean includeProtected, boolean includePrivate) { + final Map cache = + reflectCacheOn ? getCache(clazz, includeProtected, includePrivate) : null; + ClassReflectBean ret = null; + if (cache != null) { + ret = cache.get(clazz); + if (ret != null) { + return ret; + } + } + Map map = new HashMap(); + discoverAccessibleMethods(clazz, map, includeProtected, includePrivate); + Field[] fields = getAccessibleFields(clazz, includeProtected, includePrivate); + final NativeJavaMethod constructorMethod = createConstructorMethod(clazz, includePrivate); + ret = new ClassReflectBean(constructorMethod, map, fields); + // for(Iterator iter = map.keySet().iterator();iter.hasNext();) { + // final MethodSignature next =(MethodSignature) iter.next(); + // if(!"getSelf".equals(next.name)) { + // iter.remove(); + // } + // } + + if (cache != null) { + cache.put(clazz, ret); + } + return ret; + } + /** * 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]); - } - private void discoverAccessibleMethods( Class clazz, Map map, @@ -395,14 +842,17 @@ static void registerMethod(Map map, Method method) { 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()); + private final boolean staticMethod; + private final boolean privateMethod; + private final Method method; + + private MethodSignature(final Method method) { + name = method.getName(); + args = method.getParameterTypes(); + this.method = method; + int mods = method.getModifiers(); + staticMethod = Modifier.isStatic(mods); + privateMethod = Modifier.isPrivate(mods); } @Override @@ -421,219 +871,129 @@ public int hashCode() { } @SuppressWarnings("unchecked") - private void reflect( + private ClassReflectBean 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 overloadedMethods; - if (value instanceof ArrayList) { - overloadedMethods = (ArrayList) value; - } else { - if (!(value instanceof Method)) Kit.codeBug(); - // value should be instance of Method as at this stage - // staticMembers and members can only contain methods - overloadedMethods = new ArrayList<>(); - overloadedMethods.add(value); - ht.put(name, overloadedMethods); + final ClassReflectBean cfCache = + createClassReflectBean(cl, includeProtected, includePrivate); + if (!lazyInit) { + // replace Method instances by wrapped NativeJavaMethod objects + // first in staticMembers and then in members + for (int tableCursor = 0; tableCursor != 2; ++tableCursor) { + boolean isStatic = (tableCursor == 0); + Map ht = isStatic ? staticMembers : members; + for (Map.Entry entry : ht.entrySet()) { + Object value = entry.getValue(); + NativeJavaMethod fun = toNativeJavaMethod(cx, scope, value); + entry.setValue(fun); } - overloadedMethods.add(method); } - } - // replace Method instances by wrapped NativeJavaMethod objects - // first in staticMembers and then in members - for (int tableCursor = 0; tableCursor != 2; ++tableCursor) { - boolean isStatic = (tableCursor == 0); - Map ht = isStatic ? staticMembers : members; - for (Map.Entry entry : ht.entrySet()) { - MemberBox[] methodBoxes; - Object value = entry.getValue(); - if (value instanceof Method) { - methodBoxes = new MemberBox[1]; - methodBoxes[0] = new MemberBox((Method) value); - } else { - ArrayList overloadedMethods = (ArrayList) value; - int N = overloadedMethods.size(); - if (N < 2) Kit.codeBug(); - methodBoxes = new MemberBox[N]; - for (int i = 0; i != N; ++i) { - Method method = (Method) overloadedMethods.get(i); - methodBoxes[i] = new MemberBox(method); - } - } - NativeJavaMethod fun = new NativeJavaMethod(methodBoxes); - if (scope != null) { - ScriptRuntime.setFunctionProtoAndParent(fun, cx, scope, false); - } - entry.setValue(fun); + // Reflect fields. + Field[] fields = cfCache.fields; + for (Field field : fields) { + initField(cx, scope, field); } - } - // Reflect fields. - Field[] fields = getAccessibleFields(includeProtected, includePrivate); - for (Field field : fields) { - String name = field.getName(); - int mods = field.getModifiers(); - try { - boolean isStatic = Modifier.isStatic(mods); + // Create bean properties from corresponding get/set methods first for + // static members and then for instance members + for (int tableCursor = 0; tableCursor != 2; ++tableCursor) { + boolean isStatic = (tableCursor == 0); Map ht = isStatic ? staticMembers : members; - Object member = ht.get(name); - if (member == null) { - ht.put(name, field); - } else if (member instanceof NativeJavaMethod) { - NativeJavaMethod method = (NativeJavaMethod) member; - FieldAndMethods fam = new FieldAndMethods(scope, method.methods, field); - Map fmht = - isStatic ? staticFieldAndMethods : fieldAndMethods; - if (fmht == null) { - fmht = new HashMap<>(); - if (isStatic) { - staticFieldAndMethods = fmht; - } else { - fieldAndMethods = fmht; - } - } - fmht.put(name, fam); - ht.put(name, fam); - } else if (member instanceof Field) { - Field oldField = (Field) member; - // If this newly reflected field shadows an inherited field, - // then replace it. Otherwise, since access to the field - // would be ambiguous from Java, no field should be - // reflected. - // For now, the first field found wins, unless another field - // explicitly shadows it. - if (oldField.getDeclaringClass().isAssignableFrom(field.getDeclaringClass())) { - ht.put(name, field); - } - } else { - // "unknown member type" - Kit.codeBug(); - } - } catch (SecurityException e) { - // skip this field - Context.reportWarning( - "Could not access field " - + name - + " of class " - + cl.getName() - + " due to lack of privileges."); - } - } - - // Create bean properties from corresponding get/set methods first for - // static members and then for instance members - for (int tableCursor = 0; tableCursor != 2; ++tableCursor) { - boolean isStatic = (tableCursor == 0); - Map ht = isStatic ? staticMembers : members; - Map toAdd = new HashMap<>(); - - // Now, For each member, make "bean" properties. - for (String name : ht.keySet()) { - // 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(Locale.ROOT); - } else { - char ch1 = nameComponent.charAt(1); - if (!Character.isUpperCase(ch1)) { - beanPropertyName = - Character.toLowerCase(ch0) + nameComponent.substring(1); + Map toAdd = new HashMap<>(); + + // Now, For each member, make "bean" properties. + for (String name : ht.keySet()) { + // 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(Locale.ROOT); + } else { + char ch1 = nameComponent.charAt(1); + if (!Character.isUpperCase(ch1)) { + beanPropertyName = + Character.toLowerCase(ch0) + nameComponent.substring(1); + } } } - } - // If we already have a member by this name, don't do this - // property. - if (toAdd.containsKey(beanPropertyName)) continue; - 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())) { - - continue; + // If we already have a member by this name, don't do this + // property. + if (toAdd.containsKey(beanPropertyName)) continue; + 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())) { + + continue; + } } - } - // 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); - } + // 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); - - if (ht.containsKey(setterName)) { - // Is this value a method? - Object member = ht.get(setterName); - 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; + // setter + MemberBox setter = null; + NativeJavaMethod setters = null; + String setterName = "set".concat(nameComponent); + + if (ht.containsKey(setterName)) { + // Is this value a method? + Object member = ht.get(setterName); + 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); + toAdd.put(beanPropertyName, bp); } - // Make the property. - BeanProperty bp = new BeanProperty(getter, setter, setters); - toAdd.put(beanPropertyName, bp); } - } - // Add the new bean properties. - ht.putAll(toAdd); + // Add the new bean properties. + ht.putAll(toAdd); + } } - // Reflect constructors - Constructor[] constructors = getAccessibleConstructors(includePrivate); - MemberBox[] ctorMembers = new MemberBox[constructors.length]; - for (int i = 0; i != constructors.length; ++i) { - ctorMembers[i] = new MemberBox(constructors[i]); - } - ctors = new NativeJavaMethod(ctorMembers, cl.getSimpleName()); - } + ctors = cfCache.constructorMethod; + return cfCache; + } // end reflect - private Constructor[] getAccessibleConstructors(boolean includePrivate) { + private static Constructor[] getAccessibleConstructors(Class cl, boolean includePrivate) { // The JVM currently doesn't allow changing access on java.lang.Class // constructors, so don't try if (includePrivate && cl != ScriptRuntime.ClassClass) { @@ -654,7 +1014,8 @@ private Constructor[] getAccessibleConstructors(boolean includePrivate) { return cl.getConstructors(); } - private Field[] getAccessibleFields(boolean includeProtected, boolean includePrivate) { + private Field[] getAccessibleFields( + Class cl, boolean includeProtected, boolean includePrivate) { if (includePrivate || includeProtected) { try { List fieldsList = new ArrayList<>();