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

org.jsweet.transpiler.extension.Java2TypeScriptAdapter Maven / Gradle / Ivy

The newest version!
/*
 * JSweet transpiler - http://www.jsweet.org
 * Copyright (C) 2015 CINCHEO SAS 
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */
package org.jsweet.transpiler.extension;

import static java.util.Arrays.asList;
import static java.util.stream.Collectors.joining;
import static org.jsweet.JSweetConfig.ANNOTATION_ERASED;
import static org.jsweet.JSweetConfig.ANNOTATION_KEEP_USES;
import static org.jsweet.JSweetConfig.ANNOTATION_OBJECT_TYPE;
import static org.jsweet.JSweetConfig.ANNOTATION_STRING_ENUM;
import static org.jsweet.JSweetConfig.ANNOTATION_STRING_TYPE;
import static org.jsweet.JSweetConfig.DEPRECATED_UTIL_CLASSNAME;
import static org.jsweet.JSweetConfig.GLOBALS_CLASS_NAME;
import static org.jsweet.JSweetConfig.GLOBALS_PACKAGE_NAME;
import static org.jsweet.JSweetConfig.INDEXED_DELETE_FUCTION_NAME;
import static org.jsweet.JSweetConfig.INDEXED_DELETE_STATIC_FUCTION_NAME;
import static org.jsweet.JSweetConfig.INDEXED_GET_FUCTION_NAME;
import static org.jsweet.JSweetConfig.INDEXED_GET_STATIC_FUCTION_NAME;
import static org.jsweet.JSweetConfig.INDEXED_SET_FUCTION_NAME;
import static org.jsweet.JSweetConfig.INDEXED_SET_STATIC_FUCTION_NAME;
import static org.jsweet.JSweetConfig.INVOKE_FUCTION_NAME;
import static org.jsweet.JSweetConfig.LANG_PACKAGE;
import static org.jsweet.JSweetConfig.LANG_PACKAGE_ALT;
import static org.jsweet.JSweetConfig.UTIL_CLASSNAME;
import static org.jsweet.JSweetConfig.UTIL_PACKAGE;
import static org.jsweet.JSweetConfig.isJSweetPath;
import static org.jsweet.transpiler.Java2TypeScriptTranslator.CLASS_NAME_IN_CONSTRUCTOR;
import static org.jsweet.transpiler.Java2TypeScriptTranslator.ENUM_WRAPPER_CLASS_WRAPPERS;
import static org.jsweet.transpiler.Java2TypeScriptTranslator.INTERFACES_FIELD_NAME;

import java.lang.reflect.Field;
import java.util.Date;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.function.BiFunction;
import java.util.function.BooleanSupplier;
import java.util.function.DoubleBinaryOperator;
import java.util.function.DoubleConsumer;
import java.util.function.DoublePredicate;
import java.util.function.DoubleSupplier;
import java.util.function.DoubleToIntFunction;
import java.util.function.DoubleToLongFunction;
import java.util.function.DoubleUnaryOperator;
import java.util.function.Function;
import java.util.function.IntBinaryOperator;
import java.util.function.IntConsumer;
import java.util.function.IntPredicate;
import java.util.function.IntSupplier;
import java.util.function.IntToDoubleFunction;
import java.util.function.IntToLongFunction;
import java.util.function.IntUnaryOperator;
import java.util.function.LongBinaryOperator;
import java.util.function.LongConsumer;
import java.util.function.LongPredicate;
import java.util.function.LongSupplier;
import java.util.function.LongToDoubleFunction;
import java.util.function.LongToIntFunction;
import java.util.function.LongUnaryOperator;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.*;

import org.apache.commons.lang3.StringUtils;
import org.jsweet.JSweetConfig;
import org.jsweet.transpiler.JSweetContext;
import org.jsweet.transpiler.JSweetProblem;
import org.jsweet.transpiler.JSweetTranspiler;
import org.jsweet.transpiler.Java2TypeScriptTranslator;
import org.jsweet.transpiler.Java2TypeScriptTranslator.ComparisonMode;
import org.jsweet.transpiler.ModuleImportDescriptor;
import org.jsweet.transpiler.TypeChecker;
import org.jsweet.transpiler.model.ExtendedElement;
import org.jsweet.transpiler.model.ExtendedElementFactory;
import org.jsweet.transpiler.model.ForeachLoopElement;
import org.jsweet.transpiler.model.IdentifierElement;
import org.jsweet.transpiler.model.ImportElement;
import org.jsweet.transpiler.model.InvocationElement;
import org.jsweet.transpiler.model.LiteralElement;
import org.jsweet.transpiler.model.MethodInvocationElement;
import org.jsweet.transpiler.model.NewClassElement;
import org.jsweet.transpiler.model.VariableAccessElement;
import org.jsweet.transpiler.model.support.MethodInvocationElementSupport;
import org.jsweet.transpiler.util.Util;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.TypeCastTree;

/**
 * This is an adapter for the TypeScript code generator. It overrides the
 * default adapter's behavior.
 *
 * @author Renaud Pawlak
 */
public class Java2TypeScriptAdapter extends PrinterAdapter {

    protected final static String VAR_DECL_KEYWORD = Java2TypeScriptTranslator.VAR_DECL_KEYWORD;

    public Java2TypeScriptTranslator getPrinter() {
        return (Java2TypeScriptTranslator) super.getPrinter();
    }

    /**
     * Creates a root adapter (with no parent).
     *
     * @param context the transpilation context
     */
    public Java2TypeScriptAdapter(JSweetContext context) {
        super(context);
        init();
    }

    /**
     * Creates a new adapter that will try delegate to the given parent adapter when
     * not implementing its own behavior.
     *
     * @param parentAdapter cannot be null: if no parent you must use the
     *                      {@link #AbstractPrinterAdapter(JSweetContext)}
     *                      constructor
     */
    public Java2TypeScriptAdapter(PrinterAdapter parent) {
        super(parent);
        init();
    }

    private void init() {
        addTypeMapping(Object.class.getName(), "any");
        addTypeMapping(Runnable.class.getName(), "() => void");
        addTypeMapping(Callable.class.getName(), "() => any");

        addTypeMapping(DoubleConsumer.class.getName(), "(number) => void");
        addTypeMapping(DoublePredicate.class.getName(), "(number) => boolean");
        addTypeMapping(DoubleSupplier.class.getName(), "() => number");
        addTypeMapping(DoubleBinaryOperator.class.getName(), "(number, number) => number");
        addTypeMapping(DoubleUnaryOperator.class.getName(), "(number) => number");
        addTypeMapping(DoubleToIntFunction.class.getName(), "(number) => number");
        addTypeMapping(DoubleToLongFunction.class.getName(), "(number) => number");

        addTypeMapping(IntConsumer.class.getName(), "(number) => void");
        addTypeMapping(IntPredicate.class.getName(), "(number) => boolean");
        addTypeMapping(IntSupplier.class.getName(), "() => number");
        addTypeMapping(IntBinaryOperator.class.getName(), "(number, number) => number");
        addTypeMapping(IntUnaryOperator.class.getName(), "(number) => number");
        addTypeMapping(IntToDoubleFunction.class.getName(), "(number) => number");
        addTypeMapping(IntToLongFunction.class.getName(), "(number) => number");

        addTypeMapping(LongConsumer.class.getName(), "(number) => void");
        addTypeMapping(LongPredicate.class.getName(), "(number) => boolean");
        addTypeMapping(LongSupplier.class.getName(), "() => number");
        addTypeMapping(LongBinaryOperator.class.getName(), "(number, number) => number");
        addTypeMapping(LongUnaryOperator.class.getName(), "(number) => number");
        addTypeMapping(LongToDoubleFunction.class.getName(), "(number) => number");
        addTypeMapping(LongToIntFunction.class.getName(), "(number) => number");

        addTypeMapping(BooleanSupplier.class.getName(), "() => boolean");

        addTypeMapping(String.class.getName(), "string");
        addTypeMapping(Number.class.getName(), "number");
        addTypeMapping(Integer.class.getName(), "number");
        addTypeMapping(Short.class.getName(), "number");
        addTypeMapping(Float.class.getName(), "number");
        addTypeMapping(Long.class.getName(), "number");
        addTypeMapping(Byte.class.getName(), "number");
        addTypeMapping(Double.class.getName(), "number");
        addTypeMapping(Boolean.class.getName(), "boolean");
        addTypeMapping(Character.class.getName(), "string");
        addTypeMapping(CharSequence.class.getName(), "any");
        addTypeMapping(Void.class.getName(), "void");

        addTypeMapping("double", "number");
        addTypeMapping("int", "number");
        addTypeMapping("float", "number");
        addTypeMapping("long", "number");
        addTypeMapping("byte", "number");
        addTypeMapping("short", "number");
        addTypeMapping("char", "string");
        addTypeMapping("Class", "any");
        addTypeMapping(Field.class.getName(), "any");
        addTypeMapping(LANG_PACKAGE + ".Object", "Object");
        addTypeMapping(LANG_PACKAGE + ".Boolean", "boolean");
        addTypeMapping(LANG_PACKAGE + ".String", "string");
        addTypeMapping(LANG_PACKAGE + ".Number", "number");
        addTypeMapping(LANG_PACKAGE_ALT + ".Object", "Object");
        addTypeMapping(LANG_PACKAGE_ALT + ".Boolean", "boolean");
        addTypeMapping(LANG_PACKAGE_ALT + ".String", "string");
        addTypeMapping(LANG_PACKAGE_ALT + ".Number", "number");

        context.getLangTypeMappings().put(Object.class.getName(), "Object");
        context.getLangTypeMappings().put(String.class.getName(), "String");
        context.getLangTypeMappings().put(Boolean.class.getName(), "Boolean");
        context.getLangTypeMappings().put(Number.class.getName(), "Number");
        context.getLangTypeMappings().put(Integer.class.getName(), "Number");
        context.getLangTypeMappings().put(Long.class.getName(), "Number");
        context.getLangTypeMappings().put(Short.class.getName(), "Number");
        context.getLangTypeMappings().put(Float.class.getName(), "Number");
        context.getLangTypeMappings().put(Double.class.getName(), "Number");
        context.getLangTypeMappings().put(Byte.class.getName(), "Number");
        context.getLangTypeMappings().put(Character.class.getName(), "String");
        context.getLangTypeMappings().put(Math.class.getName(), "Math");
        context.getLangTypeMappings().put(StrictMath.class.getName(), "Math");
        context.getLangTypeMappings().put(Exception.class.getName(), "Error");
        context.getLangTypeMappings().put(Throwable.class.getName(), "Error");
        context.getLangTypeMappings().put(Error.class.getName(), "Error");
        context.getLangTypeMappings().put(Date.class.getName(), "Date");

        for (String s : context.getLangTypeMappings().keySet()) {
            context.getLangTypesSimpleNames().add(s.substring(s.lastIndexOf('.') + 1));
        }

        context.getBaseThrowables().add(Throwable.class.getName());
        context.getBaseThrowables().add(Error.class.getName());
        context.getBaseThrowables().add(Exception.class.getName());

    }

