All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.teavm.jso.impl.JSClassProcessor Maven / Gradle / Ivy

The newest version!
/*
 *  Copyright 2014 Alexey Andreev.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.teavm.jso.impl;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import org.mozilla.javascript.CompilerEnvirons;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.ast.AstRoot;
import org.mozilla.javascript.ast.FunctionNode;
import org.teavm.backend.javascript.rendering.JSParser;
import org.teavm.cache.IncrementalDependencyRegistration;
import org.teavm.diagnostics.Diagnostics;
import org.teavm.interop.NoSideEffects;
import org.teavm.jso.JSBody;
import org.teavm.jso.JSByRef;
import org.teavm.jso.JSClass;
import org.teavm.jso.JSFunctor;
import org.teavm.jso.JSObject;
import org.teavm.jso.JSPrimitiveType;
import org.teavm.jso.JSTopLevel;
import org.teavm.model.AnnotationContainerReader;
import org.teavm.model.AnnotationHolder;
import org.teavm.model.AnnotationReader;
import org.teavm.model.AnnotationValue;
import org.teavm.model.BasicBlock;
import org.teavm.model.CallLocation;
import org.teavm.model.ClassHolder;
import org.teavm.model.ClassReader;
import org.teavm.model.ClassReaderSource;
import org.teavm.model.ElementModifier;
import org.teavm.model.Instruction;
import org.teavm.model.MethodDescriptor;
import org.teavm.model.MethodHolder;
import org.teavm.model.MethodReader;
import org.teavm.model.MethodReference;
import org.teavm.model.Program;
import org.teavm.model.TextLocation;
import org.teavm.model.ValueType;
import org.teavm.model.Variable;
import org.teavm.model.instructions.ArrayElementType;
import org.teavm.model.instructions.AssignInstruction;
import org.teavm.model.instructions.CastInstruction;
import org.teavm.model.instructions.ClassConstantInstruction;
import org.teavm.model.instructions.ConstructArrayInstruction;
import org.teavm.model.instructions.ConstructInstruction;
import org.teavm.model.instructions.ExitInstruction;
import org.teavm.model.instructions.GetElementInstruction;
import org.teavm.model.instructions.IntegerConstantInstruction;
import org.teavm.model.instructions.InvocationType;
import org.teavm.model.instructions.InvokeInstruction;
import org.teavm.model.instructions.IsInstanceInstruction;
import org.teavm.model.instructions.PutElementInstruction;
import org.teavm.model.instructions.PutFieldInstruction;
import org.teavm.model.util.InstructionVariableMapper;
import org.teavm.model.util.ModelUtils;
import org.teavm.model.util.ProgramUtils;

class JSClassProcessor {
    private static final String NO_SIDE_EFFECTS = NoSideEffects.class.getName();
    private static final MethodReference WRAP = new MethodReference(JSWrapper.class, "wrap", Object.class,
            Object.class);
    private static final MethodReference MAYBE_WRAP = new MethodReference(JSWrapper.class, "maybeWrap", Object.class,
            Object.class);
    private static final MethodReference UNWRAP = new MethodReference(JSWrapper.class, "unwrap", Object.class,
            JSObject.class);
    private static final MethodReference MAYBE_UNWRAP = new MethodReference(JSWrapper.class, "maybeUnwrap",
            Object.class, JSObject.class);
    private static final MethodReference IS_JS = new MethodReference(JSWrapper.class, "isJs",
            Object.class, boolean.class);
    private static final MethodReference IS_PRIMITIVE = new MethodReference(JSWrapper.class, "isPrimitive",
            Object.class, JSObject.class, boolean.class);
    private static final MethodReference INSTANCE_OF = new MethodReference(JSWrapper.class, "instanceOf",
            Object.class, JSObject.class, boolean.class);
    private final ClassReaderSource classSource;
    private final JSBodyRepository repository;
    private final JavaInvocationProcessor javaInvocationProcessor;
    private Program program;
    private int[] variableAliases;
    private boolean[] nativeConstructedObjects;
    private JSTypeInference types;
    private final List replacement = new ArrayList<>();
    private final JSTypeHelper typeHelper;
    private final Diagnostics diagnostics;
    private final Map overriddenMethodCache = new HashMap<>();
    private final boolean strict;
    private JSValueMarshaller marshaller;
    private IncrementalDependencyRegistration incrementalCache;
    private JSImportAnnotationCache annotationCache;
    private ClassReader objectClass;

    JSClassProcessor(ClassReaderSource classSource, JSTypeHelper typeHelper, JSBodyRepository repository,
            Diagnostics diagnostics, IncrementalDependencyRegistration incrementalCache, boolean strict) {
        this.classSource = classSource;
        this.typeHelper = typeHelper;
        this.repository = repository;
        this.diagnostics = diagnostics;
        this.incrementalCache = incrementalCache;
        this.strict = strict;
        javaInvocationProcessor = new JavaInvocationProcessor(typeHelper, repository, classSource, diagnostics);

        annotationCache = new JSImportAnnotationCache(classSource, diagnostics);
    }

    public ClassReaderSource getClassSource() {
        return classSource;
    }

    MethodReference isFunctor(String className) {
        if (!typeHelper.isJavaScriptImplementation(className)) {
            return null;
        }
        ClassReader cls = classSource.get(className);
        if (cls == null) {
            return null;
        }
        Map methods = new HashMap<>();
        getFunctorMethods(className, methods);
        if (methods.size() == 1) {
            return methods.values().iterator().next();
        }
        return null;
    }

    private void getFunctorMethods(String className, Map methods) {
        classSource.getAncestors(className).forEach(cls -> {
            if (cls.getAnnotations().get(JSFunctor.class.getName()) != null && marshaller.isProperFunctor(cls)) {
                MethodReference method = cls.getMethods().iterator().next().getReference();
                if (!methods.containsKey(method.getDescriptor())) {
                    methods.put(method.getDescriptor(), method);
                }
            }
        });
    }

    void processClass(ClassHolder cls) {
        Set preservedMethods = new HashSet<>();
        for (String iface : cls.getInterfaces()) {
            if (typeHelper.isJavaScriptClass(iface)) {
                addPreservedMethods(iface, preservedMethods);
            }
        }
    }

    private void addPreservedMethods(String ifaceName, Set methods) {
        ClassReader iface = classSource.get(ifaceName);
        for (MethodReader method : iface.getMethods()) {
            methods.add(method.getDescriptor());
        }
        for (String superIfaceName : iface.getInterfaces()) {
            addPreservedMethods(superIfaceName, methods);
        }
    }

    void processMemberMethods(ClassHolder cls) {
        for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
            if (method.hasModifier(ElementModifier.STATIC)) {
                continue;
            }
            if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) {
                ValueType[] staticSignature = getStaticSignature(method.getReference());
                MethodHolder callerMethod = new MethodHolder(new MethodDescriptor(method.getName() + "$static",
                        staticSignature));
                callerMethod.getModifiers().add(ElementModifier.STATIC);
                Program program = ProgramUtils.copy(method.getProgram());
                program.createVariable();
                var variableMapper = new InstructionVariableMapper(var -> program.variableAt(var.getIndex() + 1));
                for (int i = program.variableCount() - 1; i > 0; --i) {
                    program.variableAt(i).setDebugName(program.variableAt(i - 1).getDebugName());
                    program.variableAt(i).setLabel(program.variableAt(i - 1).getLabel());
                }
                for (int i = 0; i < program.basicBlockCount(); ++i) {
                    BasicBlock block = program.basicBlockAt(i);
                    variableMapper.apply(block);
                }
                callerMethod.setProgram(program);
                ModelUtils.copyAnnotations(method.getAnnotations(), callerMethod.getAnnotations());
                cls.addMethod(callerMethod);
            }
        }
    }

    private MethodReader getOverriddenMethod(MethodReader finalMethod) {
        MethodReference ref = finalMethod.getReference();
        if (!overriddenMethodCache.containsKey(ref)) {
            overriddenMethodCache.put(ref, findOverriddenMethod(finalMethod.getOwnerName(), finalMethod));
        }
        return overriddenMethodCache.get(ref);
    }

    private MethodReader findOverriddenMethod(String className, MethodReader finalMethod) {
        if (finalMethod.getName().equals("")) {
            return null;
        }
        return classSource.getAncestors(className)
                .skip(1)
                .map(cls -> cls.getMethod(finalMethod.getDescriptor()))
                .filter(Objects::nonNull)
                .findFirst()
                .orElse(null);
    }

    private static ValueType[] getStaticSignature(MethodReference method) {
        ValueType[] signature = method.getSignature();
        ValueType[] staticSignature = new ValueType[signature.length + 1];
        for (int i = 0; i < signature.length; ++i) {
            staticSignature[i + 1] = signature[i];
        }
        staticSignature[0] = ValueType.object(method.getClassName());
        return staticSignature;
    }

    private void setCurrentProgram(Program program) {
        this.program = program;
        marshaller = new JSValueMarshaller(diagnostics, typeHelper, classSource, program, replacement);
        findVariableAliases();
    }

    private void findVariableAliases() {
        if (program == null) {
            return;
        }
        variableAliases = new int[program.variableCount()];
        nativeConstructedObjects = new boolean[program.variableCount()];
        var resolved = new boolean[program.variableCount()];
        Arrays.fill(resolved, true);
        for (var i = 0; i < variableAliases.length; ++i) {
            variableAliases[i] = i;
        }
        for (var block : program.getBasicBlocks()) {
            for (var instruction : block) {
                if (instruction instanceof AssignInstruction) {
                    var assign = (AssignInstruction) instruction;
                    var from = assign.getAssignee().getIndex();
                    var to = assign.getReceiver().getIndex();
                    variableAliases[to] = from;
                    resolved[assign.getAssignee().getIndex()] = true;
                } else if (instruction instanceof ConstructInstruction) {
                    var construct = (ConstructInstruction) instruction;
                    if (typeHelper.isJavaScriptClass(construct.getType())) {
                        nativeConstructedObjects[construct.getReceiver().getIndex()] = true;
                    }
                }
            }
        }
        for (var i = 0; i < variableAliases.length; ++i) {
            getVariableAlias(i, resolved);
        }
    }

    private int getVariableAlias(int index, boolean[] resolved) {
        if (resolved[index]) {
            return variableAliases[index];
        }
        resolved[index] = true;
        variableAliases[index] = getVariableAlias(variableAliases[index], resolved);
        return variableAliases[index];
    }

    void processProgram(MethodHolder methodToProcess) {
        setCurrentProgram(methodToProcess.getProgram());
        types = new JSTypeInference(typeHelper, classSource, program, methodToProcess.getReference());
        types.ensure();
        for (int i = 0; i < program.basicBlockCount(); ++i) {
            var block = program.basicBlockAt(i);
            for (var insn : block) {
                if (insn instanceof CastInstruction) {
                    replacement.clear();
                    var callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
                    if (processCast((CastInstruction) insn, callLocation)) {
                        insn.insertNextAll(replacement);
                        insn.delete();
                    }
                } else if (insn instanceof IsInstanceInstruction) {
                    processIsInstance((IsInstanceInstruction) insn);
                } else if (insn instanceof InvokeInstruction) {
                    var invoke = (InvokeInstruction) insn;
                    var callLocation = new CallLocation(methodToProcess.getReference(), insn.getLocation());
                    if (processToString(invoke, callLocation)) {
                        continue;
                    }

                    var method = getMethod(invoke.getMethod().getClassName(), invoke.getMethod().getDescriptor());
                    processInvokeArgs(invoke, method);
                    if (method == null) {
                        continue;
                    }
                    replacement.clear();
                    if (processInvocation(method, callLocation, invoke, methodToProcess)) {
                        insn.insertNextAll(replacement);
                        insn.delete();
                    }
                } else if (insn instanceof PutFieldInstruction) {
                    processPutField((PutFieldInstruction) insn);
                } else if (insn instanceof GetElementInstruction) {
                    processGetFromArray((GetElementInstruction) insn);
                } else if (insn instanceof PutElementInstruction) {
                    processPutIntoArray((PutElementInstruction) insn);
                } else if (insn instanceof ConstructArrayInstruction) {
                    processConstructArray((ConstructArrayInstruction) insn);
                } else if (insn instanceof ExitInstruction) {
                    var exit = (ExitInstruction) insn;
                    exit.setValueToReturn(wrapJsAsJava(insn, exit.getValueToReturn(),
                            methodToProcess.getResultType()));
                } else if (insn instanceof ClassConstantInstruction) {
                    processClassConstant((ClassConstantInstruction) insn);
                } else if (insn instanceof ConstructInstruction) {
                    processConstructObject((ConstructInstruction) insn);
                } else if (insn instanceof AssignInstruction) {
                    var assign = (AssignInstruction) insn;
                    var index = variableAliases[assign.getReceiver().getIndex()];
                    if (nativeConstructedObjects[index]) {
                        assign.delete();
                    }
                }
            }
        }

        var varMapper = new InstructionVariableMapper(v -> {
            if (v.getIndex() < nativeConstructedObjects.length
                    && nativeConstructedObjects[variableAliases[v.getIndex()]]) {
                return program.variableAt(variableAliases[v.getIndex()]);
            }
            return v;
        });
        for (var block : program.getBasicBlocks()) {
            varMapper.apply(block);
        }
    }

    private void processInvokeArgs(InvokeInstruction invoke, MethodReader methodToInvoke) {
        if (methodToInvoke != null && methodToInvoke.getAnnotations().get(JSBody.class.getName()) != null) {
            return;
        }
        var className = invoke.getMethod().getClassName();
        if (typeHelper.isJavaScriptClass(invoke.getMethod().getClassName())) {
            if (invoke.getMethod().getName().equals("")
                    || getObjectClass().getMethod(invoke.getMethod().getDescriptor()) == null) {
                return;
            } else {
                className = "java.lang.Object";
            }
        }
        Variable[] newArgs = null;
        for (var i = 0; i < invoke.getArguments().size(); ++i) {
            var type = invoke.getMethod().parameterType(i);
            var arg = invoke.getArguments().get(i);
            var newArg = wrapJsAsJava(invoke, arg, type);
            if (newArg != arg) {
                if (newArgs == null) {
                    newArgs = invoke.getArguments().toArray(new Variable[0]);
                }
                newArgs[i] = newArg;
            }
        }
        if (newArgs != null) {
            invoke.setArguments(newArgs);
        }

        if (invoke.getInstance() != null) {
            invoke.setInstance(wrapJsAsJava(invoke, invoke.getInstance(), ValueType.object(className)));
        }
    }

    private void processPutField(PutFieldInstruction putField) {
        putField.setValue(wrapJsAsJava(putField, putField.getValue(), putField.getFieldType()));
    }

    private void processGetFromArray(GetElementInstruction insn) {
        if (insn.getType() != ArrayElementType.OBJECT) {
            return;
        }

        var type = types.typeOf(insn.getReceiver());
        if (type == JSType.JS || type == JSType.MIXED) {
            var unwrap = new InvokeInstruction();
            unwrap.setType(InvocationType.SPECIAL);
            unwrap.setMethod(type == JSType.MIXED ? MAYBE_UNWRAP : UNWRAP);
            unwrap.setArguments(program.createVariable());
            unwrap.setReceiver(insn.getReceiver());
            unwrap.setLocation(insn.getLocation());
            insn.setReceiver(unwrap.getArguments().get(0));
            insn.insertNext(unwrap);
        }
    }

    private void processPutIntoArray(PutElementInstruction insn) {
        if (insn.getType() != ArrayElementType.OBJECT) {
            return;
        }

        var type = types.typeOf(insn.getValue());
        if (type == JSType.JS || type == JSType.MIXED) {
            var wrap = new InvokeInstruction();
            wrap.setType(InvocationType.SPECIAL);
            wrap.setMethod(type == JSType.MIXED ? MAYBE_WRAP : WRAP);
            wrap.setArguments(insn.getValue());
            wrap.setReceiver(program.createVariable());
            wrap.setLocation(insn.getLocation());
            insn.setValue(wrap.getReceiver());
            insn.insertPrevious(wrap);
        }
    }

    private void processConstructArray(ConstructArrayInstruction insn) {
        insn.setItemType(processType(insn.getItemType()));
    }

    private void processClassConstant(ClassConstantInstruction insn) {
        insn.setConstant(processType(insn.getConstant()));
    }

    private void processConstructObject(ConstructInstruction insn) {
        if (nativeConstructedObjects[insn.getReceiver().getIndex()]) {
            insn.delete();
        }
    }

    private ValueType processType(ValueType type) {
        return processType(typeHelper, type);
    }

    static ValueType processType(JSTypeHelper typeHelper, ValueType type) {
        var originalType = type;
        var degree = 0;
        while (type instanceof ValueType.Array) {
            degree++;
            type = ((ValueType.Array) type).getItemType();
        }
        if (!(type instanceof ValueType.Object)) {
            return originalType;
        }

        var className = ((ValueType.Object) type).getClassName();
        if (!typeHelper.isJavaScriptClass(className)) {
            return originalType;
        }

        type = ValueType.object(JSWrapper.class.getName());
        while (degree-- > 0) {
            type = ValueType.arrayOf(type);
        }
        return type;
    }

    private boolean processCast(CastInstruction cast, CallLocation location) {
        if (!(cast.getTargetType() instanceof ValueType.Object)) {
            cast.setTargetType(processType(cast.getTargetType()));
            return false;
        }

        String targetClassName = ((ValueType.Object) cast.getTargetType()).getClassName();
        if (!typeHelper.isJavaScriptClass(targetClassName)) {
            return false;
        }

        cast.setValue(unwrapJavaToJs(cast, cast.getValue()));

        ClassReader targetClass = classSource.get(targetClassName);
        if (targetClass.getAnnotations().get(JSFunctor.class.getName()) == null) {
            if (!strict || isTransparent(targetClassName)) {
                var assign = new AssignInstruction();
                assign.setLocation(location.getSourceLocation());
                assign.setAssignee(cast.getValue());
                assign.setReceiver(cast.getReceiver());
                replacement.add(assign);
            } else {
                var instanceOfResult = program.createVariable();
                processIsInstanceUnwrapped(cast.getLocation(), cast.getValue(), targetClassName, instanceOfResult);

                var invoke = new InvokeInstruction();
                invoke.setType(InvocationType.SPECIAL);
                invoke.setMethod(JSMethods.THROW_CCE_IF_FALSE);
                invoke.setArguments(instanceOfResult, cast.getValue());
                invoke.setReceiver(cast.getReceiver());
                replacement.add(invoke);
            }
            return true;
        }

        Variable result = marshaller.unwrapFunctor(location, cast.getValue(), targetClass);
        var assign = new AssignInstruction();
        assign.setLocation(location.getSourceLocation());
        assign.setAssignee(result);
        assign.setReceiver(cast.getReceiver());
        replacement.add(assign);

        return true;
    }

    private void processIsInstance(IsInstanceInstruction isInstance) {
        if (!(isInstance.getType() instanceof ValueType.Object)) {
            isInstance.setType(processType(isInstance.getType()));
            return;
        }

        String targetClassName = ((ValueType.Object) isInstance.getType()).getClassName();
        if (!typeHelper.isJavaScriptClass(targetClassName)) {
            return;
        }

        replacement.clear();
        processIsInstance(isInstance.getLocation(), types.typeOf(isInstance.getValue()), isInstance.getValue(),
                targetClassName, isInstance.getReceiver());
        isInstance.insertPreviousAll(replacement);
        isInstance.delete();
        replacement.clear();
    }

    private void processIsInstance(TextLocation location, JSType type, Variable value, String targetClassName,
            Variable receiver) {
        if (type == JSType.JS) {
            if (isTransparent(targetClassName)) {
                var cst = new IntegerConstantInstruction();
                cst.setConstant(1);
                cst.setReceiver(receiver);
                cst.setLocation(location);
                replacement.add(cst);
            } else {
                var primitiveType = getPrimitiveType(targetClassName);
                var invoke = new InvokeInstruction();
                invoke.setType(InvocationType.SPECIAL);
                invoke.setMethod(primitiveType != null ? JSMethods.IS_PRIMITIVE : JSMethods.INSTANCE_OF);
                var secondArg = primitiveType != null
                        ? marshaller.addJsString(primitiveType, location)
                        : marshaller.classRef(targetClassName, location);
                invoke.setArguments(value, secondArg);
                invoke.setReceiver(receiver);
                invoke.setLocation(location);
                replacement.add(invoke);
            }
        } else {
            if (isTransparent(targetClassName)) {
                var invoke = new InvokeInstruction();
                invoke.setType(InvocationType.SPECIAL);
                invoke.setMethod(IS_JS);
                invoke.setArguments(value);
                invoke.setReceiver(receiver);
                invoke.setLocation(location);
                replacement.add(invoke);
            } else {
                var primitiveType = getPrimitiveType(targetClassName);
                var invoke = new InvokeInstruction();
                invoke.setType(InvocationType.SPECIAL);
                invoke.setMethod(primitiveType != null ? IS_PRIMITIVE : INSTANCE_OF);
                var secondArg = primitiveType != null
                        ? marshaller.addJsString(primitiveType, location)
                        : marshaller.classRef(targetClassName, location);
                invoke.setArguments(value, secondArg);
                invoke.setReceiver(receiver);
                invoke.setLocation(location);
                replacement.add(invoke);
            }
        }
    }

    private void processIsInstanceUnwrapped(TextLocation location, Variable value, String targetClassName,
            Variable receiver) {
        var primitiveType = getPrimitiveType(targetClassName);
        var invoke = new InvokeInstruction();
        invoke.setType(InvocationType.SPECIAL);
        invoke.setMethod(primitiveType != null ? JSMethods.IS_PRIMITIVE : JSMethods.INSTANCE_OF_OR_NULL);
        var secondArg = primitiveType != null
                ? marshaller.addJsString(primitiveType, location)
                : marshaller.classRef(targetClassName, location);
        invoke.setArguments(value, secondArg);
        invoke.setReceiver(receiver);
        invoke.setLocation(location);
        replacement.add(invoke);
    }

    private boolean isTransparent(String className) {
        var cls = classSource.get(className);
        if (cls == null) {
            return true;
        }
        if (cls.hasModifier(ElementModifier.INTERFACE)) {
            return true;
        }
        var clsAnnot = cls.getAnnotations().get(JSClass.class.getName());
        if (clsAnnot != null) {
            var transparent = clsAnnot.getValue("transparent");
            if (transparent != null) {
                return transparent.getBoolean();
            }
        }
        return false;
    }

    private String getPrimitiveType(String className) {
        var cls = classSource.get(className);
        if (cls == null) {
            return null;
        }
        var clsAnnot = cls.getAnnotations().get(JSPrimitiveType.class.getName());
        if (clsAnnot != null) {
            var value = clsAnnot.getValue("value");
            if (value != null) {
                return value.getString();
            }
        }
        return null;
    }

    private Variable wrapJsAsJava(Instruction instruction, Variable var, ValueType type) {
        if (!(type instanceof ValueType.Object)) {
            return var;
        }

        var cls = ((ValueType.Object) type).getClassName();
        if (typeHelper.isJavaScriptClass(cls)) {
            return var;
        }

        var varType = types.typeOf(var);
        if (varType != JSType.JS && varType != JSType.MIXED) {
            return var;
        }
        var wrap = new InvokeInstruction();
        wrap.setType(InvocationType.SPECIAL);
        wrap.setMethod(varType == JSType.JS ? WRAP : MAYBE_WRAP);
        wrap.setArguments(var);
        wrap.setReceiver(program.createVariable());
        wrap.setLocation(instruction.getLocation());
        instruction.insertPrevious(wrap);
        return wrap.getReceiver();
    }

    private Variable unwrapJavaToJs(Instruction instruction, Variable var) {
        var varType = types.typeOf(var);
        if (varType != JSType.JAVA && varType != JSType.MIXED) {
            return var;
        }
        var unwrap = new InvokeInstruction();
        unwrap.setType(InvocationType.SPECIAL);
        unwrap.setMethod(varType == JSType.JAVA ? UNWRAP : MAYBE_UNWRAP);
        unwrap.setArguments(var);
        unwrap.setReceiver(program.createVariable());
        unwrap.setLocation(instruction.getLocation());
        instruction.insertPrevious(unwrap);
        return unwrap.getReceiver();
    }

    private boolean processToString(InvokeInstruction invoke, CallLocation location) {
        if (!invoke.getMethod().getName().equals("toString") || !invoke.getArguments().isEmpty()
                || invoke.getInstance() == null || !invoke.getMethod().getReturnType().isObject(String.class)
                || types.typeOf(invoke.getInstance()) != JSType.JS) {
            return false;
        }

        replacement.clear();
        var methodName = marshaller.addStringWrap(marshaller.addString("toString", invoke.getLocation()),
                invoke.getLocation());

        var jsInvoke = new InvokeInstruction();
        jsInvoke.setType(InvocationType.SPECIAL);
        jsInvoke.setMethod(JSMethods.invoke(0));
        jsInvoke.setReceiver(program.createVariable());
        jsInvoke.setLocation(invoke.getLocation());
        jsInvoke.setArguments(invoke.getInstance(), methodName);
        replacement.add(jsInvoke);

        var assign = new AssignInstruction();
        assign.setAssignee(marshaller.unwrapReturnValue(location, jsInvoke.getReceiver(),
                invoke.getMethod().getReturnType(), false, canBeOnlyJava(invoke.getReceiver())));
        assign.setReceiver(invoke.getReceiver());
        assign.setLocation(invoke.getLocation());
        replacement.add(assign);

        invoke.insertNextAll(replacement);
        replacement.clear();
        invoke.delete();

        return true;
    }

    private boolean processInvocation(MethodReader method, CallLocation callLocation, InvokeInstruction invoke,
            MethodHolder methodToProcess) {
        if (method.getAnnotations().get(JSBody.class.getName()) != null) {
            return processJSBodyInvocation(method, callLocation, invoke, methodToProcess);
        }

        if (!typeHelper.isJavaScriptClass(invoke.getMethod().getClassName())) {
            return false;
        }

        if (method.getName().equals("")) {
            return processConstructor(method, callLocation, invoke);
        }

        var isStatic = method.hasModifier(ElementModifier.STATIC);
        if (isStatic) {
            switch (method.getOwnerName()) {
                case "org.teavm.platform.PlatformQueue":
                    return false;
            }
        }
        if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) {
            if (isStatic) {
                return false;
            }
            MethodReader overridden = getOverriddenMethod(method);
            if (overridden != null) {
                diagnostics.error(callLocation, "JS final method {{m0}} overrides {{m1}}. "
                        + "Overriding final method of overlay types is prohibited.",
                        method.getReference(), overridden.getReference());
            }
            if (method.getProgram() != null && method.getProgram().basicBlockCount() > 0) {
                invoke.setMethod(new MethodReference(method.getOwnerName(), method.getName() + "$static",
                        getStaticSignature(method.getReference())));
                Variable[] newArguments = new Variable[invoke.getArguments().size() + 1];
                newArguments[0] = invoke.getInstance();
                for (int i = 0; i < invoke.getArguments().size(); ++i) {
                    newArguments[i + 1] = invoke.getArguments().get(i);
                }
                invoke.setArguments(newArguments);
                invoke.setInstance(null);
            }
            invoke.setType(InvocationType.SPECIAL);
            return false;
        }

        var annot = annotationCache.get(method.getReference(), callLocation);
        if (annot != null) {
            switch (annot.kind) {
                case PROPERTY:
                    return processProperty(method, annot.name, callLocation, invoke);
                case INDEXER:
                    return processIndexer(method, callLocation, invoke);
                case METHOD:
                    return processMethod(method, annot.name, callLocation, invoke);
            }
        }
        return processMethod(method, null, callLocation, invoke);
    }

    private boolean processJSBodyInvocation(MethodReader method, CallLocation callLocation, InvokeInstruction invoke,
            MethodHolder methodToProcess) {
        boolean[] byRefParams = new boolean[method.parameterCount()];
        validateSignature(method, callLocation, byRefParams);
        if (invoke.getInstance() != null) {
            if (!typeHelper.isSupportedType(ValueType.object(method.getOwnerName()))) {
                diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
                        + "declaration. It is non-static and declared on a non-overlay class {{c1}}",
                        invoke.getMethod(), method.getOwnerName());
            }
        }

        boolean returnByRef = method.getAnnotations().get(JSByRef.class.getName()) != null;
        if (returnByRef && !typeHelper.isSupportedByRefType(method.getResultType())) {
            diagnostics.error(callLocation, "Method {{m0}} is marked with @JSByRef, but does not return valid "
                    + "array type", method.getReference());
            return false;
        }

        requireJSBody(diagnostics, method);
        MethodReference delegate = repository.methodMap.get(method.getReference());
        if (delegate == null) {
            return false;
        }

        Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
        InvokeInstruction newInvoke = new InvokeInstruction();
        newInvoke.setMethod(delegate);
        newInvoke.setType(InvocationType.SPECIAL);
        newInvoke.setReceiver(result);
        newInvoke.setLocation(invoke.getLocation());
        List newArgs = new ArrayList<>();
        if (invoke.getInstance() != null) {
            var arg = invoke.getInstance();
            arg = marshaller.wrapArgument(callLocation, arg,
                    ValueType.object(method.getOwnerName()), types.typeOf(arg), false);
            newArgs.add(arg);
        }
        for (int i = 0; i < invoke.getArguments().size(); ++i) {
            var arg = invoke.getArguments().get(i);
            arg = marshaller.wrapArgument(callLocation, invoke.getArguments().get(i),
                    method.parameterType(i), types.typeOf(arg), byRefParams[i]);
            newArgs.add(arg);
        }
        newInvoke.setArguments(newArgs.toArray(new Variable[0]));
        replacement.add(newInvoke);
        if (result != null) {
            result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType(), returnByRef,
                    canBeOnlyJava(invoke.getReceiver()));
            copyVar(result, invoke.getReceiver(), invoke.getLocation());
        }

        incrementalCache.addDependencies(methodToProcess.getReference(), method.getOwnerName());

        return true;
    }

    private boolean processProperty(MethodReader method, String suggestedName, CallLocation callLocation,
            InvokeInstruction invoke) {
        boolean pure = method.getAnnotations().get(NO_SIDE_EFFECTS) != null;
        if (isProperGetter(method, suggestedName)) {
            var propertyName = suggestedName;
            if (propertyName == null) {
                propertyName = method.getName().charAt(0) == 'i' ? cutPrefix(method.getName(), 2)
                        : cutPrefix(method.getName(), 3);
            }
            Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
            addPropertyGet(propertyName, getCallTarget(invoke), result, invoke.getLocation(), pure);
            if (result != null) {
                result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType(), false,
                        canBeOnlyJava(invoke.getReceiver()));
                copyVar(result, invoke.getReceiver(), invoke.getLocation());
            }
            return true;
        }
        if (isProperSetter(method, suggestedName)) {
            var propertyName = suggestedName;
            if (propertyName == null) {
                propertyName = cutPrefix(method.getName(), 3);
            }
            var value = invoke.getArguments().get(0);
            value = marshaller.wrapArgument(callLocation, value,
                    method.parameterType(0), types.typeOf(value), false);
            addPropertySet(propertyName, getCallTarget(invoke), value, invoke.getLocation(), pure);
            return true;
        }
        diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript property "
                + "declaration", invoke.getMethod());
        return false;
    }

    private boolean processIndexer(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
        if (isProperGetIndexer(method.getDescriptor())) {
            Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
            var index = invoke.getArguments().get(0);
            addIndexerGet(getCallTarget(invoke), marshaller.wrapArgument(callLocation, index,
                    method.parameterType(0), types.typeOf(index), false), result, invoke.getLocation());
            if (result != null) {
                result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType(), false,
                        canBeOnlyJava(invoke.getReceiver()));
                copyVar(result, invoke.getReceiver(), invoke.getLocation());
            }
            return true;
        }
        if (isProperSetIndexer(method.getDescriptor())) {
            var index = invoke.getArguments().get(0);
            index = marshaller.wrapArgument(callLocation, index, method.parameterType(0), types.typeOf(index), false);
            var value = invoke.getArguments().get(1);
            value = marshaller.wrapArgument(callLocation, value, method.parameterType(1),
                    types.typeOf(value), false);
            addIndexerSet(getCallTarget(invoke), index, value, invoke.getLocation());
            return true;
        }
        diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript indexer "
                + "declaration", invoke.getMethod());
        return false;
    }

    private boolean validateSignature(MethodReader method, CallLocation callLocation, boolean[] byRefParams) {
        if (method.getResultType() != ValueType.VOID && !typeHelper.isSupportedType(method.getResultType())) {
            diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
                    + "declaration, since it returns wrong type", method.getReference());
            return false;
        }

        ValueType[] parameterTypes = method.getParameterTypes();
        AnnotationContainerReader[] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterTypes.length; i++) {
            ValueType paramType = parameterTypes[i];
            if (!typeHelper.isSupportedType(paramType)) {
                diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
                        + "declaration: its " + (i + 1) + "th parameter has wrong type", method.getReference());
                return false;
            }
            if (parameterAnnotations[i].get(JSByRef.class.getName()) != null) {
                if (!typeHelper.isSupportedByRefType(paramType)) {
                    diagnostics.error(callLocation, "Method {{m0}} is not a proper native JavaScript method "
                            + "declaration: its " + (i + 1) + "th parameter is declared as JSByRef, "
                            + "which has incompatible type", method.getReference());
                    return false;
                }
                byRefParams[i] = true;
            }
        }

        return true;
    }

    private boolean canBeOnlyJava(Variable variable) {
        var type = types.typeOf(variable);
        return type != JSType.JS && type != JSType.MIXED;
    }

    private boolean processMethod(MethodReader method, String name, CallLocation callLocation,
            InvokeInstruction invoke) {
        if (name == null) {
            name = method.getName();
        }
        boolean[] byRefParams = new boolean[method.parameterCount() + 1];
        if (!validateSignature(method, callLocation, byRefParams)) {
            return false;
        }

        var vararg = method.hasModifier(ElementModifier.VARARGS);
        Variable result = invoke.getReceiver() != null ? program.createVariable() : null;
        InvokeInstruction newInvoke = new InvokeInstruction();
        newInvoke.setMethod(vararg ? JSMethods.APPLY : JSMethods.invoke(method.parameterCount()));
        newInvoke.setType(InvocationType.SPECIAL);
        newInvoke.setReceiver(result);

        List newArguments = new ArrayList<>();
        newArguments.add(getCallTarget(invoke));
        newArguments.add(marshaller.addStringWrap(marshaller.addString(name, invoke.getLocation()),
                invoke.getLocation()));
        newInvoke.setLocation(invoke.getLocation());

        var callArguments = new ArrayList();
        for (int i = 0; i < invoke.getArguments().size(); ++i) {
            var arg = invoke.getArguments().get(i);
            var byRef = byRefParams[i];
            if (vararg && i == invoke.getArguments().size() - 1
                    && typeHelper.isSupportedByRefType(method.parameterType(i))) {
                byRef = true;
            }
            arg = marshaller.wrapArgument(callLocation, arg,
                    method.parameterType(i), types.typeOf(arg), byRef);
            callArguments.add(arg);
        }

        if (vararg) {
            Variable prefixArg = null;
            if (callArguments.size() > 1) {
                var arrayOfInvocation = new InvokeInstruction();
                arrayOfInvocation.setType(InvocationType.SPECIAL);
                arrayOfInvocation.setArguments(callArguments.subList(0, callArguments.size() - 1)
                        .toArray(new Variable[0]));
                arrayOfInvocation.setMethod(JSMethods.arrayOf(callArguments.size() - 1));
                arrayOfInvocation.setReceiver(program.createVariable());
                arrayOfInvocation.setLocation(invoke.getLocation());
                replacement.add(arrayOfInvocation);
                prefixArg = arrayOfInvocation.getReceiver();
            }

            var arrayArg = callArguments.get(callArguments.size() - 1);

            if (prefixArg != null) {
                var concat = new InvokeInstruction();
                concat.setType(InvocationType.SPECIAL);
                concat.setArguments(prefixArg, arrayArg);
                concat.setMethod(JSMethods.CONCAT_ARRAY);
                concat.setReceiver(program.createVariable());
                concat.setLocation(invoke.getLocation());
                replacement.add(concat);
                arrayArg = concat.getReceiver();
            }
            newArguments.add(arrayArg);
        } else {
            newArguments.addAll(callArguments);
        }
        newInvoke.setArguments(newArguments.toArray(new Variable[0]));

        replacement.add(newInvoke);
        if (result != null) {
            result = marshaller.unwrapReturnValue(callLocation, result, method.getResultType(), false,
                    canBeOnlyJava(invoke.getReceiver()));
            copyVar(result, invoke.getReceiver(), invoke.getLocation());
        }

        return true;
    }

    private ClassReader getObjectClass() {
        if (objectClass == null) {
            objectClass = classSource.get("java.lang.Object");
        }
        return objectClass;
    }

    private Variable getCallTarget(InvokeInstruction invoke) {
        if (invoke.getInstance() != null) {
            return invoke.getInstance();
        }
        var cls = classSource.get(invoke.getMethod().getClassName());
        var method = cls != null ? cls.getMethod(invoke.getMethod().getDescriptor()) : null;
        var isTopLevel = (cls != null && cls.getAnnotations().get(JSTopLevel.class.getName()) != null)
                || (method != null && method.getAnnotations().get(JSTopLevel.class.getName()) != null);
        if (isTopLevel) {
            var methodAnnotations = method != null ? method.getAnnotations() : null;
            return marshaller.moduleRef(invoke.getMethod().getClassName(), methodAnnotations, invoke.getLocation());
        } else {
            return marshaller.classRef(invoke.getMethod().getClassName(), invoke.getLocation());
        }
    }

    private boolean processConstructor(MethodReader method, CallLocation callLocation, InvokeInstruction invoke) {
        var byRefParams = new boolean[method.parameterCount() + 1];
        if (!validateSignature(method, callLocation, byRefParams)) {
            return false;
        }

        var result = program.variableAt(variableAliases[invoke.getInstance().getIndex()]);
        var newInvoke = new InvokeInstruction();
        newInvoke.setMethod(JSMethods.construct(method.parameterCount()));
        newInvoke.setType(InvocationType.SPECIAL);
        newInvoke.setReceiver(result);
        var newArguments = new ArrayList();
        newArguments.add(marshaller.classRef(invoke.getMethod().getClassName(), invoke.getLocation()));
        newInvoke.setLocation(invoke.getLocation());
        for (int i = 0; i < invoke.getArguments().size(); ++i) {
            var arg = invoke.getArguments().get(i);
            arg = marshaller.wrapArgument(callLocation, arg,
                    method.parameterType(i), types.typeOf(arg), byRefParams[i]);
            newArguments.add(arg);
        }
        newInvoke.setArguments(newArguments.toArray(new Variable[0]));
        replacement.add(newInvoke);

        return true;
    }

    private void requireJSBody(Diagnostics diagnostics, MethodReader methodToProcess) {
        if (!repository.processedMethods.add(methodToProcess.getReference())) {
            return;
        }
        processJSBody(diagnostics, methodToProcess);
    }

    private void processJSBody(Diagnostics diagnostics, MethodReader methodToProcess) {
        CallLocation location = new CallLocation(methodToProcess.getReference());
        boolean isStatic = methodToProcess.hasModifier(ElementModifier.STATIC);

        // validate parameter names
        AnnotationReader bodyAnnot = methodToProcess.getAnnotations().get(JSBody.class.getName());
        AnnotationValue paramsValue = bodyAnnot.getValue("params");
        int jsParamCount = paramsValue != null ? paramsValue.getList().size() : 0;
        if (methodToProcess.parameterCount() != jsParamCount) {
            diagnostics.error(location, "JSBody method {{m0}} declares " + methodToProcess.parameterCount()
                    + " parameters, but annotation specifies " + jsParamCount, methodToProcess.getReference());
            return;
        }

        // generate parameter types for original method and validate
        int paramCount = methodToProcess.parameterCount();
        if (!isStatic) {
            ++paramCount;
        }
        if (!isStatic) {
            ValueType paramType = ValueType.object(methodToProcess.getOwnerName());
            if (!typeHelper.isSupportedType(paramType)) {
                diagnostics.error(location, "Non-static JSBody method {{m0}} is owned by non-JS class {{c1}}",
                        methodToProcess.getReference(), methodToProcess.getOwnerName());
            }
        }
        if (methodToProcess.getResultType() != ValueType.VOID
                && !typeHelper.isSupportedType(methodToProcess.getResultType())) {
            diagnostics.error(location, "JSBody method {{m0}} returns unsupported type {{t1}}",
                    methodToProcess.getReference(), methodToProcess.getResultType());
        }

        // generate parameter types for proxy method
        ValueType[] proxyParamTypes = new ValueType[paramCount + 1];
        for (int i = 0; i < paramCount; ++i) {
            proxyParamTypes[i] = ValueType.parse(JSObject.class);
        }
        proxyParamTypes[paramCount] = methodToProcess.getResultType() == ValueType.VOID
                ? ValueType.VOID
                : ValueType.parse(JSObject.class);

        ClassReader ownerClass = classSource.get(methodToProcess.getOwnerName());
        int methodIndex = indexOfMethod(ownerClass, methodToProcess);

        // create proxy method
        MethodReference proxyMethod = new MethodReference(methodToProcess.getOwnerName(),
                methodToProcess.getName() + "$js_body$_" + methodIndex, proxyParamTypes);
        String script = bodyAnnot.getValue("script").getString();
        String[] parameterNames = paramsValue != null ? paramsValue.getList().stream()
                .map(AnnotationValue::getString)
                .toArray(String[]::new) : new String[0];

        // Parse JS script
        var errorReporter = new TeaVMErrorReporter(diagnostics, new CallLocation(methodToProcess.getReference()));
        var env = new CompilerEnvirons();
        env.setRecoverFromErrors(true);
        env.setLanguageVersion(Context.VERSION_1_8);
        env.setIdeMode(true);
        var parser = new JSParser(env, errorReporter);
        AstRoot rootNode;
        try {
            rootNode = (AstRoot) parser.parseAsObject(new StringReader("function(){" + script + "}"), null, 0);
        } catch (IOException e) {
            throw new RuntimeException("IO Error occurred", e);
        }
        var body = ((FunctionNode) rootNode.getFirstChild()).getBody();

        JsBodyImportInfo[] imports;
        var importsValue = bodyAnnot.getValue("imports");
        if (importsValue != null) {
            var importsList = importsValue.getList();
            imports = new JsBodyImportInfo[importsList.size()];
            for (var i = 0; i < importsList.size(); ++i) {
                var importAnnot = importsList.get(0).getAnnotation();
                imports[i] = new JsBodyImportInfo(importAnnot.getValue("alias").getString(),
                        importAnnot.getValue("fromModule").getString());
            }
        } else {
            imports = new JsBodyImportInfo[0];
        }

        repository.methodMap.put(methodToProcess.getReference(), proxyMethod);
        if (errorReporter.hasErrors()) {
            repository.emitters.put(proxyMethod, new JSBodyBloatedEmitter(isStatic, proxyMethod,
                    script, parameterNames, imports));
        } else {
            var expr = JSBodyInlineUtil.isSuitableForInlining(methodToProcess.getReference(),
                    parameterNames, body);
            if (expr != null) {
                repository.inlineMethods.add(methodToProcess.getReference());
            } else {
                expr = body;
            }
            javaInvocationProcessor.process(location, expr);
            var emitter = new JSBodyAstEmitter(isStatic, expr, rootNode, parameterNames, imports);
            repository.emitters.put(proxyMethod, emitter);
        }
        if (imports.length > 0) {
            repository.imports.put(proxyMethod, imports);
        }
    }

    private int indexOfMethod(ClassReader cls, MethodReader method) {
        int index = 0;
        for (MethodReader m : cls.getMethods()) {
            if (m.getDescriptor().equals(method.getDescriptor())) {
                return index;
            }
            ++index;
        }
        return -1;
    }

    void createJSMethods(ClassHolder cls) {
        for (MethodHolder method : cls.getMethods().toArray(new MethodHolder[0])) {
            MethodReference methodRef = method.getReference();
            if (method.getAnnotations().get(JSBody.class.getName()) == null) {
                continue;
            }

            requireJSBody(diagnostics, method);
            if (!repository.methodMap.containsKey(method.getReference())) {
                continue;
            }

            MethodReference proxyRef = repository.methodMap.get(methodRef);
            MethodHolder proxyMethod = new MethodHolder(proxyRef.getDescriptor());
            proxyMethod.getModifiers().add(ElementModifier.NATIVE);
            proxyMethod.getModifiers().add(ElementModifier.STATIC);
            if (method.getAnnotations().get(NoSideEffects.class.getName()) != null) {
                proxyMethod.getAnnotations().add(new AnnotationHolder(NoSideEffects.class.getName()));
            }
            boolean inline = repository.inlineMethods.contains(methodRef);
            AnnotationHolder generatorAnnot = new AnnotationHolder(inline
                    ? DynamicInjector.class.getName() : DynamicGenerator.class.getName());
            proxyMethod.getAnnotations().add(generatorAnnot);
            cls.addMethod(proxyMethod);

            Set callbacks = repository.callbackMethods.get(proxyRef);
            if (callbacks != null) {
                for (MethodReference callback : callbacks) {
                    generateCallbackCaller(cls, callback);
                }
            }
        }
    }

    private void generateCallbackCaller(ClassHolder cls, MethodReference callback) {
        MethodReference calleeRef = repository.callbackCallees.get(callback);
        MethodReader callee = classSource.resolve(calleeRef);
        MethodHolder callerMethod = new MethodHolder(callback.getDescriptor());
        callerMethod.getModifiers().add(ElementModifier.STATIC);
        CallLocation location = new CallLocation(callback);

        setCurrentProgram(new Program());
        for (int i = 0; i <= callback.parameterCount(); ++i) {
            program.createVariable();
        }
        BasicBlock block = program.createBasicBlock();

        int paramIndex = 1;
        InvokeInstruction insn = new InvokeInstruction();
        insn.setType(InvocationType.SPECIAL);
        insn.setMethod(calleeRef);
        replacement.clear();
        if (!callee.hasModifier(ElementModifier.STATIC)) {
            insn.setInstance(marshaller.unwrapReturnValue(location, program.variableAt(paramIndex++),
                    ValueType.object(calleeRef.getClassName()), false, true));
        }
        Variable[] args = new Variable[callee.parameterCount()];
        for (int i = 0; i < callee.parameterCount(); ++i) {
            args[i] = marshaller.unwrapReturnValue(location, program.variableAt(paramIndex++),
                    callee.parameterType(i), false, true);
        }
        insn.setArguments(args);
        if (callee.getResultType() != ValueType.VOID) {
            insn.setReceiver(program.createVariable());
        }
        block.addAll(replacement);
        block.add(insn);

        ExitInstruction exit = new ExitInstruction();
        if (insn.getReceiver() != null) {
            replacement.clear();
            exit.setValueToReturn(marshaller.wrap(insn.getReceiver(), callee.getResultType(), JSType.MIXED,
                    null, false));
            block.addAll(replacement);
        }
        block.add(exit);

        callerMethod.setProgram(program);
        cls.addMethod(callerMethod);
        processProgram(callerMethod);
    }

    private void addPropertyGet(String propertyName, Variable instance, Variable receiver,
            TextLocation location, boolean pure) {
        Variable nameVar = marshaller.addStringWrap(marshaller.addString(propertyName, location), location);
        InvokeInstruction insn = new InvokeInstruction();
        insn.setType(InvocationType.SPECIAL);
        insn.setMethod(pure ? JSMethods.GET_PURE : JSMethods.GET);
        insn.setReceiver(receiver);
        insn.setArguments(instance, nameVar);
        insn.setLocation(location);
        replacement.add(insn);
    }

    private void addPropertySet(String propertyName, Variable instance, Variable value, TextLocation location,
            boolean pure) {
        Variable nameVar = marshaller.addStringWrap(marshaller.addString(propertyName, location), location);
        InvokeInstruction insn = new InvokeInstruction();
        insn.setType(InvocationType.SPECIAL);
        insn.setMethod(pure ? JSMethods.SET_PURE : JSMethods.SET);
        insn.setArguments(instance, nameVar, value);
        insn.setLocation(location);
        replacement.add(insn);
    }

    private void addIndexerGet(Variable array, Variable index, Variable receiver, TextLocation location) {
        InvokeInstruction insn = new InvokeInstruction();
        insn.setType(InvocationType.SPECIAL);
        insn.setMethod(JSMethods.GET);
        insn.setReceiver(receiver);
        insn.setArguments(array, index);
        insn.setLocation(location);
        replacement.add(insn);
    }

    private void addIndexerSet(Variable array, Variable index, Variable value, TextLocation location) {
        InvokeInstruction insn = new InvokeInstruction();
        insn.setType(InvocationType.SPECIAL);
        insn.setMethod(JSMethods.SET);
        insn.setArguments(array, index, value);
        insn.setLocation(location);
        replacement.add(insn);
    }

    private void copyVar(Variable a, Variable b, TextLocation location) {
        AssignInstruction insn = new AssignInstruction();
        insn.setAssignee(a);
        insn.setReceiver(b);
        insn.setLocation(location);
        replacement.add(insn);
    }

    private MethodReader getMethod(String className, MethodDescriptor descriptor) {
        ClassReader cls = classSource.get(className);
        if (cls == null) {
            return null;
        }
        MethodReader method = cls.getMethod(descriptor);
        if (method != null) {
            return method;
        }
        if (cls.getParent() != null && !cls.getParent().equals("java.lang.Object")) {
            method = getMethod(cls.getParent(), descriptor);
            if (method != null) {
                return method;
            }
        }
        for (String iface : cls.getInterfaces()) {
            method = getMethod(iface, descriptor);
            if (method != null) {
                return method;
            }
        }
        return null;
    }

    private boolean isProperGetter(MethodReader method, String suggestedName) {
        if (method.parameterCount() > 0 || !typeHelper.isSupportedType(method.getResultType())) {
            return false;
        }
        if (suggestedName != null) {
            return true;
        }

        if (method.getResultType().equals(ValueType.BOOLEAN)) {
            if (isProperPrefix(method.getName(), "is")) {
                return true;
            }
        }
        return isProperPrefix(method.getName(), "get");
    }

    private boolean isProperSetter(MethodReader method, String suggestedName) {
        if (method.parameterCount() != 1 || !typeHelper.isSupportedType(method.parameterType(0))
                || method.getResultType() != ValueType.VOID) {
            return false;
        }

        return suggestedName != null || isProperPrefix(method.getName(), "set");
    }

    private boolean isProperPrefix(String name, String prefix) {
        if (!name.startsWith(prefix) || name.length() == prefix.length()) {
            return false;
        }
        char c = name.charAt(prefix.length());
        return Character.isUpperCase(c) || !Character.isAlphabetic(c) && Character.isJavaIdentifierStart(c);
    }

    private boolean isProperGetIndexer(MethodDescriptor desc) {
        return desc.parameterCount() == 1 && typeHelper.isSupportedType(desc.parameterType(0))
                && typeHelper.isSupportedType(desc.getResultType());
    }

    private boolean isProperSetIndexer(MethodDescriptor desc) {
        return desc.parameterCount() == 2 && typeHelper.isSupportedType(desc.parameterType(0))
                && typeHelper.isSupportedType(desc.parameterType(1)) && desc.getResultType() == ValueType.VOID;
    }

    private static String cutPrefix(String name, int prefixLength) {
        if (name.length() == prefixLength + 1) {
            return name.substring(prefixLength).toLowerCase();
        }
        char c = name.charAt(prefixLength + 1);
        if (Character.isUpperCase(c)) {
            return name.substring(prefixLength);
        }
        return Character.toLowerCase(name.charAt(prefixLength)) + name.substring(prefixLength + 1);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy