Merge pull request #631 from cincheo/feature/613_Optional_support_jsweet2

fix #613 Optional support
This commit is contained in:
Louis Grignon 2020-11-27 15:13:15 +01:00 committed by GitHub
commit b61de98c5a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 2039 additions and 1869 deletions

View File

@ -48,6 +48,7 @@ import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
@ -68,6 +69,7 @@ import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.PackageSymbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Types;
@ -1562,30 +1564,6 @@ public class JSweetContext extends Context {
return defaultValue;
}
// /**
// * Gets the value of the given annotation property.
// *
// * @param annotation
// * the annotation
// * @param propertyName
// * the name of the annotation property to get the value of
// * @param defaultValue
// * the value to return if not found
// */
// @SuppressWarnings("unchecked")
// private static <T> T getAnnotationValue(AnnotationMirror annotation,
// String propertyName, T defaultValue) {
// for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue>
// annoProperty : annotation
// .getElementValues().entrySet()) {
// if
// (annoProperty.getKey().getSimpleName().toString().equals(propertyName)) {
// return (T) annoProperty.getValue().getValue();
// }
// }
// return defaultValue;
// }
/**
* Gets the symbol's annotation that correspond to the given annotation type
* name if exists.
@ -1599,19 +1577,6 @@ public class JSweetContext extends Context {
return null;
}
// /**
// * Gets the annotation tree that matches the given type name.
// */
// private static JCAnnotation getAnnotation(List<JCAnnotation> annotations,
// String annotationType) {
// for (JCAnnotation a : annotations) {
// if (annotationType.equals(a.type.toString())) {
// return a;
// }
// }
// return null;
// }
/**
* Grabs the names of all the support interfaces in the class and interface
* hierarchy.
@ -1947,4 +1912,7 @@ public class JSweetContext extends Context {
}
}
public Elements elements() {
return JavacElements.instance(this);
}
}

View File

@ -1315,11 +1315,8 @@ public class Java2TypeScriptAdapter extends PrinterAdapter {
print(")");
}
} else {
printMacroName(targetMethodName);
print("(<any>((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("))");
printDefaultEquals(invocationElement.getTargetExpression(),
invocationElement.getArgument(0));
}
return true;
case "compareTo":
@ -1413,6 +1410,12 @@ public class Java2TypeScriptAdapter extends PrinterAdapter {
}
}
protected void printDefaultEquals(ExtendedElement left, ExtendedElement right) {
print("(<any>((o1: any, o2: any) => o1 && o1.equals ? o1.equals(o2) : o1 === o2)(");
printTarget(left).print(",").print(right);
print("))");
}
protected void printFunctionalInvocation2(ExtendedElement target, String functionName,
List<ExtendedElement> arguments) {
print("((target => (target['" + functionName + "'] === undefined)?target:target['" + functionName + "'])(")
@ -1677,4 +1680,5 @@ public class Java2TypeScriptAdapter extends PrinterAdapter {
return super.substituteForEachLoop(foreachLoop, targetHasLength, indexVarName);
}
}

View File

@ -52,6 +52,7 @@ import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
@ -68,7 +69,6 @@ import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import com.sun.tools.javac.code.Type;
import org.jsweet.transpiler.JSweetContext;
import org.jsweet.transpiler.Java2TypeScriptTranslator;
import org.jsweet.transpiler.ModuleKind;
@ -85,6 +85,7 @@ import org.jsweet.transpiler.model.VariableAccessElement;
import org.jsweet.transpiler.model.support.ForeachLoopElementSupport;
import org.jsweet.transpiler.util.Util;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.tree.JCTree.JCEnhancedForLoop;
import com.sun.tools.javac.tree.JCTree.JCLiteral;
import com.sun.tools.javac.tree.JCTree.JCTypeApply;
@ -168,11 +169,29 @@ public class RemoveJavaDependenciesAdapter extends Java2TypeScriptAdapter {
extTypesMapping.put(Method.class.getName(), "{ owner: any, name: string, fn : Function }");
addTypeMapping(
(typeTree,
name) -> name.startsWith("java.")
&& types().isSubtype(typeTree.getType(), util().getType(Throwable.class)) ? "Error"
: null);
addTypeMapping((ExtendedElement typeElement, String qualifiedName) -> {
TypeMirror type = typeElement.getType();
if (!qualifiedName.startsWith("java.") || type == null) {
return null;
}
// Throwable
if (types().isSubtype(type, util().getType(Throwable.class))) {
return "Error";
}
// Optional
if (types().isSubtype(types().erasure(type), types().erasure(util().getType(Optional.class)))
&& type instanceof DeclaredType) {
List<? extends TypeMirror> typeArgs = ((DeclaredType) type).getTypeArguments();
if (typeArgs.size() > 0) {
return getMappedType(typeArgs.get(0));
}
}
return null;
});
// TODO: use standard API
// addTypeMapping((typeTree,
// name) -> typeTree.asType() instanceof DeclaredType
@ -315,6 +334,11 @@ public class RemoveJavaDependenciesAdapter extends Java2TypeScriptAdapter {
return true;
}
break;
case "java.util.Optional":
if (substituteMethodInvocationOnOptional(invocation, targetMethodName, delegate)) {
return true;
}
break;
case "java.lang.StringBuffer":
case "java.lang.StringBuilder":
if (substituteMethodInvocationOnStringBuilder(invocation, targetMethodName, delegate)) {
@ -493,6 +517,68 @@ public class RemoveJavaDependenciesAdapter extends Java2TypeScriptAdapter {
return super.substituteMethodInvocation(invocation);
}
private boolean substituteMethodInvocationOnOptional(MethodInvocationElement invocation, String targetMethodName,
boolean delegate) {
switch (targetMethodName) {
case "of":
print(invocation.getArgument(0));
return true;
case "empty":
print("null");
return true;
case "ofNullable":
print(invocation.getArgument(0));
return true;
case "equals":
printDefaultEquals(invocation.getTargetExpression(), invocation.getArgument(0));
return true;
case "filter":
printMacroName(targetMethodName);
print("((o, condition) => condition(o) ? o : null)(").print(invocation.getTargetExpression()).print(",")
.print(invocation.getArgument(0)).print(")");
return true;
case "orElse":
printMacroName(targetMethodName);
print("((v, v2) => v == null ? v2 : v)(").print(invocation.getTargetExpression()).print(",")
.print(invocation.getArgument(0)).print(")");
return true;
case "orElseGet":
case "or":
printMacroName(targetMethodName);
print("((v, get) => v == null ? get() : v)(").print(invocation.getTargetExpression()).print(",")
.print(invocation.getArgument(0)).print(")");
return true;
case "ifPresent":
printMacroName(targetMethodName);
print("((v, action) => v != null ? action(v) : null)(").print(invocation.getTargetExpression()).print(",")
.print(invocation.getArgument(0)).print(")");
return true;
case "ifPresentOrElse":
printMacroName(targetMethodName);
print("((v, action, fallbackAction) => v != null ? action(v) : fallbackAction())(")
.print(invocation.getTargetExpression()).print(",").print(invocation.getArgument(0)).print(",")
.print(invocation.getArgument(1)).print(")");
return true;
case "map":
case "flatMap":
printMacroName(targetMethodName);
print("(").print(invocation.getArgument(0)).print(")(").print(invocation.getTargetExpression()).print(")");
return true;
case "orElseThrow":
printMacroName(targetMethodName);
if (invocation.getArgumentCount() > 0) {
print("((v, throwError) => { if (v == null) throwError(); return v; })(")
.print(invocation.getTargetExpression()).print(",").print(invocation.getArgument(0)).print(")");
} else {
print("(v => { if (v == null) throw new Error('value is null'); return v; })(")
.print(invocation.getTargetExpression()).print(")");
}
return true;
}
return false;
}
protected boolean substituteMethodInvocationOnObjects(MethodInvocationElement invocation, String targetMethodName,
boolean delegate) {
switch (targetMethodName) {

View File

@ -33,7 +33,6 @@ public interface Util {
* Gets the type from an existing runtime class when possible (return null
* when the type cannot be found in the compiler's symbol table).
* <p/>
* This method only looks up well-known Java types.
*/
TypeMirror getType(Class<?> clazz);