    @Override
    public String needsImport(ImportElement importElement, String qualifiedName) {
        ImportTree importDecl = ExtendedElementFactory.toTree(importElement);
        if (isJSweetPath(qualifiedName) || isMappedType(qualifiedName)
                || context.getLangTypeMappings().containsKey(qualifiedName)
                || qualifiedName.startsWith("java.util.function.")
                || qualifiedName.endsWith(GLOBALS_PACKAGE_NAME + "." + GLOBALS_CLASS_NAME)) {
            return null;
        }
        if (importElement.getImportedType() != null) {
            if (context.hasAnnotationType(importElement.getImportedType(), ANNOTATION_ERASED, ANNOTATION_OBJECT_TYPE)) {
                return null;
            }
            if (importElement.getImportedType().getKind() == ElementKind.ANNOTATION_TYPE
                    && !context.hasAnnotationType(importElement.getImportedType(), JSweetConfig.ANNOTATION_DECORATOR)) {
                return null;
            }
        }
        if (importDecl.isStatic()) {
            if (importDecl.getQualifiedIdentifier() instanceof MemberSelectTree) {
                MemberSelectTree staticImportSelect = (MemberSelectTree) importDecl.getQualifiedIdentifier();
                switch (staticImportSelect.getExpression().toString()) {
                case "java.lang.Math":
                    return null;
                }

                TypeElement typeElementForStaticImport = Util.getTypeElement(staticImportSelect.getExpression());
                String name = getPrinter().getRootRelativeName(typeElementForStaticImport, false);
                String methodName = staticImportSelect.getIdentifier().toString();

                // function is a top-level global function (no need to import)
                if (GLOBALS_CLASS_NAME.equals(name)) {
                    return null;
                }
                if (!context.useModules && name.endsWith(GLOBALS_PACKAGE_NAME + "." + GLOBALS_CLASS_NAME)) {
                    return null;
                }

                if (JSweetConfig.TS_STRICT_MODE_KEYWORDS.contains(methodName.toLowerCase())) {
                    // if method name is a reserved ts keyword, we have to fully
                    // qualify calls to it (hence discarding any import)
                    return null;
                }
                boolean globals = name.endsWith("." + JSweetConfig.GLOBALS_CLASS_NAME);
                if (globals) {
                    name = name.substring(0, name.length() - JSweetConfig.GLOBALS_CLASS_NAME.length() - 1);
                }

                // function belong to the current package (no need to import)
                String current = getPrinter().getRootRelativeName(getPackageElement(), context.useModules);
                if (context.useModules) {
                    if (current.equals(name)) {
                        return null;
                    }
                } else {
                    if (current.startsWith(name)) {
                        return null;
                    }
                }
                Element nameSymbol = Util.getElement(staticImportSelect);
                if (nameSymbol == null) {
                    nameSymbol = util().findFirstDeclarationInType(typeElementForStaticImport, methodName);
                }

                return StringUtils.isBlank(name) ? null
                        : name + "." + (nameSymbol == null ? methodName : getPrinter().getIdentifier(nameSymbol));
            } else {
                return null;
            }
        } else {
            if (context.useModules) {
                // check if inner class and do not import
                if (importDecl.getQualifiedIdentifier() instanceof MemberSelectTree) {
                    MemberSelectTree qualified = (MemberSelectTree) importDecl.getQualifiedIdentifier();
                    Element importedElement = Util.getElement(qualified);
                    if (importedElement instanceof TypeElement
                            && importedElement.getEnclosingElement() instanceof TypeElement) {
                        return null;
                    }
                }
            }
        }
        if (importElement.isStatic()) {
            return null;
        } else {
            return super.needsImport(importElement, qualifiedName);
        }
    }

    private boolean isWithinGlobals(String targetClassName) {
        if (targetClassName == null
                || (targetClassName.equals(UTIL_CLASSNAME) || targetClassName.equals(DEPRECATED_UTIL_CLASSNAME))) {
            ClassTree parentClassTree = getPrinter().getParent(ClassTree.class);
            TypeElement parentClassElement = Util.getElement(parentClassTree);
            return parentClassTree != null
                    && parentClassElement.getQualifiedName().toString().endsWith("." + GLOBALS_CLASS_NAME);
        } else {
            return false;
        }
    }

    private boolean substituteUnresolvedMethodInvocation(MethodInvocationTree invocation) {
        // this is a patch that should be removed when Class.isInstance gets supported
        // by J4TS
        if (invocation.getMethodSelect() instanceof MemberSelectTree) {
            MemberSelectTree fieldAccess = (MemberSelectTree) invocation.getMethodSelect();
            String methName = fieldAccess.getIdentifier().toString();
            TypeElement typeElement = Util.getTypeElement(fieldAccess.getExpression());
            String typeName = typeElement != null ? typeElement.toString() : null;
            if (typeName != null && "isInstance".equals(methName) && Class.class.getName().equals(typeName)) {
                printMacroName(fieldAccess.toString());
                print("((c: any, o: any) => { if (typeof c === 'string') return (o.constructor && o.constructor")
                        .print("[\"" + Java2TypeScriptTranslator.INTERFACES_FIELD_NAME + "\"] && o.constructor")
                        .print("[\"" + Java2TypeScriptTranslator.INTERFACES_FIELD_NAME + "\"].indexOf(c) >= 0) || (o")
                        .print("[\"" + Java2TypeScriptTranslator.INTERFACES_FIELD_NAME + "\"] && o")
                        .print("[\"" + Java2TypeScriptTranslator.INTERFACES_FIELD_NAME
                                + "\"].indexOf(c) >= 0); else if (typeof c === 'function') return (o instanceof c) || (o.constructor && o.constructor === c); })(");
                getPrinter().print(fieldAccess.getExpression()).print(",").print(invocation.getArguments().get(0))
                        .print(")");
                return true;
            }
        }
        print("/*unresolved method*/");
        getPrinter().printDefaultMethodInvocation(invocation);
        return true;
    }

