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

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

The newest version!
/*
 * JSweet transpiler - http://www.jsweet.org
 * Copyright (C) 2019 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 java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeMirror;

import org.jsweet.transpiler.JSweetProblem;
import org.jsweet.transpiler.model.ExtendedElement;
import org.jsweet.transpiler.model.ForeachLoopElement;
import org.jsweet.transpiler.model.MethodInvocationElement;
import org.jsweet.transpiler.model.NewClassElement;
import org.jsweet.transpiler.model.support.ForeachLoopElementSupport;
import org.jsweet.transpiler.util.Util;

import com.sun.source.tree.EnhancedForLoopTree;

/**
 * An adapter that removes many uses of Java APIs and replace them with
 * JavaScript ES6 equivalent when possible.
 */

public class RemoveJavaDependenciesES6Adapter extends RemoveJavaDependenciesAdapter {
    protected final static Set SET_CLASS_NAMES = Stream
            .of(Set.class, HashSet.class, LinkedHashSet.class, TreeSet.class, AbstractSet.class).map(Class::getName)
            .collect(Collectors.toSet());

    protected final static Set MAP_CLASS_NAMES = Stream
            .of(Map.class, HashMap.class, LinkedHashMap.class, TreeMap.class, AbstractMap.class).map(Class::getName)
            .collect(Collectors.toSet());

    public RemoveJavaDependenciesES6Adapter(PrinterAdapter parentAdapter) {
        super(parentAdapter);
    }

    @Override
    protected void initTypesMapping() {
        super.initTypesMapping();
        SET_CLASS_NAMES.forEach(name -> extTypesMapping.put(name, "Set"));
        MAP_CLASS_NAMES.forEach(name -> extTypesMapping.put(name, "Map"));
        extTypesMapping.put(Map.class.getName() + ".Entry", "any");
    }

    @Override
    public boolean substituteForEachLoop(ForeachLoopElement foreachLoop, boolean targetHasLength, String indexVarName) {
        EnhancedForLoopTree loop = ((ForeachLoopElementSupport) foreachLoop).getTree();
        if (!targetHasLength
                && SET_CLASS_NAMES.contains(context.types.erasure(Util.getType(loop.getExpression())).toString())) {
            getPrinter().print(loop.getExpression())
                    .print(".forEach((" + loop.getVariable().getName().toString() + ")=>");
            getPrinter().printIndent().print(loop.getStatement());
            endIndent().println().printIndent().print(")");
            return true;
        }
        return super.substituteForEachLoop(foreachLoop, targetHasLength, indexVarName);
    }

    @Override
    public boolean substituteNewClass(NewClassElement newClass) {
        String className = newClass.getTypeAsElement().toString();

        if (SET_CLASS_NAMES.contains(className)) {
            this.substituteNewSet(newClass);
            return true;
        }
        if (MAP_CLASS_NAMES.contains(className)) {
            this.substituteNewMap(newClass);
            return true;
        }

        return super.substituteNewClass(newClass);
    }

    protected void substituteNewSet(NewClassElement newClass) {
        TypeMirror genericIterable = context.types.erasure(util().getType(Iterable.class));

        boolean ignoreArguments = newClass.getArgumentCount() == 0
                || !(newClass.getArgument(0).getType() instanceof ArrayType || context.types
                        .isAssignable(context.types.erasure(newClass.getArgument(0).getType()), genericIterable));

        if (ignoreArguments) {
            print("new Set()");
        } else {
            print("new Set(").print(newClass.getArgument(0)).print(")");
        }
    }

    protected void substituteNewMap(NewClassElement newClass) {
        boolean ignoreArguments = newClass.getArgumentCount() == 0
                || !context.types.erasure(newClass.getArgument(0).getType()).toString().endsWith("Map");

        if (ignoreArguments) {
            print("new Map()");
        } else {
            print("new Map(").print(newClass.getArgument(0)).print(")");
        }
    }