View File

@ -125,6 +125,12 @@ public class UtilSupport implements Util {
case "java.util.List":
return context.symtab.listType;
}
TypeElement typeElement = context.elements().getTypeElement(clazz.getName());
if (typeElement != null) {
return typeElement.asType();
}
return null;
}

1
transpiler/src/test/java/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/source/

View File

@ -40,6 +40,7 @@ import source.api.ForeachIteration;
import source.api.J4TSInvocations;
import source.api.JdkInvocations;
import source.api.Numbers;
import source.api.Optionals;
import source.api.PrimitiveInstantiation;
import source.api.PromisesAsyncAwait;
import source.api.QualifiedInstantiation;
@ -156,7 +157,6 @@ public class ApiTests extends AbstractTest {
assertTrue(result.get("subMethod2"));
assertEquals("subMethod2", result.get("resultSubMethod2"));
}, getSourceFile(PromisesAsyncAwait.class));
}
@ -281,12 +281,15 @@ public class ApiTests extends AbstractTest {
eval(ModuleKind.none, (logHandler, r) -> {
logHandler.assertNoProblems();
System.out.println("result = " + r.get("localeString"));
assertTrue(asList(
"1/1/2020, 1:00:00 AM",
"2020-1-1 1:00:00 AM").contains(
r.get("localeString")));
assertTrue(asList("1/1/2020, 1:00:00 AM", "2020-1-1 1:00:00 AM").contains(r.get("localeString")));
}, getSourceFile(Dates.class));
}
@Test
public void testOptional() {
eval((logHandler, result) -> {
logHandler.assertNoProblems();
}, getSourceFile(Optionals.class));
}
}

View File

@ -0,0 +1,103 @@
package source.api;
import java.util.Optional;
import def.js.Error;
public class Optionals {
private static final String PREFERRED_VALUE = "c'est la vie";
public static void main(String[] args) {
}
static void executePresentTests() {
Optional<String> o = Optional.of(PREFERRED_VALUE);
assertContains(o, PREFERRED_VALUE);
o = Optional.ofNullable(PREFERRED_VALUE);
assertContains(o, PREFERRED_VALUE);
assert !o.equals(Optional.ofNullable(null));
assertContains(o.filter((current) -> current == PREFERRED_VALUE), PREFERRED_VALUE);
assertEmpty(o.filter((current) -> current == "XXXC'est la vie"));
assertContains(o.flatMap(current -> Optional.of("new")), "new");
assertContains(o.map(current -> "new2"), "new2");
String v = o.orElse("defaultVal");
assert v == PREFERRED_VALUE;
v = o.orElseGet(() -> "defaultVal2");
assert v == PREFERRED_VALUE;
String[] modifiedForPresent = { "", "" };
o.ifPresent(__ -> modifiedForPresent[0] = "modified");
assert modifiedForPresent[0] == "modified";
assert modifiedForPresent[1] == "modified";
// TODO
// Object[] valsAfterStream = o.stream().toArray();
}
static void executeAbsentTests() {
Optional<String> o = Optional.empty();
assertEmpty(o);
o = Optional.ofNullable(null);
assertEmpty(o);
assert o.equals(Optional.ofNullable(null));
boolean caught = false;
try {
o = Optional.of(null);
} catch (Error e) {
caught = true;
}
assert caught;
String v = o.orElse("defaultVal");
assert v == "defaultVal";
v = o.orElseGet(() -> "defaultVal2");
assert v == "defaultVal2";
final String[] modified = { "", "" };
o.ifPresent(__ -> modified[0] = "what");
assert modified[0] == "";
assert modified[1] == "valid";
}
static <T> void assertContains(Optional<T> o, T value) {
assert o.get() == value;
assert o.isPresent();
o.orElseThrow(() -> new MyMissingError());
}
static void assertEmpty(Optional<?> o) {
assert !o.isPresent();
assert o != null;
boolean caught = false;
try {
o.get();
} catch (Error e) {
caught = true;
}
assert caught;
caught = false;
try {
o.orElseThrow(() -> new MyMissingError());
} catch (MyMissingError e) {
caught = true;
}
assert caught;
}
}
class MyMissingError extends def.js.Error {
}