    @Override
    public boolean substituteMethodInvocation(MethodInvocationElement invocationElement) {

        if (invocationElement.getMethod() == null && context.options.isIgnoreJavaErrors()) {
            // may happen if the method is not available
            return substituteUnresolvedMethodInvocation(((MethodInvocationElementSupport) invocationElement).getTree());
        }

        Element targetTypeElement = util().getMethodOwner(invocationElement.getMethod());
        TypeMirror targetType = null;
        if (targetTypeElement != null) {
            targetType = targetTypeElement.asType();
        }

        // This is some sort of hack to avoid invoking erased methods.
        // If the containing class is erased, we still invoke it because we
        // don't know if the class may be provided externally.
        // Pitfalls: (1) may erase invocations that are provided externally, (2)
        // if the invocation is the target of an enclosing invocation or field
        // access, TS compilation will fail.
        // So, we should probably find a better way to erase invocations (or at
        // least do it conditionally).
        if (hasAnnotationType(invocationElement.getMethod(), ANNOTATION_ERASED)
                && !hasAnnotationType(invocationElement.getMethod(), ANNOTATION_KEEP_USES)
                && !isAmbientDeclaration(invocationElement.getMethod())) {
            print("null /*erased method " + invocationElement.getMethod() + "*/");
            return true;
        }

        if (invocationElement.getTargetExpression() != null) {
            targetType = invocationElement.getTargetExpression().getType();
            if (invocationElement.getTargetExpression().getTypeAsElement() != null) {
                targetTypeElement = invocationElement.getTargetExpression().getTypeAsElement();
            }
        }

        // resolve target type if generic (upper bound)
        if (targetType.getKind() == TypeKind.TYPEVAR) {
            TypeVariable typeVariableType = (TypeVariable) targetType;
            if (!util().isNullType(typeVariableType.getUpperBound())) {
                targetType = typeVariableType.getUpperBound();
                targetTypeElement = types().asElement(targetType);
            }
        }

        String targetMethodName = invocationElement.getMethodName();
        String targetClassName = targetType != null && targetType.getKind() == TypeKind.ARRAY ? "Array"
                : targetTypeElement.toString();

        if ("println".equals(targetMethodName) || "printf".equals(targetMethodName)
                || "print".equals(targetMethodName)) {
            if (invocationElement.getTargetExpression() != null) {
                if ("System.out".equals(invocationElement.getTargetExpression().toString())) {
                    PrinterAdapter print = print("console.info(");
                    if (invocationElement.getArgumentCount() > 0)
                        print.print(invocationElement.getArgument(0));
                    print.print(")");
                    return true;
                }
                if ("System.err".equals(invocationElement.getTargetExpression().toString())) {
                    PrinterAdapter print = print("console.error(");
                    if (invocationElement.getArgumentCount() > 0)
                        print.print(invocationElement.getArgument(0));
                    print.print(")");
                    return true;
                }
            }
        }

        MethodInvocationTree methodInvocationTree = ExtendedElementFactory.toTree(invocationElement);
        if ("super".equals(invocationElement.getMethodName())) {
            // we omit call to super if class extends nothing or if parent is an
            // interface
            ClassTree parentClassTree = getPrinter().getParent(ClassTree.class);
            if (parentClassTree.getExtendsClause() == null //
                    || context.isInterface(Util.getTypeElement(parentClassTree.getExtendsClause()))) {
                return true;
            }
            // special case when subclassing a Java exception type
            if (methodInvocationTree.getMethodSelect() instanceof IdentifierTree) {
                IdentifierTree methodSelectIdentifier = (IdentifierTree) methodInvocationTree.getMethodSelect();

                String superClassName = util()
                        .getQualifiedName(Util.getElement(methodSelectIdentifier).getEnclosingElement());
                if (context.getBaseThrowables().contains(superClassName)) {
                    // ES6 would take the cause, but we ignore it so far for
                    // backward compatibility
                    // PATCH:
                    // https://github.com/Microsoft/TypeScript/issues/5069
                    if (invocationElement.getArgumentCount() > 0) {
                        print("super(").print(invocationElement.getArgument(0)).print(")");
                        print("; this.message=").print(invocationElement.getArgument(0));
                    } else {
                        print("super()");
                    }
                    return true;
                }
            }
        }

        if (targetTypeElement != null && targetTypeElement.getKind() == ElementKind.ENUM
                && (invocationElement.getTargetExpression() != null
                        && !"this".equals(invocationElement.getTargetExpression().toString()))) {
            String relTarget = getRootRelativeName(targetTypeElement);
            switch (targetMethodName) {
            case "name":
                printMacroName("Enum." + targetMethodName);
                print(relTarget).print("[").print(invocationElement.getTargetExpression()).print("]");
                return true;
            case "ordinal":
                printMacroName("Enum." + targetMethodName);
                print(relTarget).print("[").print(relTarget).print("[").print(invocationElement.getTargetExpression())
                        .print("]").print("]");
                return true;
            case "valueOf":
                printMacroName("Enum." + targetMethodName);
                if (invocationElement.getArgumentCount() == 1) {
                    print("").print(invocationElement.getTargetExpression()).print("[")
                            .print(invocationElement.getArgument(0)).print("]");
                    return true;
                }
                break;
            case "values":
                printEnumValuesJSCode((TypeElement) targetTypeElement);
                return true;
            case "equals":
                printMacroName("Enum." + targetMethodName);
                print("((").print(invocationElement.getTargetExpression()).print(") === (")
                        .print(invocationElement.getArgument(0)).print("))");
                return true;
            case "compareTo":
                printMacroName("Enum." + targetMethodName);
                print("((").print(invocationElement.getTargetExpression()).print(") - (")
                        .print(invocationElement.getArgument(0)).print("))");
                return true;
            case "getClass":
            case "getDeclaringClass":
                printMacroName("Enum." + targetMethodName);
                print(relTarget);
                return true;
            }
            // enum objets wrapping
            if (invocationElement.getTargetExpression() != null) {
                if (invocationElement.getMethod().getModifiers().contains(Modifier.STATIC)) {
                    print(invocationElement.getTargetExpression())
                            .print(Java2TypeScriptTranslator.ENUM_WRAPPER_CLASS_SUFFIX + ".")
                            .print(invocationElement.getMethodName()).print("(")
                            .printArgList(invocationElement.getArguments()).print(")");

                    ModuleImportDescriptor moduleImport = getModuleImportDescriptor(getCompilationUnit(),
                            invocationElement.getTargetExpression().toString()
                                    + Java2TypeScriptTranslator.ENUM_WRAPPER_CLASS_SUFFIX,
                            (TypeElement) invocationElement.getTargetExpression().getTypeAsElement());
                    if (moduleImport != null) {
                        getPrinter().useModule(moduleImport);
                    }
                    return true;
                }
            }

            getPrinter().useModule(getModuleImportDescriptor(getCompilationUnit(),
                    context.getActualName(targetTypeElement), (TypeElement) targetTypeElement));
            print(relTarget).print("[\"" + Java2TypeScriptTranslator.ENUM_WRAPPER_CLASS_WRAPPERS + "\"][")
                    .print(invocationElement.getTargetExpression()).print("].").print(invocationElement.getMethodName())
                    .print("(").printArgList(invocationElement.getArguments()).print(")");
            return true;
        }

        // enum static methods
        if (targetTypeElement != null && targetTypeElement.getKind() == ElementKind.ENUM) {

            switch (targetMethodName) {
            case "values":
                printEnumValuesJSCode((TypeElement) targetTypeElement);
                return true;
            }
        }

        if (targetClassName != null && targetMethodName != null) {
            switch (targetClassName) {
            case UTIL_CLASSNAME:
            case DEPRECATED_UTIL_CLASSNAME:
                switch (targetMethodName) {
                case "$export":
                    if (!invocationElement.getArgument(0).isStringLiteral()) {
                        report(invocationElement.getArgument(0), JSweetProblem.STRING_LITERAL_EXPECTED);
                    }
                    String varName = "_exportedVar_"
                            + StringUtils.strip(invocationElement.getArgument(0).toString(), "\"");
                    getPrinter().footer.append(VAR_DECL_KEYWORD + " " + varName + ";\n");
                    if (invocationElement.getArgumentCount() == 1) {
                        print(varName);
                    } else {
                        print("{ " + varName + " = ").print(invocationElement.getArgument(1)).print("; ");
                        print("console.log('" + JSweetTranspiler.EXPORTED_VAR_BEGIN
                                + StringUtils.strip(invocationElement.getArgument(0).toString(), "\"") + "='+")
                                        .print(varName).print("+'" + JSweetTranspiler.EXPORTED_VAR_END + "') }");
                    }
                    return true;

                case "array":
                case "function":
                case "string":
                case "bool":
                case "number":
                case "integer":
                case "object":
                    printCastMethodInvocation(invocationElement);
                    return true;

                case "any":
                    print("(");
                    printCastMethodInvocation(invocationElement);
                    print(")");
                    return true;

                case "async":
                    print("async ");
                    print(invocationElement.getArgument(0));
                    return true;

                case "await":
                    print("await ");
                    printCastMethodInvocation(invocationElement);
                    return true;

                case "asyncReturn":
                    printCastMethodInvocation(invocationElement);
                    return true;

                case "union":
                    getPrinter().typeChecker.checkUnionTypeAssignment(getPrinter().getParent(),
                            getCompilationUnitTree(), methodInvocationTree);
                    print("(");
                    printCastMethodInvocation(invocationElement);
                    print(")");
                    return true;

                case "typeof":
                    print("typeof ").print(invocationElement.getArgument(0));
                    return true;

                case "$noarrow":
                    print(invocationElement.getArgument(0));
                    return true;

                case "equalsStrict":
                    print("(").print(invocationElement.getArgument(0)).print(" === ")
                            .print(invocationElement.getArgument(1)).print(")");
                    return true;

                case "notEqualsStrict":
                    print("(").print(invocationElement.getArgument(0)).print(" !== ")
                            .print(invocationElement.getArgument(1)).print(")");
                    return true;

                case "equalsLoose":
                    print("(").print(invocationElement.getArgument(0)).print(" == ")
                            .print(invocationElement.getArgument(1)).print(")");
                    return true;

                case "notEqualsLoose":
                    print("(").print(invocationElement.getArgument(0)).print(" != ")
                            .print(invocationElement.getArgument(1)).print(")");
                    return true;

                case "$strict":
                    getPrinter().enterComparisonMode(ComparisonMode.STRICT);
                    print(invocationElement.getArgument(0));
                    getPrinter().exitComparisonMode();
                    return true;

                case "$loose":
                    getPrinter().enterComparisonMode(ComparisonMode.LOOSE);
                    print(invocationElement.getArgument(0));
                    getPrinter().exitComparisonMode();
                    return true;

                case "$insert":
                    if (invocationElement.getArgument(0) instanceof LiteralElement) {
                        print(((LiteralElement) invocationElement.getArgument(0)).getValue().toString());
                        return true;
                    } else {
                        report(invocationElement, JSweetProblem.MISUSED_INSERT_MACRO,
                                invocationElement.getMethodName());
                    }

                case "$template":
                    if (invocationElement.getArgumentCount() == 1) {
                        if (invocationElement.getArgument(0) instanceof LiteralElement) {
                            print("`" + ((LiteralElement) invocationElement.getArgument(0)).getValue().toString()
                                    + "`");
                            return true;
                        } else {
                            if (invocationElement.getArgument(1) instanceof LiteralElement) {
                                print(invocationElement.getArgument(0)).print(
                                        "`" + ((LiteralElement) invocationElement.getArgument(1)).getValue().toString()
                                                + "`");
                                return true;
                            }
                        }
                    }
                    report(invocationElement, JSweetProblem.MISUSED_INSERT_MACRO, invocationElement.getMethodName());

                case "$map":
                    if (invocationElement.getArgumentCount() % 2 != 0) {
                        report(invocationElement, JSweetProblem.UNTYPED_OBJECT_ODD_PARAMETER_COUNT);
                    }
                    print("{");
                    if (methodInvocationTree.getArguments() != null) {
                        for (int i = 0; i < methodInvocationTree.getArguments().size() / 2; i++) {
                            ExpressionTree keyArgTree = methodInvocationTree.getArguments().get(2 * i);
                            String key = keyArgTree.toString();
                            if (util().isLiteralExpression(keyArgTree) && key.startsWith("\"")) {
                                key = key.substring(1, key.length() - 1);
                                // TODO [Java11]
//							if (JJavaName.isJavaIdentifier(key)) {
//								print(key);
//							} else {
                                print("\"" + key + "\"");
//							}
                            } else {
                                report(invocationElement.getArgument(0), JSweetProblem.UNTYPED_OBJECT_WRONG_KEY, key);
                            }
                            print(": ");

                            ExpressionTree valueArgTree = methodInvocationTree.getArguments().get(2 * i + 1);
                            getPrinter().print(valueArgTree);
//						if (args != null && args.head != null) {
                            print(",");
//						}
                        }
                    }
                    print("}");
                    return true;

                case "$array":
                    print("[").printArgList(invocationElement.getArguments()).print("]");
                    return true;

                case "$apply":
                    print("(").print(invocationElement.getArgument(0)).print(")(")
                            .printArgList(invocationElement.getArgumentTail()).print(")");
                    return true;
                case "$new":
                    print("new (").print(invocationElement.getArgument(0)).print(")(")
                            .printArgList(invocationElement.getArgumentTail()).print(")");
                    return true;
                }
            }
        }

        if (targetMethodName != null) {
            switch (targetMethodName) {
            case INVOKE_FUCTION_NAME:
                if (invocationElement.getTargetExpression() != null && !(UTIL_CLASSNAME.equals(targetClassName)
                        || DEPRECATED_UTIL_CLASSNAME.equals(targetClassName))) {

                } else {
                    if (invocationElement.getArgumentCount() == 1) {
                        print("this[").print(invocationElement.getArguments().get(0)).print("]");
                    } else {
                        print(invocationElement.getArguments().get(0)).print("[")
                                .print(invocationElement.getArguments().get(1)).print("]");
                    }
                }
                print(invocationElement.getTargetExpression());
                print("[");
                print(invocationElement.getArgument(0));
                print("]");
                print("(");
                List arguments = invocationElement.getArguments();
                int argCount = arguments.size();
                for (int i = 1; i < argCount; i++) {
                    print(arguments.get(i));
                    if (i < argCount - 1) {
                        print(",");
                    }
                }
                print(")");
                return true;
            case INDEXED_GET_FUCTION_NAME:
                if (isWithinGlobals(targetClassName)) {
                    if (invocationElement.getArgumentCount() == 1) {
                        report(invocationElement, JSweetProblem.GLOBAL_INDEXER_GET);
                        return true;
                    } else {
                        if (invocationElement.getArgument(0).toString().equals(GLOBALS_CLASS_NAME + ".class")
                                || invocationElement.getArgument(0).toString()
                                        .endsWith("." + GLOBALS_CLASS_NAME + ".class")) {
                            report(invocationElement, JSweetProblem.GLOBAL_INDEXER_GET);
                            return true;
                        }
                    }
                }

                if (invocationElement.getTargetExpression() != null && !(UTIL_CLASSNAME.equals(targetClassName)
                        || DEPRECATED_UTIL_CLASSNAME.equals(targetClassName))) {
                    print(invocationElement.getTargetExpression()).print("[").print(invocationElement.getArgument(0))
                            .print("]");
                } else {
                    if (invocationElement.getArgumentCount() == 1) {
                        print("this[").print(invocationElement.getArguments().get(0)).print("]");
                    } else {
                        print(invocationElement.getArguments().get(0)).print("[")
                                .print(invocationElement.getArguments().get(1)).print("]");
                    }
                }
                return true;
            case INDEXED_GET_STATIC_FUCTION_NAME:
                if (invocationElement.getArgumentCount() == 1 && isWithinGlobals(targetClassName)) {
                    report(invocationElement, JSweetProblem.GLOBAL_INDEXER_GET);
                    return true;
                }

                print(invocationElement.getTargetExpression()).print("[").print(invocationElement.getArgument(0))
                        .print("]");
                return true;

            case INDEXED_SET_FUCTION_NAME:
                if (isWithinGlobals(targetClassName)) {
                    if (invocationElement.getArgumentCount() == 2) {
                        report(invocationElement, JSweetProblem.GLOBAL_INDEXER_SET);
                        return true;
                    } else {
                        if (invocationElement.getArgument(0).toString().equals(GLOBALS_CLASS_NAME + ".class")
                                || invocationElement.getArgument(0).toString()
                                        .endsWith(GLOBALS_CLASS_NAME + ".class")) {
                            report(invocationElement, JSweetProblem.GLOBAL_INDEXER_SET);
                            return true;
                        }
                    }
                }

                if (invocationElement.getTargetExpression() != null && !(UTIL_CLASSNAME.equals(targetClassName)
                        || DEPRECATED_UTIL_CLASSNAME.equals(targetClassName))) {
                    // check the type through the getter
                    for (Element e : invocationElement.getTargetExpression().getTypeAsElement().getEnclosedElements()) {
                        if (e instanceof ExecutableElement
                                && INDEXED_GET_FUCTION_NAME.equals(e.getSimpleName().toString())) {
                            ExecutableElement getter = (ExecutableElement) e;
                            TypeMirror getterType = getter.getReturnType();
                            TypeMirror getterIndexType = getter.getParameters().get(0).asType();

                            TypeMirror invokedIndexType = invocationElement.getArgument(0).getType();
                            TypeMirror invokedValueType = invocationElement.getArgument(1).getType();

                            boolean sameIndexType = types().isSameType(getterIndexType, invokedIndexType);

                            if (sameIndexType && !types().isAssignable(invokedValueType, types().erasure(getterType))) {
                                report(invocationElement.getArgument(1), JSweetProblem.INDEXED_SET_TYPE_MISMATCH,
                                        getterType);
                            }
                        }
                    }

                    print(invocationElement.getTargetExpression()).print("[").print(invocationElement.getArgument(0))
                            .print("] = ").print(invocationElement.getArgument(1));
                } else {
                    if (invocationElement.getArgumentCount() == 2) {
                        print("this[").print(invocationElement.getArgument(0)).print("] = ")
                                .print(invocationElement.getArgument(1));
                    } else {
                        print(invocationElement.getArgument(0)).print("[").print(invocationElement.getArgument(1))
                                .print("] = ").print(invocationElement.getArgument(2));
                    }
                }
                return true;

            case INDEXED_SET_STATIC_FUCTION_NAME:

                if (invocationElement.getArgumentCount() == 2 && isWithinGlobals(targetClassName)) {
                    report(invocationElement, JSweetProblem.GLOBAL_INDEXER_SET);
                    return true;
                }

                print(invocationElement.getTargetExpression()).print("[").print(invocationElement.getArguments().get(0))
                        .print("] = ").print(invocationElement.getArguments().get(1));
                return true;

            case INDEXED_DELETE_FUCTION_NAME:
                if (isWithinGlobals(targetClassName)) {
                    if (invocationElement.getArgumentCount() == 1) {
                        report(invocationElement, JSweetProblem.GLOBAL_DELETE);
                        return true;
                    } else {
                        if (invocationElement.getArgument(0).toString().equals(GLOBALS_CLASS_NAME + ".class")
                                || invocationElement.getArguments().get(0).toString()
                                        .endsWith(GLOBALS_CLASS_NAME + ".class")) {
                            report(invocationElement, JSweetProblem.GLOBAL_DELETE);
                            return true;
                        }
                    }
                }

                if (invocationElement.getTargetExpression() != null && !(UTIL_CLASSNAME.equals(targetClassName)
                        || DEPRECATED_UTIL_CLASSNAME.equals(targetClassName))) {
                    print("delete ").print(invocationElement.getTargetExpression()).print("[")
                            .print(invocationElement.getArguments().get(0)).print("]");
                } else {
                    if (invocationElement.getArgumentCount() == 1) {
                        print("delete this[").print(invocationElement.getArgument(0)).print("]");
                    } else {
                        print("delete ").print(invocationElement.getArgument(0)).print("[")
                                .print(invocationElement.getArgument(1)).print("]");
                    }
                }
                return true;

            case INDEXED_DELETE_STATIC_FUCTION_NAME:
                if (invocationElement.getArgumentCount() == 1 && isWithinGlobals(targetClassName)) {
                    report(invocationElement, JSweetProblem.GLOBAL_DELETE);
                    return true;
                }

                if (invocationElement.getTargetExpression() != null && !(UTIL_CLASSNAME.equals(targetClassName)
                        || DEPRECATED_UTIL_CLASSNAME.equals(targetClassName))) {
                    print("delete ").print(invocationElement.getTargetExpression()).print("[")
                            .print(invocationElement.getArgument(0)).print("]");
                } else {
                    if (invocationElement.getArgumentCount() == 1) {
                        print("delete ").print("this[").print(invocationElement.getArgument(0)).print("]");
                    } else {
                        print("delete ").print(invocationElement.getArgument(0)).print("[")
                                .print(invocationElement.getArgument(1)).print("]");
                    }
                }
                return true;
            }

        }

        if (invocationElement.getTargetExpression() == null && "$super".equals(targetMethodName)) {
            print("super(").printArgList(invocationElement.getArguments()).print(")");
            return true;
        }
        if (invocationElement.getTargetExpression() != null && targetClassName != null
                && (targetClassName.startsWith(UTIL_PACKAGE + ".function.")
                        || targetClassName.equals(Callable.class.getName())
                        || targetClassName.startsWith(Function.class.getPackage().getName()))) {
            if (!TypeChecker.jdkAllowed && targetClassName.startsWith(Function.class.getPackage().getName())
                    && TypeChecker.FORBIDDEN_JDK_FUNCTIONAL_METHODS.contains(targetMethodName)) {
                report(invocationElement, JSweetProblem.JDK_METHOD, targetMethodName);
            }

            if (targetClassName.equals(Function.class.getName()) && targetMethodName.equals("identity")) {
                print("(x=>x)");
                return true;
            }

            printFunctionalInvocation(invocationElement.getTargetExpression(), targetMethodName,
                    invocationElement.getArguments());
            return true;
        }
        if (invocationElement.getTargetExpression() != null && targetClassName != null
                && targetClassName.equals(java.lang.Runnable.class.getName())) {
            printFunctionalInvocation(invocationElement.getTargetExpression(), targetMethodName,
                    invocationElement.getArguments());
            return true;
        }

        // built-in Java support

        if (targetClassName != null) {

            // expand macros
            switch (targetMethodName) {
            case "getMessage":
                if (targetTypeElement instanceof TypeElement) {
                    if (types().isAssignable(targetTypeElement.asType(), util().getType(Throwable.class))) {
                        printTarget(invocationElement.getTargetExpression()).print(".message");
                        return true;
                    }
                }
                break;
            case "getCause":
                if (targetTypeElement instanceof TypeElement) {
                    if (types().isAssignable(targetTypeElement.asType(), util().getType(Throwable.class))) {
                        print("(null)");
                        return true;
                    }
                }
                break;
            case "printStackTrace":
                if (targetTypeElement instanceof TypeElement) {
                    if (types().isAssignable(targetTypeElement.asType(), util().getType(Throwable.class))) {
                        print("console.error(").print(invocationElement.getTargetExpression()).print(".message, ")
                                .print(invocationElement.getTargetExpression()).print(")");
                        return true;
                    }
                }
                break;
            }

            switch (targetClassName) {
            case "java.lang.String":
            case "java.lang.CharSequence":
                switch (targetMethodName) {
                case "valueOf":
                    printMacroName(targetMethodName);
                    if (invocationElement.getArgumentCount() == 3) {
                        print("((str, index, len) => str.join('').substring(index, index + len))(")
                                .printArgList(invocationElement.getArguments()).print(")");
                    } else {
                        print("String(").printArgList(invocationElement.getArguments()).print(").toString()");
                    }
                    return true;
                case "subSequence":
                    printMacroName(targetMethodName);
                    print(invocationElement.getTargetExpression()).print(".substring(")
                            .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                // this macro should use 'includes' in ES6
                case "contains":
                    printMacroName(targetMethodName);
                    print("(").print(invocationElement.getTargetExpression()).print(".indexOf(")
                            .printArgList(invocationElement.getArguments()).print(") != -1)");
                    return true;
                case "length":
                    print(invocationElement.getTargetExpression()).print(".length");
                    return true;
                // this macro is not needed in ES6
                case "startsWith":
                    printMacroName(targetMethodName);
                    print("((str, searchString, position = 0) => str.substr(position, searchString.length) === searchString)(")
                            .print(invocationElement.getTargetExpression()).print(", ")
                            .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "endsWith":
                    printMacroName(targetMethodName);
                    print("((str, searchString) => { " + VAR_DECL_KEYWORD + " pos = str.length - searchString.length; "
                            + VAR_DECL_KEYWORD
                            + " lastIndex = str.indexOf(searchString, pos); return lastIndex !== -1 && lastIndex === pos; })(")
                                    .print(invocationElement.getTargetExpression()).print(", ")
                                    .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                // this macro is not needed in ES6
                case "codePointAt":
                    printMacroName(targetMethodName);
                    print(invocationElement.getTargetExpression()).print(".charCodeAt(")
                            .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "isEmpty":
                    printMacroName(targetMethodName);
                    print("(").print(invocationElement.getTargetExpression()).print(".length === 0)");
                    return true;
                case "compareToIgnoreCase":
                    printMacroName(targetMethodName);
                    print(invocationElement.getTargetExpression()).print(".toUpperCase().localeCompare(")
                            .printArgList(invocationElement.getArguments()).print(".toUpperCase())");
                    return true;
                case "compareTo":
                    printMacroName(targetMethodName);
                    print(invocationElement.getTargetExpression()).print(".localeCompare(")
                            .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "equalsIgnoreCase":
                    printMacroName(targetMethodName);
                    print("((o1, o2) => o1.toUpperCase() === (o2===null ? o2 : o2.toUpperCase()))(")
                            .print(invocationElement.getTargetExpression()).print(", ")
                            .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "toChars":
                    printMacroName(targetMethodName);
                    print("String.fromCharCode(").printArgList(invocationElement.getArguments()).print(")");
                    return true;
                // In ES6, we can use the Array.from method
                case "getBytes":
                    printMacroName(targetMethodName);
                    print("(").print(invocationElement.getTargetExpression())
                            .print(").split('').map(s => s.charCodeAt(0))");
                    return true;
                // In ES6, we can use the Array.from method
                case "toCharArray":
                    printMacroName(targetMethodName);
                    print("(").print(invocationElement.getTargetExpression()).print(").split('')");
                    return true;
                case "getChars":
                    printMacroName(targetMethodName);
                    print("((a, s, e, d, l) => { d.splice.apply(d, [l, e-s].concat(a.substring(s, e).split(''))); })(")
                            .print(invocationElement.getTargetExpression()).print(", ")
                            .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "replaceAll":
                    printMacroName(targetMethodName);
                    print(invocationElement.getTargetExpression()).print(".replace(new RegExp(")
                            .print(invocationElement.getArguments().get(0)).print(", 'g'),")
                            .print(invocationElement.getArguments().get(1)).print(")");
                    return true;
                case "replace":
                    printMacroName(targetMethodName);
                    print(invocationElement.getTargetExpression()).print(".split(")
                            .print(invocationElement.getArguments().get(0)).print(").join(")
                            .print(invocationElement.getArguments().get(1)).print(")");
                    return true;
                case "lastIndexOf":
                    print(invocationElement.getTargetExpression()).print(".lastIndexOf(")
                            .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "indexOf":
                    if (invocationElement.getArgumentCount() == 1
                            && util().isNumber(invocationElement.getArgument(0).getType())) {
                        print(invocationElement.getTargetExpression()).print(".indexOf(String.fromCharCode(")
                                .print(invocationElement.getArgument(0)).print("))");
                    } else {
                        print(invocationElement.getTargetExpression()).print(".indexOf(")
                                .printArgList(invocationElement.getArguments()).print(")");
                    }
                    return true;
                case "toLowerCase":
                    if (invocationElement.getArgumentCount() > 0) {
                        printMacroName(targetMethodName);
                        print(invocationElement.getTargetExpression()).print(".toLowerCase()");
                        return true;
                    }
                    break;
                case "toUpperCase":
                    if (invocationElement.getArgumentCount() > 0) {
                        printMacroName(targetMethodName);
                        print(invocationElement.getTargetExpression()).print(".toUpperCase()");
                        return true;
                    }
                    break;
                }
                break;
            case "java.lang.Character":
                switch (targetMethodName) {
                case "toChars":
                    printMacroName(targetMethodName);
                    print("String.fromCharCode(").printArgList(invocationElement.getArguments()).print(")");
                    return true;
                }
                break;
            case "java.lang.Number":
            case "java.lang.Float":
            case "java.lang.Double":
            case "java.lang.Integer":
            case "java.lang.Byte":
            case "java.lang.Long":
            case "java.lang.Short":
                switch (targetMethodName) {
                case "isNaN":
                    printMacroName(targetMethodName);
                    if (invocationElement.getArgumentCount() > 0) {
                        print("isNaN(").printArgList(invocationElement.getArguments()).print(")");
                        return true;
                    } else {
                        print("isNaN(").print(invocationElement.getTargetExpression()).print(")");
                        return true;
                    }
                case "isInfinite":
                    printMacroName(targetMethodName);
                    if (invocationElement.getArgumentCount() > 0) {
                        print("((value) => Number.NEGATIVE_INFINITY === value || Number.POSITIVE_INFINITY === value)(")
                                .printArgList(invocationElement.getArguments()).print(")");
                        return true;
                    } else {
                        print("((value) => Number.NEGATIVE_INFINITY === value || Number.POSITIVE_INFINITY === value)(")
                                .print(invocationElement.getTargetExpression()).print(")");
                        return true;
                    }
                case "isFinite":
                    printMacroName(targetMethodName);
                    print("((value) => !isNaN(value) && Number.NEGATIVE_INFINITY !== value && Number.POSITIVE_INFINITY !== value)(")
                            .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "intValue":
                    printMacroName(targetMethodName);
                    print("(").print(invocationElement.getTargetExpression()).print("|0").print(")");
                    return true;
                case "shortValue":
                    printMacroName(targetMethodName);
                    print("(").print(invocationElement.getTargetExpression()).print("|0").print(")");
                    return true;
                case "byteValue":
                    printMacroName(targetMethodName);
                    print("(").print(invocationElement.getTargetExpression()).print("|0").print(")");
                    return true;
                case "floatValue":
                    printMacroName(targetMethodName);
                    print(invocationElement.getTargetExpression());
                    return true;
                case "doubleValue":
                    printMacroName(targetMethodName);
                    print(invocationElement.getTargetExpression());
                    return true;
                case "longValue":
                    printMacroName(targetMethodName);
                    print(invocationElement.getTargetExpression());
                    return true;
                case "compare":
                    if (invocationElement.getArgumentCount() == 2) {
                        printMacroName(targetMethodName);
                        print("(").print(invocationElement.getArgument(0)).print(" - ")
                                .print(invocationElement.getArgument(1)).print(")");
                        return true;
                    }
                    break;
                case "toString":
                    if (invocationElement.getArgumentCount() > 0) {
                        printMacroName(targetMethodName);
                        print("(''+(").print(invocationElement.getArgument(0)).print("))");
                        return true;
                    }
                }
                break;
            case "java.lang.Boolean":
                switch (targetMethodName) {
                case "booleanValue":
                    printMacroName(targetMethodName);
                    print(invocationElement.getTargetExpression());
                    return true;
                }
                break;
            case "java.lang.StrictMath":
            case "java.lang.Math":
                switch (targetMethodName) {
                case "cbrt":
                    printMacroName(targetMethodName);
                    print("Math.pow(").printArgList(invocationElement.getArguments()).print(", 1/3)");
                    return true;
                case "copySign":
                    printMacroName(targetMethodName);
                    print("((magnitude, sign) => { if (sign < 0) { return (magnitude < 0) ? magnitude : -magnitude; } else { return (magnitude > 0) ? magnitude : -magnitude; } })(")
                            .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "cosh":
                    printMacroName(targetMethodName);
                    print("(x => (Math.exp(x) + Math.exp(-x)) / 2)(").printArgList(invocationElement.getArguments())
                            .print(")");
                    return true;
                case "expm1":
                    printMacroName(targetMethodName);
                    print("(d => { if (d == 0.0 || d === Number.NaN) { return d; } else if (!Number.POSITIVE_INFINITY === d && !Number.NEGATIVE_INFINITY === d) { if (d < 0) { return -1; } else { return Number.POSITIVE_INFINITY; } } })(")
                            .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "hypot":
                    printMacroName(targetMethodName);
                    print("(x => Math.sqrt(x * x + y * y))(").printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "log10":
                    printMacroName(targetMethodName);
                    print("(x => Math.log(x) * Math.LOG10E)(").printArgList(invocationElement.getArguments())
                            .print(")");
                    return true;
                case "log1p":
                    printMacroName(targetMethodName);
                    print("(x => Math.log(x + 1))(").printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "rint":
                    printMacroName(targetMethodName);
                    print("(d => { if (d === Number.NaN) { return d; } else if (Number.POSITIVE_INFINITY === d || Number.NEGATIVE_INFINITY === d) { return d; } else if (d == 0) { return d; } else { return Math.round(d); } })(")
                            .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "scalb":
                    printMacroName(targetMethodName);
                    print("((d, scaleFactor) => { if (scaleFactor >= 31 || scaleFactor <= -31) { return d * Math.pow(2, scaleFactor); } else if (scaleFactor > 0) { return d * (1 << scaleFactor); } else if (scaleFactor == 0) { return d; } else { return d * 1 / (1 << -scaleFactor); } })(")
                            .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "signum":
                    printMacroName(targetMethodName);
                    print("(f => { if (f > 0) { return 1; } else if (f < 0) { return -1; } else { return 0; } })(")
                            .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "sinh":
                    printMacroName(targetMethodName);
                    print("(x => (Math.exp(x) - Math.exp(-x)) / 2)(").printArgList(invocationElement.getArguments())
                            .print(")");
                    return true;
                case "tanh":
                    printMacroName(targetMethodName);
                    print("(x => { if (x == Number.POSITIVE_INFINITY) { return 1; } else if (x == Number.NEGATIVE_INFINITY) { return -1; } double e2x = Math.exp(2 * x); return (e2x - 1) / (e2x + 1); })(")
                            .printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "toDegrees":
                    printMacroName(targetMethodName);
                    print("(x => x * 180 / Math.PI)(").printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "toRadians":
                    printMacroName(targetMethodName);
                    print("(x => x * Math.PI / 180)(").printArgList(invocationElement.getArguments()).print(")");
                    return true;
                case "nextUp":
                    delegateToEmulLayer(targetClassName, targetMethodName, invocationElement);
                    return true;
                case "nextDown":
                    delegateToEmulLayer(targetClassName, targetMethodName, invocationElement);
                    return true;
                case "ulp":
                    delegateToEmulLayer(targetClassName, targetMethodName, invocationElement);
                    return true;
                case "IEEEremainder":
                    delegateToEmulLayer(targetClassName, targetMethodName, invocationElement);
                    return true;
                default:
                    print("Math." + targetMethodName + "(").printArgList(invocationElement.getArguments()).print(")");
                    return true;
                }

            case "java.lang.Class":
                switch (targetMethodName) {
                case "getName":
                    printMacroName(targetMethodName);
                    getPrinter().print("(c => typeof c === 'string' ? c : c[\""
                            + Java2TypeScriptTranslator.CLASS_NAME_IN_CONSTRUCTOR + "\"] ? c[\""
                            + Java2TypeScriptTranslator.CLASS_NAME_IN_CONSTRUCTOR + "\"] : c[\"name\"])(");
                    printTarget(invocationElement.getTargetExpression());
                    print(")");
                    return true;
                case "getSimpleName":
                    printMacroName(targetMethodName);
                    print("(c => typeof c === 'string' ? (c).substring((c).lastIndexOf('.')+1) : c[\""
                            + Java2TypeScriptTranslator.CLASS_NAME_IN_CONSTRUCTOR + "\"] ? c[\""
                            + Java2TypeScriptTranslator.CLASS_NAME_IN_CONSTRUCTOR + "\"].substring(c[\""
                            + Java2TypeScriptTranslator.CLASS_NAME_IN_CONSTRUCTOR
                            + "\"].lastIndexOf('.')+1) : c[\"name\"].substring(c[\"name\"].lastIndexOf('.')+1))(");
                    printTarget(invocationElement.getTargetExpression());
                    print(")");
                    return true;
                }
                break;

            }

            if (invocationElement.getTargetExpression() != null && isMappedType(targetClassName)
                    && targetClassName.startsWith("java.lang.")) {
                if (invocationElement.getMethod().getModifiers().contains(Modifier.STATIC)) {
                    // delegation to javaemul
                    delegateToEmulLayer(targetClassName, targetMethodName, invocationElement);
                    return true;
                } else {
                    switch (targetMethodName) {
                    case "equals":
                        TypeMirror t1 = util().unboxedTypeOrType(invocationElement.getTargetExpression().getType());
                        TypeMirror t2 = util().unboxedTypeOrType(invocationElement.getArgument(0).getType());
                        if (types().isSameType(t1, t2) && util().isCoreType(t1)) {
                            if (isInlinedExpression(invocationElement)) {
                                print("(");
                            }
                            print(invocationElement.getTargetExpression()).print(" === ");
                            ExtendedElement arg = invocationElement.getArgument(0);
                            boolean inlinable = arg instanceof VariableAccessElement
                                    || (arg instanceof MethodInvocationElement
                                            && !"equals".equals(((MethodInvocationElement) arg).getMethodName()));
                            if (!inlinable) {
                                print("(");
                            }
                            print(invocationElement.getArgument(0));
                            if (!inlinable) {
                                print(")");
                            }
                            if (isInlinedExpression(invocationElement)) {
                                print(")");
                            }
                        } else {
                            printMacroName(targetMethodName);
                            printDefaultEquals(invocationElement.getTargetExpression(),
                                    invocationElement.getArgument(0));
                        }
                        return true;
                    case "compareTo":
                        if (invocationElement.getArgumentCount() == 1
                                && invocationElement.getTargetExpression() != null) {
                            printMacroName(targetMethodName);
                            print("(((o1: any, o2: any) => { if (o1 && o1.compareTo) { return o1.compareTo(o2); } else { return o1 < o2 ? -1 : o2 < o1 ? 1 : 0; } })(");
                            printTarget(invocationElement.getTargetExpression()).print(",")
                                    .print(invocationElement.getArgument(0));
                            print("))");
                        }
                        return true;
                    }
                }
            }

        }

        switch (targetMethodName) {
        case "getClass":
            print("(");
            printTarget(invocationElement.getTargetExpression());
            print(".constructor)");
            return true;
        case "hashCode":
            if (invocationElement.getArgumentCount() == 0) {
                printMacroName(targetMethodName);
                print("(((o: any) => { if (o.hashCode) { return o.hashCode(); } else { "
                        + "return o.toString().split('').reduce((prevHash, currVal) => (((prevHash << 5) - prevHash) + currVal.charCodeAt(0))|0, 0); }})(");
                printTarget(invocationElement.getTargetExpression());
                print("))");
                return true;
            }
            break;
        case "equals":
            if (invocationElement.getTargetExpression() != null && invocationElement.getArgumentCount() == 1) {
                Element invocationTargetTypeElement = invocationElement.getTargetExpression().getTypeAsElement();
                if (invocationTargetTypeElement instanceof TypeElement) {
                    ExecutableElement methSym = util().findMethodDeclarationInType( //
                            (TypeElement) invocationTargetTypeElement, //
                            targetMethodName, //
                            (ExecutableType) invocationElement.getMethod().asType());
                    if (methSym != null
                            && (Object.class.getName().equals(methSym.getEnclosingElement().toString())
                                    || util().isInterface(methSym.getEnclosingElement()))
                            || util().isInterface(types().asElement(invocationElement.getTargetType()))
                            || invocationElement.getTargetExpression().getType().getKind() == TypeKind.TYPEVAR) {
                        printMacroName(targetMethodName);
                        print("(((o1: any, o2: any) => { if (o1 && o1.equals) { return o1.equals(o2); } else { return o1 === o2; } })(");
                        printTarget(invocationElement.getTargetExpression()).print(",")
                                .print(invocationElement.getArgument(0));
                        print("))");
                        return true;
                    }
                }
            }
            break;
        case "clone":
            if (!invocationElement.getMethod().getModifiers().contains(Modifier.STATIC)
                    && invocationElement.getArgumentCount() == 0) {
                printMacroName(targetMethodName);
                if (invocationElement.getTargetExpression() != null
                        && "super".equals(invocationElement.getTargetExpression().toString())) {
                    ClassTree parent = getPrinter().getParent(ClassTree.class);
                    TypeElement parentElement = Util.getElement(parent);
                    if (parentElement.getSuperclass() != null
                            && !util().isType(parentElement.getSuperclass(), Object.class)) {
                        print("((o: any) => { if (super.clone != undefined) { return super.clone(); } else { let clone = Object.create(o); for(let p in o) { if (o.hasOwnProperty(p)) clone[p] = o[p]; } return clone; } })(this)");
                    } else {
                        print("((o: any) => { let clone = Object.create(o); for(let p in o) { if (o.hasOwnProperty(p)) clone[p] = o[p]; } return clone; })(this)");
                    }
                } else {
                    print("((o: any) => { if (o.clone != undefined) { return (o).clone(); } else { let clone = Object.create(o); for(let p in o) { if (o.hasOwnProperty(p)) clone[p] = o[p]; } return clone; } })(");
                    printTarget(invocationElement.getTargetExpression());
                    print(")");
                }
                return true;
            }
        }

        getPrinter().printDefaultMethodInvocation(methodInvocationTree);

        return true;

    }

