add basic adapters and associated tests

This commit is contained in:
Renaud Pawlak 2017-07-23 19:00:34 +02:00
parent c10770d80b
commit 1a1110969d
9 changed files with 502 additions and 5 deletions

View File

@ -243,7 +243,7 @@ public class Java2TypeScriptTranslator extends AbstractTreePrinter {
private List<JCClassDecl> localClasses = new ArrayList<>();
private List<String> generatedMethodNames = new ArrayList<>();
// to be accessed in the parent scope
private boolean isAnonymousClass = false;
// to be accessed in the parent scope
@ -1461,8 +1461,9 @@ public class Java2TypeScriptTranslator extends AbstractTreePrinter {
Map<Name, String> signatures = new HashMap<>();
for (MethodSymbol meth : methods) {
if (meth.type instanceof MethodType) {
// do not generate default abstract method for already generated methods
if(getScope().generatedMethodNames.contains(meth.name.toString())) {
// do not generate default abstract method for already
// generated methods
if (getScope().generatedMethodNames.contains(meth.name.toString())) {
continue;
}
MethodSymbol s = Util.findMethodDeclarationInType(getContext().types, classdecl.sym,
@ -2809,7 +2810,8 @@ public class Java2TypeScriptTranslator extends AbstractTreePrinter {
print("[]");
}
} else {
if (context.hasAnnotationType(varDecl.vartype.type.tsym, ANNOTATION_STRING_TYPE)) {
if (context.hasAnnotationType(varDecl.vartype.type.tsym, ANNOTATION_STRING_TYPE)
&& !varDecl.vartype.type.tsym.isEnum()) {
print("\"");
print(context.getAnnotationValue(varDecl.vartype.type.tsym, ANNOTATION_STRING_TYPE,
String.class, varDecl.vartype.type.tsym.name.toString()).toString());

View File

@ -1,5 +1,23 @@
package org.jsweet.transpiler.extension;
/*
* JSweet transpiler - http://www.jsweet.org
* Copyright (C) 2015 CINCHEO SAS <renaud.pawlak@cincheo.fr>
*
* 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.
*/
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
@ -8,6 +26,17 @@ import javax.lang.model.element.VariableElement;
import org.jsweet.transpiler.util.Util;
/**
* This simple adapter renames non-public members by adding two underscores as a
* prefix.
*
* <p>
* Note that this could be dangerous to use for protected fields if wanting to
* access them from subclasses declared in other JSweet projects.
*
* @author Renaud Pawlak
*/
public class AddPrefixToNonPublicMembersAdapter extends PrinterAdapter {
public AddPrefixToNonPublicMembersAdapter(PrinterAdapter parentAdapter) {

View File

@ -0,0 +1,110 @@
/*
* JSweet transpiler - http://www.jsweet.org
* Copyright (C) 2015 CINCHEO SAS <renaud.pawlak@cincheo.fr>
*
* 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.math.BigDecimal;
import javax.lang.model.element.Element;
import org.jsweet.transpiler.extension.PrinterAdapter;
import org.jsweet.transpiler.model.MethodInvocationElement;
import org.jsweet.transpiler.model.NewClassElement;
/**
* This optional adapter tunes the JavaScript generation to map the Java's
* BigDecimal API to the Big JavaScript library.
*
* <p>
* Warning: this adapter is not activated by default. See JSweet specifications
* to know how to activate this adapter.
*
* <p>
* This extension requires the big.js candy to be available in the JSweet
* classpath: https://github.com/jsweet-candies/candy-bigjs.
*
* @author Renaud Pawlak
*/
public class BigDecimalAdapter extends PrinterAdapter {
public BigDecimalAdapter(PrinterAdapter parent) {
super(parent);
// all BigDecimal types are mapped to Big
addTypeMapping(BigDecimal.class.getName(), "Big");
}
@Override
public boolean substituteNewClass(NewClassElement newClass) {
String className = newClass.getTypeAsElement().toString();
// map the BigDecimal constructors
if (BigDecimal.class.getName().equals(className)) {
print("new Big(").printArgList(newClass.getArguments()).print(")");
return true;
}
// delegate to the adapter chain
return super.substituteNewClass(newClass);
}
@Override
public boolean substituteMethodInvocation(MethodInvocationElement invocation) {
if (invocation.getTargetExpression() != null) {
Element targetType = invocation.getTargetExpression().getTypeAsElement();
if (BigDecimal.class.getName().equals(targetType.toString())) {
// BigDecimal methods are mapped to their Big.js equivalent
switch (invocation.getMethodName()) {
case "multiply":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".times(").printArgList(invocation.getArguments())
.print(")");
return true;
case "add":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".plus(").printArgList(invocation.getArguments())
.print(")");
return true;
case "scale":
printMacroName(invocation.getMethodName());
// we assume that we always have a scale of 2, which is a
// good default if we deal with currencies...
// to be changed/implemented further
print("2");
return true;
case "setScale":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".round(").print(invocation.getArguments().get(0))
.print(")");
return true;
case "compareTo":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".cmp(").print(invocation.getArguments().get(0))
.print(")");
return true;
case "equals":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".eq(").print(invocation.getArguments().get(0))
.print(")");
return true;
}
}
}
// delegate to the adapter chain
return super.substituteMethodInvocation(invocation);
}
}

View File

@ -0,0 +1,119 @@
/*
* JSweet transpiler - http://www.jsweet.org
* Copyright (C) 2015 CINCHEO SAS <renaud.pawlak@cincheo.fr>
*
* 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.Arrays;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.TreeMap;
import javax.lang.model.element.Element;
import org.jsweet.transpiler.model.MethodInvocationElement;
import org.jsweet.transpiler.model.NewClassElement;
/**
* This optional adapter tunes the JavaScript generation to use JavaScript ES6
* maps to implement Java maps.
*
* <p>
* Note that this is a partial implementation that shall be extended to support
* more cases and adapted to your own requirements. Additionally, this
* implementation generates untyped JavaScript in order to avoid having to have
* the ES6 API in the compilation path.
*
* @author Renaud Pawlak
*/
public class MapAdapter extends PrinterAdapter {
static String[] mapTypes = { Map.class.getName(), HashMap.class.getName(), TreeMap.class.getName(),
Hashtable.class.getName() };
public MapAdapter(PrinterAdapter parent) {
super(parent);
// rewrite all Java map and compatible map implementations types
// note that we rewrite to 'any' because we don't want to require the
// ES6 API to compile (all subsequent accesses will be untyped)
for (String mapType : mapTypes) {
addTypeMapping(mapType, "any");
}
}
@Override
public boolean substituteNewClass(NewClassElement newClass) {
String className = newClass.getTypeAsElement().toString();
// map the map constructor to the global 'Map' variable (untyped access)
if (Arrays.binarySearch(mapTypes, className) >= 0) {
// this access is browser/node-compatible
print("new (typeof window == 'undefined'?global:window)['Map'](").printArgList(newClass.getArguments()).print(")");
return true;
}
// delegate to the adapter chain
return super.substituteNewClass(newClass);
}
@Override
public boolean substituteMethodInvocation(MethodInvocationElement invocation) {
if (invocation.getTargetExpression() != null) {
Element targetType = invocation.getTargetExpression().getTypeAsElement();
if (Arrays.binarySearch(mapTypes, targetType.toString()) >= 0) {
// Java Map methods are mapped to their JavaScript equivalent
switch (invocation.getMethodName()) {
case "put":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".set(").printArgList(invocation.getArguments())
.print(")");
return true;
// although 'get' has the same name, we still rewrite it in case
// another extension would provide it's own implementation
case "get":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".get(").printArgList(invocation.getArguments())
.print(")");
return true;
case "containsKey":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".has(").printArgList(invocation.getArguments())
.print(")");
return true;
// we use the ES6 'Array.from' method in an untyped way to
// transform the iterator in an array
case "keySet":
printMacroName(invocation.getMethodName());
print("(<any>Array).from(").print(invocation.getTargetExpression()).print(".keys())");
return true;
case "values":
printMacroName(invocation.getMethodName());
print("(<any>Array).from(").print(invocation.getTargetExpression()).print(".values())");
return true;
// in ES6 maps, 'size' is a property, not a method
case "size":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression()).print(".size");
return true;
}
}
}
// delegate to the adapter chain
return super.substituteMethodInvocation(invocation);
}
}

View File

@ -0,0 +1,123 @@
/*
* JSweet transpiler - http://www.jsweet.org
* Copyright (C) 2015 CINCHEO SAS <renaud.pawlak@cincheo.fr>
*
* 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 javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import org.jsweet.JSweetConfig;
import org.jsweet.transpiler.model.CaseElement;
import org.jsweet.transpiler.model.ExtendedElement;
import org.jsweet.transpiler.model.MethodInvocationElement;
import org.jsweet.transpiler.model.VariableAccessElement;
/**
* This optional adapter tunes the JavaScript generation to remove enums and
* replace them with strings. It only applies to enums that are annotated with
* <code>@StringType</code>.
*
* <p>
* For instance: <code>@StringType enum MyEnum { A, B, C }</code> will be erased
* and all subsequent accesses to the enum will be mapped to simple strings.
*
* <p>
* Typically, the method declaration <code>void m(MyEnum e) {...}</code> will be
* mapped to <code>void m(e : string) {...}</code>. And of course, the
* invocation <code>xxx.m(MyEnum.A)</code> will be mapped to
* <code>xxx.m("A")</code>.
*
* <p>
* Warning: this adapter is not activated by default. See JSweet specifications
* to know how to activate this adapter.
*
* @author Renaud Pawlak
*/
public class StringEnumAdapter extends PrinterAdapter {
private boolean isStringEnum(Element element) {
// note: this function could be improved to exclude enums that have
// fields or methods other than the enum constants
return element.getKind() == ElementKind.ENUM && hasAnnotationType(element, JSweetConfig.ANNOTATION_STRING_TYPE);
}
public StringEnumAdapter(PrinterAdapter parent) {
super(parent);
// eligible enums will be translated to string in JS
addTypeMapping((typeTree, name) -> isStringEnum(typeTree.getTypeAsElement()) ? "string" : null);
// ignore enum declarations with a programmatic annotation manager
addAnnotationManager(new AnnotationManager() {
@Override
public Action manageAnnotation(Element element, String annotationType) {
// add the @Erased annotation to string enums
return JSweetConfig.ANNOTATION_ERASED.equals(annotationType) && isStringEnum(element) ? Action.ADD
: Action.VOID;
}
});
}
@Override
public boolean substituteMethodInvocation(MethodInvocationElement invocation) {
if (invocation.getTargetExpression() != null) {
Element targetType = invocation.getTargetExpression().getTypeAsElement();
// enum API must be erased and use plain strings instead
if (isStringEnum(targetType)) {
switch (invocation.getMethodName()) {
case "name":
printMacroName(invocation.getMethodName());
print(invocation.getTargetExpression());
return true;
case "valueOf":
printMacroName(invocation.getMethodName());
print(invocation.getArgument(0));
return true;
case "equals":
printMacroName(invocation.getMethodName());
print("(").print(invocation.getTargetExpression()).print(" == ")
.print(invocation.getArguments().get(0)).print(")");
return true;
}
}
}
return super.substituteMethodInvocation(invocation);
}
@Override
public boolean substituteVariableAccess(VariableAccessElement variableAccess) {
// accessing an enum field is replaced by a simple string value
// (MyEnum.A => "A")
if (isStringEnum(variableAccess.getTargetElement())) {
print("\"" + variableAccess.getVariableName() + "\"");
return true;
}
return super.substituteVariableAccess(variableAccess);
}
@Override
public boolean substituteCaseStatementPattern(CaseElement caseStatement, ExtendedElement pattern) {
// map enums to strings in case statements
if (isStringEnum(pattern.getTypeAsElement())) {
print("\"" + pattern + "\"");
return true;
}
return super.substituteCaseStatementPattern(caseStatement, pattern);
}
}

View File

@ -22,6 +22,8 @@ import org.jsweet.transpiler.JSweetContext;
import org.jsweet.transpiler.JSweetFactory;
import org.jsweet.transpiler.ModuleKind;
import org.jsweet.transpiler.extension.Java2TypeScriptAdapter;
import org.jsweet.transpiler.extension.PrinterAdapter;
import org.jsweet.transpiler.extension.StringEnumAdapter;
import org.junit.Assert;
import org.junit.Test;
@ -33,6 +35,7 @@ import source.enums.ComplexEnums;
import source.enums.EnumInSamePackage;
import source.enums.Enums;
import source.enums.ErasedEnum;
import source.enums.StringEnums;
import source.enums.other.EnumInOtherPackage;
public class EnumTests extends AbstractTest {
@ -87,7 +90,7 @@ public class EnumTests extends AbstractTest {
logHandler.assertNoProblems();
}, getSourceFile(ComplexInnerEnums.class));
}
@Test
public void testComplexEnumWithAbstractMethods() {
eval((logHandler, r) -> {
@ -137,4 +140,18 @@ public class EnumTests extends AbstractTest {
}, getSourceFile(EnumWithStatics.class));
}
@Test
public void testStringEnums() {
createTranspiler(new JSweetFactory() {
@Override
public PrinterAdapter createAdapter(JSweetContext context) {
return new StringEnumAdapter(super.createAdapter(context));
}
});
eval((logHandler, r) -> {
logHandler.assertNoProblems();
}, getSourceFile(StringEnums.class));
createTranspiler(new JSweetFactory());
}
}

