static BaseFunction buildClassCtor()

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;
    }