    /**
     * Returns a quote string (single or double quote depending on the
     * useSingleQuotesForStringLiterals option).
     */
    public String getStringLiteralQuote() {
        return getPrinter().getStringLiteralQuote();
    }

    private final static List ENUM_SPECIAL_MEMBERS = asList(CLASS_NAME_IN_CONSTRUCTOR, INTERFACES_FIELD_NAME,
            ENUM_WRAPPER_CLASS_WRAPPERS);

    private void printEnumValuesJSCode(TypeElement enumElement) {
        boolean isStringEnum = context.hasAnnotationType(enumElement, ANNOTATION_STRING_ENUM);
        String enumFullName = getRootRelativeName(enumElement);
        String addItemJS;
        if (isStringEnum) {
            String specialMembersList = ENUM_SPECIAL_MEMBERS.stream()
                    .map(member -> getStringLiteralQuote() + member + getStringLiteralQuote()).collect(joining(", "));

            addItemJS = "if ([" + specialMembersList + "].indexOf(val) === -1) result.push(val as " + enumFullName
                    + ");";
        } else {
            addItemJS = "if (!isNaN(val)) { result.push(parseInt(val,10)); }";
        }
        printMacroName("Enum.values");
        print("function() { " + VAR_DECL_KEYWORD + " result: " + enumFullName + "[] = []; for(" + VAR_DECL_KEYWORD
                + " val in ").print(enumFullName).print(") { " + addItemJS + " } return result; }()");
    }