    @Override
    public boolean substituteMethodInvocation(MethodInvocationElement invocation) {
        String targetClassName = invocation.getMethod().getEnclosingElement().toString();
        ExtendedElement targetExpression = invocation.getTargetExpression();
        if (targetExpression != null) {
            targetClassName = targetExpression.getTypeAsElement().toString();
        }

        if (SET_CLASS_NAMES.contains(targetClassName)) {
            this.substituteMethodOnSet(invocation);
            return true;
        }
        if (MAP_CLASS_NAMES.contains(targetClassName)) {
            this.substituteMethodOnMap(invocation);
            return true;
        }

        return super.substituteMethodInvocation(invocation);
    }

    protected void substituteMethodOnSet(MethodInvocationElement invocation) {
        String targetMethodName = invocation.getMethodName();
        ExtendedElement targetExpression = invocation.getTargetExpression();

        switch (targetMethodName) {
        case "add":
            printMacroName(targetMethodName);
            print("((s, v) => { const n = s.size; s.add(v); return n !== s.size; })(").print(targetExpression)
                    .print(",").print(invocation.getArgument(0)).print(")");
            break;
        case "addAll":
            printMacroName(targetMethodName);
            print("((s, c) => { const len = s.size; for (const e of c) s.add(e); return s.size !== len; })(")
                    .print(targetExpression).print(",").print(invocation.getArgument(0)).print(")");
            break;
        case "clear":
            printMacroName(targetMethodName);
            print(targetExpression).print(".clear()");
            break;
        case "contains":
            printMacroName(targetMethodName);
            print(targetExpression).print(".has(").print(invocation.getArgument(0)).print(")");
            break;
        case "containsAll":
            printMacroName(targetMethodName);
            print("((s, c) => c.every(e => s.has(e)))(").print(targetExpression).print(",")
                    .print(invocation.getArgument(0)).print(")");
            break;
        case "equals":
            printMacroName(targetMethodName);
            print("((s1, s2) => { if (!s1 || !s2) return s1 === s2; return s1.size === s2.size && Array.from(s1).every(e => s2.has(e)) })(")
                    .print(targetExpression).print(",").print(invocation.getArgument(0)).print(")");
            break;
        case "hashCode":
            printMacroName(targetMethodName);
            report(invocation, JSweetProblem.USER_ERROR, "hashCode() is not supported.");
            break;
        case "isEmpty":
            printMacroName(targetMethodName);
            print("(").print(targetExpression).print(".size === 0").print(")");
            break;
        case "iterator":
            printMacroName(targetMethodName);
            print(targetExpression).print(".values()");
            break;
        case "remove":
            printMacroName(targetMethodName);
            print("(").print(targetExpression).print(".delete(").print(invocation.getArgument(0)).print(")").print(")");
            break;
        case "removeAll":
            printMacroName(targetMethodName);
            print("((s, c) => { const len = s.size; for (const e of c) s.delete(e); return s.size !== len; })(")
                    .print(targetExpression).print(",").print(invocation.getArgument(0)).print(")");
            break;
        case "retainAll":
            printMacroName(targetMethodName);
            print("((s, c) => { const len = s.size; const ca = Array.from(c); s.forEach(e => { if (ca.indexOf(e) == -1) s.delete(e) }); return len !== s.size; })(")
                    .print(targetExpression).print(",").print(invocation.getArgument(0)).print(")");
            break;
        case "size":
            // size typing was so strong that it breaks unit tests checking size "type" (e.g
            // 0 !== 2)
            print("(");
            printMacroName(targetMethodName);
            print(targetExpression).print(".size");
            print(")");
            break;
        case "toArray":
            printMacroName(targetMethodName);
            print("(").print("Array.from(").print(targetExpression).print(")").print(")");
            break;
        default:
            printCallToEponymMethod(invocation);
        }
    }