View File

@ -12,13 +12,18 @@ import org.jsweet.transpiler.JSweetContext;
import org.jsweet.transpiler.JSweetFactory;
import org.jsweet.transpiler.ModuleKind;
import org.jsweet.transpiler.extension.Java2TypeScriptAdapter;
import org.jsweet.transpiler.extension.MapAdapter;
import org.jsweet.transpiler.extension.PrinterAdapter;
import org.jsweet.transpiler.extension.RemoveJavaDependenciesAdapter;
import org.jsweet.transpiler.extension.StringEnumAdapter;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import source.enums.StringEnums;
import source.extension.AnnotationTest;
import source.extension.Maps;
class TestFactory extends JSweetFactory {
@ -105,4 +110,19 @@ public class ExtensionTests extends AbstractTest {
}, getSourceFile(AnnotationTest.class));
}
@Test
public void testMaps() {
createTranspiler(new JSweetFactory() {
@Override
public PrinterAdapter createAdapter(JSweetContext context) {
return new MapAdapter(super.createAdapter(context));
}
});
eval((logHandler, r) -> {
logHandler.assertNoProblems();
}, getSourceFile(Maps.class));
createTranspiler(new JSweetFactory());
}
}

View File

@ -0,0 +1,32 @@
package source.enums;
import static jsweet.util.Lang.any;
import jsweet.lang.StringType;
public class StringEnums {
public static void main(String[] args) {
m(MyStringEnum.A);
}
public static void m(MyStringEnum e) {
assert any(e) == "A";
switch (e) {
case A:
break;
case B:
case C:
default:
assert false;
}
}
}
@StringType
enum MyStringEnum {
A, B, C
}

View File

@ -0,0 +1,45 @@
package source.extension;
import java.util.HashMap;
import def.js.Array;
/**
* This test is executed without any Java runtime.
*/
public class Maps {
static Array<String> trace = new Array<>();
static String key1() {
return "1";
}
static String key2() {
return "a";
}
public static void main(String[] args) {
HashMap<String, String> m = new HashMap<>();
m.put(key1(), "a");
m.put("2", "b");
// for(Entry<String, String> e : m.entrySet()) {
//
// trace.push("" + e.getKey());
// trace.push("" + e.getValue());
// }
assert 2 == m.size();
assert "a" == m.get("1");
assert m.containsKey("2");
assert m.keySet().toString() == "[1, 2]";
assert m.values().toString() == "[a, b]";
}
}