    protected void printDefaultEquals(ExtendedElement left, ExtendedElement right) {
        print("(((o1: any, o2: any) => o1 && o1.equals ? o1.equals(o2) : o1 === o2)(");
        printTarget(left).print(",").print(right);
        print("))");
    }

    protected void printFunctionalInvocation(ExtendedElement target, String functionName,
            List arguments) {
        if (target instanceof IdentifierElement) {
            print("(typeof ").print(target).print(" === 'function' ? target").print("(").printArgList(arguments)
                    .print(") : (target).").print(functionName).print("(").printArgList(arguments).print("))");
        } else {
            print("(target => (typeof target === 'function') ? target").print("(").printArgList(arguments)
                    .print(") : (target).").print(functionName).print("(").printArgList(arguments).print("))(")
                    .print(target).print(")");
        }
    }

    protected void printFunctionalInvocation2(ExtendedElement target, String functionName,
            List arguments) {
        print("((target => (target['" + functionName + "'] === undefined) ? target : target['" + functionName + "'])(")
                .print(target).print("))").print("(").printArgList(arguments).print(")");
    }

    protected final PrinterAdapter printTarget(ExtendedElement target) {
        if (target == null) {
            return print("this");
        } else if ("super".equals(target.toString())) {
            return print("this");
        } else {
            return print(target);
        }
    }