    protected void substituteMethodOnMap(MethodInvocationElement invocation) {
        String targetMethodName = invocation.getMethodName();
        ExtendedElement targetExpression = invocation.getTargetExpression();

        switch (targetMethodName) {
        case "put":
            printMacroName(targetMethodName);
            print("((m, k, v) => { const prev = m.get(k); m.set(k,v); return prev; })(");
            print(targetExpression).print(",");
            print(invocation.getArgument(0)).print(",");
            print(invocation.getArgument(1));
            print(")");
            break;
        case "putAll":
            printMacroName(targetMethodName);
            print("((m, m2) => { for (const e of m2) m.set(e[0], e[1]) })(").print(targetExpression).print(",")
                    .print(invocation.getArgument(0)).print(")");
            break;
        case "containsKey":
            printMacroName(targetMethodName);
            print(targetExpression).print(".has(").print(invocation.getArgument(0)).print(")");
            break;
        case "containsValue":
            printMacroName(targetMethodName);
            print("((m, v) => Array.from(m.values()).indexOf(v)>-1)(");
            print(targetExpression).print(",").print(invocation.getArgument(0));
            print(")");
            break;

        case "entrySet":
            printMacroName(targetMethodName);
            print("((m) => new Set(Array.from(m.entries()).map(entry => ({ getKey: () => entry[0], getValue: () => entry[1] }) ) ))(");
            print(targetExpression);
            print(")");
            break;
        case "keySet":
            printMacroName(targetMethodName);
            print("((m) => new Set(m.keys()))(");
            print(targetExpression);
            print(")");
            break;
        case "values":
            printMacroName(targetMethodName);
            print("((m) => Array.from(m.values()))(");
            print(targetExpression);
            print(")");
            break;

        case "equals":
            printMacroName(targetMethodName);
            print("((s1, s2) => { if (!s1 || !s2) return s1 === s2; return s1.size === s2.size && Array.from(s1.keys()).every(i => x[i] == x2[i]) })(")
                    .print(targetExpression).print(",").print(invocation.getArgument(0)).print(")");
            break;
        case "hashCode":
            printMacroName(targetMethodName);
            report(invocation, JSweetProblem.USER_ERROR, "hashCode() is not supported.");
            break;

        case "isEmpty":
            printMacroName(targetMethodName);
            print("(").print(targetExpression).print(".size === 0").print(")");
            break;

        case "remove":
            printMacroName(targetMethodName);
            print("((m, k) => { const v = m.get(k); return m.delete(k) ? v : undefined; })(");
            print(targetExpression).print(",").print(invocation.getArgument(0));
            print(")");
            break;

        case "getOrDefault":
            printMacroName(targetMethodName);
            print("(");
            print(targetExpression);
            print(" .get(");
            print(invocation.getArgument(0));
            print(" ) || ");
            print(invocation.getArgument(1));
            print(")");
            break;

        case "compute":
            printMacroName(targetMethodName);
            print("((m, k, f) => { const v = f(k, m.get(k)); m.set(k, v); return v; })(");
            print(targetExpression).print(",").print(invocation.getArgument(0)).print(",")
                    .print(invocation.getArgument(1));
            print(")");
            break;
        case "computeIfAbsent":
            printMacroName(targetMethodName);
            print("((m, k, f) => { if (m.get(k) == null) { const v = f(k); m.set(k, v); return v; } })(");
            print(targetExpression).print(",").print(invocation.getArgument(0)).print(",")
                    .print(invocation.getArgument(1));
            print(")");
            break;
        case "computeIfPresent":
            printMacroName(targetMethodName);
            print("((m, k, f) => { if (m.get(k) != null) { const v = f(k, m.get(k)); m.set(k, v); return v; } })(");
            print(targetExpression).print(",").print(invocation.getArgument(0)).print(",")
                    .print(invocation.getArgument(1));
            print(")");
            break;

        case "size":
            printMacroName(targetMethodName);
            // size typing was so strong that it breaks unit tests checking size "type" (e.g
            // 0 !== 2)
            print("(");
            print(targetExpression).print(".size");
            print(")");
            break;

        default:
            printCallToEponymMethod(invocation);
        }
    }

    private void printCallToEponymMethod(MethodInvocationElement invocation) {
        String targetMethodName = invocation.getMethodName();
        ExtendedElement targetExpression = invocation.getTargetExpression();

        printMacroName(targetMethodName);
        print(targetExpression).print(".").print(targetMethodName);
        print("(");
        for (int i = 0; i < invocation.getArgumentCount(); i++) {
            print(invocation.getArgument(i));
            if (i < invocation.getArgumentCount() - 1) {
                print(",");
            }
        }
        print(")");
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy