in rhino/src/main/java/org/mozilla/javascript/ScriptableObject.java [1045:1260]
static <T extends Scriptable> BaseFunction buildClassCtor(
Scriptable scope, Class<T> clazz, boolean sealed, boolean mapInheritance)
throws IllegalAccessException, InstantiationException, InvocationTargetException {
Method[] methods = FunctionObject.getMethodList(clazz);
for (Method method : methods) {
if (!method.getName().equals("init")) continue;
Class<?>[] parmTypes = method.getParameterTypes();
if (parmTypes.length == 3
&& parmTypes[0] == ScriptRuntime.ContextClass
&& parmTypes[1] == ScriptRuntime.ScriptableClass
&& parmTypes[2] == Boolean.TYPE
&& Modifier.isStatic(method.getModifiers())) {
Object[] args = {
Context.getContext(), scope, sealed ? Boolean.TRUE : Boolean.FALSE
};
method.invoke(null, args);
return null;
}
if (parmTypes.length == 1
&& parmTypes[0] == ScriptRuntime.ScriptableClass
&& Modifier.isStatic(method.getModifiers())) {
Object[] args = {scope};
method.invoke(null, args);
return null;
}
}
// If we got here, there isn't an "init" method with the right
// parameter types.
Constructor<?>[] ctors = clazz.getConstructors();
Constructor<?> protoCtor = null;
for (Constructor<?> constructor : ctors) {
if (constructor.getParameterTypes().length == 0) {
protoCtor = constructor;
break;
}
}
if (protoCtor == null) {
throw Context.reportRuntimeErrorById("msg.zero.arg.ctor", clazz.getName());
}
Scriptable proto = (Scriptable) protoCtor.newInstance(ScriptRuntime.emptyArgs);
String className = proto.getClassName();
// check for possible redefinition
Object existing = getProperty(getTopLevelScope(scope), className);
if (existing instanceof BaseFunction) {
Object existingProto = ((BaseFunction) existing).getPrototypeProperty();
if (existingProto != null && clazz.equals(existingProto.getClass())) {
return (BaseFunction) existing;
}
}
// Set the prototype's prototype, trying to map Java inheritance to JS
// prototype-based inheritance if requested to do so.
Scriptable superProto = null;
if (mapInheritance) {
Class<? super T> superClass = clazz.getSuperclass();
if (ScriptRuntime.ScriptableClass.isAssignableFrom(superClass)
&& !Modifier.isAbstract(superClass.getModifiers())) {
Class<? extends Scriptable> superScriptable = extendsScriptable(superClass);
String name =
ScriptableObject.defineClass(
scope, superScriptable, sealed, mapInheritance);
if (name != null) {
superProto = ScriptableObject.getClassPrototype(scope, name);
}
}
}
if (superProto == null) {
superProto = ScriptableObject.getObjectPrototype(scope);
}
proto.setPrototype(superProto);
// Find out whether there are any methods that begin with
// "js". If so, then only methods that begin with special
// prefixes will be defined as JavaScript entities.
final String functionPrefix = "jsFunction_";
final String staticFunctionPrefix = "jsStaticFunction_";
final String getterPrefix = "jsGet_";
final String setterPrefix = "jsSet_";
final String ctorName = "jsConstructor";
Member ctorMember = findAnnotatedMember(methods, JSConstructor.class);
if (ctorMember == null) {
ctorMember = findAnnotatedMember(ctors, JSConstructor.class);
}
if (ctorMember == null) {
ctorMember = FunctionObject.findSingleMethod(methods, ctorName);
}
if (ctorMember == null) {
if (ctors.length == 1) {
ctorMember = ctors[0];
} else if (ctors.length == 2) {
if (ctors[0].getParameterTypes().length == 0) ctorMember = ctors[1];
else if (ctors[1].getParameterTypes().length == 0) ctorMember = ctors[0];
}
if (ctorMember == null) {
throw Context.reportRuntimeErrorById("msg.ctor.multiple.parms", clazz.getName());
}
}
FunctionObject ctor = new FunctionObject(className, ctorMember, scope);
if (ctor.isVarArgsMethod()) {
throw Context.reportRuntimeErrorById("msg.varargs.ctor", ctorMember.getName());
}
ctor.initAsConstructor(
scope,
proto,
ScriptableObject.DONTENUM | ScriptableObject.PERMANENT | ScriptableObject.READONLY);
Method finishInit = null;
HashSet<String> staticNames = new HashSet<>(), instanceNames = new HashSet<>();
for (Method method : methods) {
if (method == ctorMember) {
continue;
}
String name = method.getName();
if (name.equals("finishInit")) {
Class<?>[] parmTypes = method.getParameterTypes();
if (parmTypes.length == 3
&& parmTypes[0] == ScriptRuntime.ScriptableClass
&& parmTypes[1] == FunctionObject.class
&& parmTypes[2] == ScriptRuntime.ScriptableClass
&& Modifier.isStatic(method.getModifiers())) {
finishInit = method;
continue;
}
}
// ignore any compiler generated methods.
if (name.indexOf('$') != -1) continue;
if (name.equals(ctorName)) continue;
Annotation annotation = null;
String prefix = null;
if (method.isAnnotationPresent(JSFunction.class)) {
annotation = method.getAnnotation(JSFunction.class);
} else if (method.isAnnotationPresent(JSStaticFunction.class)) {
annotation = method.getAnnotation(JSStaticFunction.class);
} else if (method.isAnnotationPresent(JSGetter.class)) {
annotation = method.getAnnotation(JSGetter.class);
} else if (method.isAnnotationPresent(JSSetter.class)) {
continue;
}
if (annotation == null) {
if (name.startsWith(functionPrefix)) {
prefix = functionPrefix;
} else if (name.startsWith(staticFunctionPrefix)) {
prefix = staticFunctionPrefix;
} else if (name.startsWith(getterPrefix)) {
prefix = getterPrefix;
} else {
// note that setterPrefix is among the unhandled names here -
// we deal with that when we see the getter
continue;
}
}
boolean isStatic =
annotation instanceof JSStaticFunction
|| Objects.equals(prefix, staticFunctionPrefix);
HashSet<String> names = isStatic ? staticNames : instanceNames;
String propName = getPropertyName(name, prefix, annotation);
if (names.contains(propName)) {
throw Context.reportRuntimeErrorById("duplicate.defineClass.name", name, propName);
}
names.add(propName);
name = propName;
if (annotation instanceof JSGetter || Objects.equals(prefix, getterPrefix)) {
if (!(proto instanceof ScriptableObject)) {
throw Context.reportRuntimeErrorById(
"msg.extend.scriptable", proto.getClass().toString(), name);
}
Method setter = findSetterMethod(methods, name, setterPrefix);
int attr =
ScriptableObject.PERMANENT
| ScriptableObject.DONTENUM
| (setter != null ? 0 : ScriptableObject.READONLY);
((ScriptableObject) proto).defineProperty(name, null, method, setter, attr);
continue;
}
if (isStatic && !Modifier.isStatic(method.getModifiers())) {
throw Context.reportRuntimeError(
"jsStaticFunction must be used with static method.");
}
FunctionObject f = new FunctionObject(name, method, proto);
if (f.isVarArgsConstructor()) {
throw Context.reportRuntimeErrorById("msg.varargs.fun", ctorMember.getName());
}
defineProperty(isStatic ? ctor : proto, name, f, DONTENUM);
if (sealed) {
f.sealObject();
}
}
// Call user code to complete initialization if necessary.
if (finishInit != null) {
Object[] finishArgs = {scope, ctor, proto};
finishInit.invoke(null, finishArgs);
}
// Seal the object if necessary.
if (sealed) {
ctor.sealObject();
if (proto instanceof ScriptableObject) {
((ScriptableObject) proto).sealObject();
}
}
return ctor;
}