    protected final void delegateToEmulLayer(String targetClassName, String targetMethodName,
            InvocationElement invocation) {
        String helperClassName = targetClassName.substring(10) + "Helper";
        if (context.useModules) {
            String pathToImportedClass = util().getRelativePath(
                    "@/" + getCompilationUnit().getPackage().toString().replace('.', '/'),
                    ("@/javaemul.internal." + helperClassName).replace('.', '/'));
            if (!pathToImportedClass.startsWith(".")) {
                pathToImportedClass = "./" + pathToImportedClass;
            }
            getPrinter().useModule(new ModuleImportDescriptor(helperClassName, pathToImportedClass));
            print(helperClassName).print(".").print(targetMethodName).print("(")
                    .printArgList(invocation.getArguments()).print(")");
        } else {
            print("javaemul.internal." + helperClassName).print(".").print(targetMethodName).print("(")
                    .printArgList(invocation.getArguments()).print(")");
        }
    }

    protected final void delegateToEmulLayerStatic(String targetClassName, String targetMethodName,
            ExtendedElement target) {
        String helperClassName = targetClassName.substring(10) + "Helper";
        if (context.useModules) {
            String pathToImportedClass = util().getRelativePath(
                    "@/" + getCompilationUnit().getPackage().toString().replace('.', '/'),
                    ("@/javaemul.internal." + helperClassName).replace('.', '/'));
            if (!pathToImportedClass.startsWith(".")) {
                pathToImportedClass = "./" + pathToImportedClass;
            }
            getPrinter().useModule(new ModuleImportDescriptor(helperClassName, pathToImportedClass));
            print(helperClassName).print(".").print(targetMethodName).print("(");
            printTarget(target).print(")");
        } else {
            print("javaemul.internal." + helperClassName).print(".").print(targetMethodName).print("(");
            printTarget(target).print(")");
        }
    }

    protected final void printCastMethodInvocation(InvocationElement invocation) {
        boolean needsParens = getPrinter().getParent() instanceof MethodInvocationTree;
        if (needsParens) {
            // async needs no parens to work
            MethodInvocationTree parentInvocation = (MethodInvocationTree) getPrinter().getParent();
            if (parentInvocation.getMethodSelect() instanceof IdentifierTree) {
                needsParens = !((IdentifierTree) parentInvocation.getMethodSelect()).getName().toString()
                        .equals("async");
            }
        }
        if (needsParens) {
            print("(");
        }
        print(invocation.getArgument(0));
        if (needsParens) {
            print(")");
        }
    }

    protected void printTypeArguments(TypeMirror typeMirror) {
        List typeArguments = util().getTypeArguments(typeMirror);
        if (typeArguments != null && typeArguments.size() > 0) {
            print("<");
            for (TypeMirror typeArg : typeArguments) {
                if (typeArg.getKind() == TypeKind.TYPEVAR || typeArg.getKind() == TypeKind.WILDCARD) {
                    print("any");
                } else {
                    if (!substituteAndPrintType(typeArg)) {
                        String mappedType = getMappedType(typeArg);
                        if (mappedType != null) {
                            print(mappedType);
                        } else {
                            print(typeArg.toString());
                        }
                    }
                }
                print(",");
            }
            removeLastChar(',');
            print(">");
        }
    }

    public boolean substituteAndPrintType(TypeMirror type) {
        String qualifiedName = util().getQualifiedName(type);
        String mappedType = context.getTypeMappingTarget(qualifiedName);
        if (mappedType != null) {
            if (mappedType.endsWith("<>")) {
                print(mappedType.substring(0, mappedType.length() - 2));
            } else {
                print(mappedType);
                if (!mappedType.endsWith(">") && !mappedType.equals("any")) {
                    printTypeArguments(type);
                }
            }
            return true;
        }

        for (Function mapping : context.getFunctionalTypeMirrorMappings()) {
            mappedType = mapping.apply(type);
            if (mappedType != null) {
                print(mappedType);
                return true;
            }
        }

        return false;
    }

    public boolean substituteAndPrintType(ExtendedElement element, TypeElement type) {
        if (substituteAndPrintType(type.asType())) {
            return true;
        }

        String typeFullName = type.toString();
        for (BiFunction mapping : context.getFunctionalTypeMappings()) {
            Object mapped = mapping.apply(element, typeFullName);
            if (mapped instanceof String) {
                print((String) mapped);
                return true;
            } else if (mapped instanceof Tree) {
                getPrinter().substituteAndPrintType((Tree) mapped);
                return true;
            } else if (mapped instanceof TypeMirror) {
                print(getMappedType((TypeMirror) mapped));
                return true;
            }
        }

        return false;
    }

    @Override
    public boolean substituteVariableAccess(VariableAccessElement variableAccess) {
        Element typeElement = variableAccess.getTypeAsElement();
        if (typeElement != null && typeElement.getKind() == ElementKind.ENUM
                && "this".equals(variableAccess.getVariableName())
                && !(getParentElement() instanceof VariableAccessElement
                        || getParentElement() instanceof MethodInvocationElement)) {
            print("this.").print(Java2TypeScriptTranslator.ENUM_WRAPPER_CLASS_ORDINAL);
            return true;
        }

        if (variableAccess.getTargetExpression() != null) {
            MemberSelectTree fieldAccess = ExtendedElementFactory.toTree(variableAccess);
            String targetFieldName = variableAccess.getVariableName();
            Element targetType = variableAccess.getTargetElement();

            // automatic static field access target redirection
            if (!"class".equals(variableAccess.getVariableName())
                    && variableAccess.getVariable().getModifiers().contains(Modifier.STATIC)) {

                if (!context.getLangTypeMappings().containsKey(targetType.toString())) {
                    if (substituteAndPrintType(variableAccess, (TypeElement) targetType)) {
                        print(".");
                        print(variableAccess.getVariableName());
                        return true;
                    }
                }
            }

            // translate tuple accesses
            if (targetFieldName.startsWith("$") && targetFieldName.length() > 1
                    && Character.isDigit(targetFieldName.charAt(1))) {
                try {
                    int i = Integer.parseInt(targetFieldName.substring(1));
                    print(variableAccess.getTargetExpression());
                    print("[" + i + "]");
                    return true;
                } catch (NumberFormatException e) {
                    // swallow
                }
            }

            if (hasAnnotationType(variableAccess.getVariable(), ANNOTATION_STRING_TYPE)) {
                print("\"");
                print(getAnnotationValue(variableAccess.getVariable(), ANNOTATION_STRING_TYPE, String.class,
                        variableAccess.getVariableName()));
                print("\"");
                return true;
            }

            Element fieldAccessElement = Util.getElement(fieldAccess);
            if (fieldAccess.getExpression().toString().equals("this")) {
                if (fieldAccessElement != null && fieldAccessElement.getModifiers().contains(Modifier.STATIC)) {
                    report(variableAccess, JSweetProblem.CANNOT_ACCESS_STATIC_MEMBER_ON_THIS,
                            fieldAccess.getIdentifier().toString());
                }
            }

            // enum objects wrapping
            if (targetType != null && targetType.getKind() == ElementKind.ENUM
                    && !util().isPartOfAnEnum(fieldAccessElement)
                    && !"this".equals(fieldAccess.getExpression().toString()) && !"class".equals(targetFieldName)) {
                String relTarget = getRootRelativeName((Element) targetType);

                TypeElement targetTypeElement = (TypeElement) targetType;
                getPrinter().useModule(getModuleImportDescriptor(getCompilationUnit(),
                        context.getActualName(targetTypeElement), (TypeElement) targetTypeElement));
                getPrinter().print(relTarget)
                        .print("[\"" + Java2TypeScriptTranslator.ENUM_WRAPPER_CLASS_WRAPPERS + "\"][")
                        .print(fieldAccess.getExpression()).print("].").print(fieldAccess.getIdentifier().toString());
                return true;
            }

            // built-in Java support
            String accessedType = util().getQualifiedName(targetType);
            if (fieldAccessElement.getModifiers().contains(Modifier.STATIC) && isMappedType(accessedType)
                    && accessedType.startsWith("java.lang.")
                    && !"class".equals(fieldAccess.getIdentifier().toString())) {
                delegateToEmulLayer(accessedType, variableAccess);
                return true;
            }
        } else {
            if (JSweetConfig.UTIL_CLASSNAME.equals(variableAccess.getTargetElement().toString())) {
                if ("$this".equals(variableAccess.getVariableName())) {
                    print("this");
                    return true;
                }
            }
            IdentifierTree identifier = ExtendedElementFactory.toTree(variableAccess);
            if (context.hasAnnotationType(Util.getElement(identifier), ANNOTATION_STRING_TYPE)) {
                print("\"");
                getPrinter().print((String) context.getAnnotationValue(Util.getElement(identifier),
                        ANNOTATION_STRING_TYPE, String.class, identifier.toString()));
                print("\"");
                return true;
            }
        }
        return super.substituteVariableAccess(variableAccess);
    }

    protected final void delegateToEmulLayer(String targetClassName, VariableAccessElement fieldAccess) {
        String helperClassName = targetClassName.substring(10) + "Helper";
        if (context.useModules) {
            String pathToImportedClass = util().getRelativePath(
                    "@/" + getCompilationUnit().getPackage().toString().replace('.', '/'),
                    ("@/javaemul.internal." + helperClassName).replace('.', '/'));
            if (!pathToImportedClass.startsWith(".")) {
                pathToImportedClass = "./" + pathToImportedClass;
            }
            getPrinter().useModule(new ModuleImportDescriptor(helperClassName, pathToImportedClass));
            print(helperClassName).print(".").print(fieldAccess.getVariableName());
        } else {
            print("javaemul.internal." + helperClassName).print(".").print(fieldAccess.getVariableName());
        }
    }

    @Override
    public boolean substituteNewClass(NewClassElement newClassElement) {
        NewClassTree newClass = ExtendedElementFactory.toTree(newClassElement);
        String className = newClassElement.getTypeAsElement().toString();
        if (className.startsWith(JSweetConfig.TUPLE_CLASSES_PACKAGE + ".")) {
            getPrinter().print("[").printArgList(null, newClass.getArguments()).print("]");
            return true;
        }

        if (isMappedType(className)) {

            print("<").print(getTypeMappingTarget(className));
            if (newClass.getIdentifier() instanceof ParameterizedTypeTree) {
                List typeArgs = ((ParameterizedTypeTree) newClass.getIdentifier()).getTypeArguments();
                if (typeArgs.size() > 0) {
                    getPrinter().print("<").printTypeArgList(typeArgs).print(">");
                }
            }
            print(">");
        }
        // macros
        if (util().isStringType(Util.getType(newClass.getIdentifier()))) {
            if (newClass.getArguments().size() >= 3) {
                getPrinter().print("((str, index, len) => ").print("str.substring(index, index + len))((")
                        .print(newClass.getArguments().get(0)).print(")");
                if ("byte[]".equals(Util.getType(newClass.getArguments().get(0)).toString())) {
                    print(".map(s => String.fromCharCode(s))");
                }
                print(".join(''), ");
                print(newClass.getArguments().get(1)).print(", ");
                print(newClass.getArguments().get(2)).print(")");
                return true;
            }
        }

        getPrinter().printDefaultNewClass(newClass);
        return true;

    }

    @Override
    public boolean substituteIdentifier(IdentifierElement identifierElement) {
        IdentifierTree identifier = ExtendedElementFactory.toTree(identifierElement);
        TypeMirror identifierType = identifierElement.getType();
        if (identifierType != null) {
            if (context.getLangTypesSimpleNames().contains(identifier.toString())
                    && context.getLangTypeMappings().containsKey(identifierType.toString())) {
                print(context.getLangTypeMappings().get(identifierType.toString()));
                return true;
            }
            if (!context.useModules && identifierType.toString().startsWith("java.lang.")) {
                if (("java.lang." + identifier.toString()).equals(identifierType.toString())) {
                    // it is a java.lang class being referenced, so we expand
                    // its name
                    print(identifierType.toString());
                    return true;
                }
            }
        }
        return super.substituteIdentifier(identifierElement);
    }

    @Override
    public boolean substituteExtends(TypeElement type) {
        // J4TS hack to avoid name clash between date classes (should be solved automatically)
        if (context.useModules && type.getEnclosingElement() != null && "java.sql".equals(type.getEnclosingElement().toString()) 
                && Date.class.getName().equals(type.getSuperclass().toString())) {
            String pathToImportedClass = util().getRelativePath(
                    "@/" + getCompilationUnit().getPackage().toString().replace('.', '/'),
                    ("@/" + Date.class.getName()).replace('.', '/'));
            if (!pathToImportedClass.startsWith(".")) {
                pathToImportedClass = "./" + pathToImportedClass;
            }
            getPrinter().useModule(new ModuleImportDescriptor("Date as java_util_Date", pathToImportedClass));
            print(" extends java_util_Date");
            return true;
        }
        return super.substituteExtends(type);
    }
    
    @Override
    public Set getErasedTypes() {
        return context.getLangTypeMappings().keySet();
    }

    protected void printForEachLoop(EnhancedForLoopTree loop, String indexVarName) {
        getPrinter().print("for(" + VAR_DECL_KEYWORD + " " + indexVarName + "=");
        if (loop.getExpression() instanceof TypeCastTree) {
            print("(");
        }
        getPrinter().print(loop.getExpression());
        if (loop.getExpression() instanceof TypeCastTree) {
            print(")");
        }
        print(".iterator();" + indexVarName + ".hasNext();) {").println().startIndent().printIndent();

        getPrinter().print(VAR_DECL_KEYWORD + " " + loop.getVariable().getName().toString() + " = ")
                .print(indexVarName + ".next();").println();
        getPrinter().printIndent().print(loop.getStatement());
        endIndent().println().printIndent().print("}");
    }

    @Override
    public boolean substituteForEachLoop(ForeachLoopElement foreachLoop, boolean targetHasLength, String indexVarName) {
        if (!targetHasLength) {
            printForEachLoop(ExtendedElementFactory.toTree(foreachLoop), indexVarName);
            return true;
        }
        return super.substituteForEachLoop(foreachLoop, targetHasLength, indexVarName);
    }


    /**
     * Ensures that the current file imports the given module (will have no effects when not using modules).
     * 
     * @param moduleImport a module import descriptor
     */
    public void useModule(ModuleImportDescriptor moduleImport) {
        getPrinter().useModule(moduleImport);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy