org.jsweet.transpiler.extension.PrinterAdapter Maven / Gradle / Ivy
Show all versions of jsweet-transpiler Show documentation
/*
* 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 java.lang.annotation.Annotation;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import org.apache.log4j.Logger;
import org.jsweet.JSweetConfig;
import org.jsweet.transpiler.JSweetContext;
import org.jsweet.transpiler.JSweetOptions;
import org.jsweet.transpiler.JSweetProblem;
import org.jsweet.transpiler.ModuleImportDescriptor;
import org.jsweet.transpiler.OverloadScanner.Overload;
import org.jsweet.transpiler.SourcePosition;
import org.jsweet.transpiler.model.ArrayAccessElement;
import org.jsweet.transpiler.model.AssignmentElement;
import org.jsweet.transpiler.model.AssignmentWithOperatorElement;
import org.jsweet.transpiler.model.BinaryOperatorElement;
import org.jsweet.transpiler.model.CaseElement;
import org.jsweet.transpiler.model.CompilationUnitElement;
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.MethodInvocationElement;
import org.jsweet.transpiler.model.NewArrayElement;
import org.jsweet.transpiler.model.NewClassElement;
import org.jsweet.transpiler.model.TypeCastElement;
import org.jsweet.transpiler.model.UnaryOperatorElement;
import org.jsweet.transpiler.model.VariableAccessElement;
import org.jsweet.transpiler.model.support.CompilationUnitElementSupport;
import org.jsweet.transpiler.model.support.ExtendedElementSupport;
import org.jsweet.transpiler.model.support.MethodInvocationElementSupport;
import org.jsweet.transpiler.util.AbstractTreePrinter;
import org.jsweet.transpiler.util.Util;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.Trees;
/**
* A printer adapter, which can be overridden to change the default printer
* behavior. Adapters are composable/chainable objects (decorator pattern).
*
* @author Renaud Pawlak
* @author Louis Grignon
*/
public class PrinterAdapter {
protected Logger logger = Logger.getLogger(getClass());
private PrinterAdapter parentAdapter;
private AbstractTreePrinter printer;
protected JSweetContext context;
/**
* Creates a root adapter (with no parent).
*
* @param context the transpilation context
*/
public PrinterAdapter(JSweetContext context) {
this.context = context;
this.parentAdapter = null;
}
/**
* 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 PrinterAdapter(PrinterAdapter parentAdapter) {
if (parentAdapter == null) {
throw new RuntimeException("cannot create an adatper with a null parent adapter: pass the context instead");
}
this.parentAdapter = parentAdapter;
this.context = parentAdapter.getContext();
}
/**
* Gets the transpiler's context.
*/
public JSweetContext getContext() {
if (context != null) {
return context;
} else {
if (getParentAdapter() != null) {
context = getParentAdapter().getContext();
}
}
return context;
}
/**
* This hook is called when the transpilation starts. Overloads must call super
* to forward this hook to the adapters chain.
*/
public void onTranspilationStarted() {
if (getParentAdapter() != null) {
getParentAdapter().onTranspilationStarted();
}
}
/**
* This hook is called when the transpilation finishes. Overloads must call
* super to forward this hook to the adapters chain.
*/
public void onTranspilationFinished() {
if (getParentAdapter() != null) {
getParentAdapter().onTranspilationFinished();
}
}
/**
* Adds a type mapping so that this adapter substitutes the source type with the
* target type during the transpilation process.
*
* @param sourceTypeName the fully qualified name of the type to be substituted
* @param targetTypeName the fully Qualified name of the type the source type is
* mapped to
*/
protected final void addTypeMapping(String sourceTypeName, String targetTypeName) {
context.addTypeMapping(sourceTypeName, targetTypeName);
}
/**
* Adds a set of name-based type mappings. This method is equivalent to calling
* {@link #addTypeMapping(String, String)} for each entry of the given map.
*/
protected final void addTypeMappings(Map nameMappings) {
context.addTypeMappings(nameMappings);
}
/**
* Returns true if the given type name is mapped through the
* {@link #addTypeMapping(String, String)} or
* {@link #addTypeMapping(String, String)} function.
*/
protected final boolean isMappedType(String sourceTypeName) {
return context.isMappedType(sourceTypeName);
}
/**
* Returns the type the given type name is mapped through the
* {@link #addTypeMapping(String, String)} or
* {@link #addTypeMapping(String, String)} function.
*/
protected final String getTypeMappingTarget(String sourceTypeName) {
return context.getTypeMappingTarget(sourceTypeName);
}
/**
* Gets the functional type mappings.
*/
protected final List> getFunctionalTypeMappings() {
return context.getFunctionalTypeMappings();
}
/**
* Adds a type mapping so that this adapter substitutes the source type tree
* with a target type during the transpilation process.
*
* @param mappingFunction a function that takes the type tree, the type name,
* and returns a substitution (either under the form of a
* string, or of a string, or of another type tree).
*/
public void addTypeMapping(BiFunction mappingFunction) {
context.addTypeMapping(mappingFunction);
}
/**
* Adds a functional type mapping.
* NOTE: If TypeMirror is generic, the mapper should include generic
* specification if relevant
*
* @param mappingFunction a function that takes the type , and returns a mapped
* type as string.
*/
protected final void addTypeMapping(Function mappingFunction) {
context.addTypeMapping(mappingFunction);
}
/**
* Returns the functional type mappings.
*/
protected final List> getTypeMirrorMappings() {
return context.getFunctionalTypeMirrorMappings();
}
/**
* Gets the string that corresponds to the given type, taking into account all
* type mappings.
*
*
* Some type mappings are set by default, some are added in the context by
* adapters.
*/
public final String getMappedType(TypeMirror type) {
return getMappedType(type, null);
}
/**
* Gets the string that corresponds to the given type, taking into account all
* type mappings.
*
*
* Some type mappings are set by default, some are added in the context by
* adapters.
*
* @param resolvedTypeArgs Optional (can be null) map of generic type to
* resolved type. For instance Map String> for a
* GenericType
*/
public final String getMappedType(TypeMirror type, Map resolvedTypeArgs) {
StringBuilder stringBuilder = new StringBuilder();
buildMappedType(stringBuilder, type, resolvedTypeArgs);
return stringBuilder.toString();
}
private final void buildMappedType(StringBuilder stringBuilder, TypeMirror type,
Map resolvedTypeArgs) {
switch (type.getKind()) {
case DECLARED:
DeclaredType declaredType = (DeclaredType) type;
Element element = declaredType.asElement();
String elementName = element.toString();
String mapped = context.getTypeMappingTarget(elementName);
if (mapped != null) {
stringBuilder.append(mapped);
} else {
stringBuilder.append(element.getSimpleName().toString());
}
if (!"any".equals(mapped) && !declaredType.getTypeArguments().isEmpty()) {
stringBuilder.append("<");
for (TypeMirror arg : declaredType.getTypeArguments()) {
buildMappedType(stringBuilder, arg, resolvedTypeArgs);
stringBuilder.append(", ");
}
stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length());
stringBuilder.append(">");
}
break;
case ARRAY:
buildMappedType(stringBuilder, ((javax.lang.model.type.ArrayType) type).getComponentType(),
resolvedTypeArgs);
stringBuilder.append("[]");
break;
case TYPEVAR:
case WILDCARD:
if (resolvedTypeArgs != null && resolvedTypeArgs.get(type) != null && resolvedTypeArgs.get(type) != type) {
buildMappedType(stringBuilder, resolvedTypeArgs.get(type), resolvedTypeArgs);
} else {
stringBuilder.append("any");
}
break;
default:
if (context.isMappedType(type.toString())) {
stringBuilder.append(context.getTypeMappingTarget(type.toString()));
} else {
stringBuilder.append(type.toString());
}
}
}
/**
* Adds an annotation on the AST through global filters.
*
*
* Example:
*
*
* context.addAnnotation(FunctionalInterface.class, "**.MyInterface");
*
*
*
* Filters are simplified regular expressions matching on the Java AST. Special
* characters are the following:
*
*
* - *: matches any token/identifier in the signature of the AST element
* - **: matches any list of tokens in signature of the AST element (same as
* ..)
* - ..: matches any list of tokens in signature of the AST element (same as
* **)
* - !: negates the filter (first character only)
*
*
*
* For example, to match:
*
*
* - all the elements in the x.y.z package: x.y.z.*
* - all the elements and subelements (fields, methods, ...) in the x.y.z
* package: x.y.z.**
* - all the methods in the x.y.z.A class: x.y.z.A.*(..)
* - all the methods taking 2 arguments in the x.y.z.A class:
* x.y.z.A.*(*,*)
* - all fields call aField in all the classes: **.aField
*
*
* @param annotationType the annotation type
* @param filters the annotation is activated if one of the filters match
* and no negative filter matches
*/
public final void addAnnotation(Class annotationType, String... filters) {
addAnnotation(annotationType.getName(), filters);
}
/**
* Adds an annotation on the AST through global filters.
*
* The annotation to be added is described by its type and by a value, which is
* passed as is to the annotation's value. If the annotation type does not
* accept a value parameter, no annotations will be added.
*
* @see #addAnnotation(String, String...)
*/
public final void addAnnotationWithValue(Class annotationType, Object value,
String... filters) {
addAnnotation(annotationType.getName() + "('" + value.toString() + "')", filters);
}
/**
* Adds an annotation manager that will tune (add or remove) annotations on the
* AST. Lastly added managers have precedence over firstly added ones.
*/
public final void addAnnotationManager(AnnotationManager annotationManager) {
context.addAnnotationManager(annotationManager);
}
/**
* Returns true if the given element is annotated with one of the given
* annotation types.
*
* To change the behavior of this method in a composable way, use
* {@link #addAnnotation(Class, String...)} or
* {@link #addAnnotationManager(AnnotationManager)}.
*/
public final boolean hasAnnotationType(Element element, String... annotationTypes) {
return context.hasAnnotationType((Element) element, annotationTypes);
}
/**
* Gets the first value of the 'value' property for the given annotation type if
* found on the given element.
*
* To change the behavior of this method in a composable way, use
* {@link #addAnnotation(Class, String...)} or
* {@link #addAnnotationManager(AnnotationManager)}.
*
* @param element the element holding the annotation
* @param annotationType the fully qualified name of the value property type
* @param propertyClass the expected class of the property (String.class,
* TypeMirror.class, Number.class, and arrays such as
* String[].class...)
* @param defaultValue the default value if the property is not found
*/
public final T getAnnotationValue(Element element, String annotationType, Class propertyClass,
T defaultValue) {
return getAnnotationValue((Element) element, annotationType, null, propertyClass, defaultValue);
}
/**
* Gets the first value of the given property for the given annotation type if
* found on the given element.
*
* @param element the element holding the annotation
* @param annotationType the fully qualified name of the value property type
* @param propertyName the name of the property in the annotation
* (null
will look up the value
* property)
* @param propertyClass the expected class of the property (String.class,
* TypeMirror.class, Number.class, and arrays such as
* String[].class...)
* @param defaultValue the default value if the property is not found
*/
public final T getAnnotationValue(Element element, String annotationType, String propertyName,
Class propertyClass, T defaultValue) {
return context.getAnnotationValue((Element) element, annotationType, propertyName, propertyClass, defaultValue);
}
/**
* Adds an annotation on the AST through global filters.
*
*
* Example:
*
*
* context.addAnnotation("@Erased", "*.writeObject(*)");
* context.addAnnotation("@Name('newName')", "*.MyDeclarationToBeRenamed");
*
*
*
* Filters are simplified regular expressions matching on the Java AST. Special
* characters are the following:
*
*
* - *: matches any character in the signature of the AST element
* - !: negates the filter (first character only)
*
*
* @param annotationDescriptor the annotation type name, optionally preceded
* with a @, and optionally defining a value (fully
* qualified name is not necessary for JSweet
* annotations)
* @param filters the annotation is activated if one of the filters
* match and no negative filter matches
*/
public final void addAnnotation(String annotationDescriptor, String... filters) {
context.addAnnotation(annotationDescriptor, filters);
}
/**
* A list of type variables to be erased (mapped to any).
*/
public Set typeVariablesToErase = new HashSet<>();
/**
* Prints a generic element by delegating to the printer.
*/
public PrinterAdapter print(ExtendedElement element) {
printer.print(((ExtendedElementSupport) element).getTree());
return this;
}
/**
* Prints a string by delegating to the printer.
*/
public PrinterAdapter print(String string) {
printer.print(string);
return this;
}
/**
* Prints a tree by delegating to the printer.
*/
protected final PrinterAdapter print(Tree tree) {
printer.print(tree);
return this;
}
/**
* Prints a name by delegating to the printer.
*/
public PrinterAdapter print(Name name) {
printer.print(name.toString());
return this;
}
/**
* Prints a new line by delegating to the printer.
*/
public PrinterAdapter println() {
printer.println();
return this;
}
/**
* Prints an argument list by delegating to the printer.
*/
public PrinterAdapter printArgList(List args) {
printer.printArgList(null,
args.stream().map(a -> ((ExtendedElementSupport) a).getTree()).collect(Collectors.toList()));
return this;
}
/**
* Prints a comma-separated, zero-indexed, generated identifier list.
*
*
* For instance printIdentifierList("x", 4)
will print:
* x0, x1, x2, x3
.
*
* @param prefix the prefix of the identifiers
* @param count the number of identifiers in the list
* @return this printer adapter
*/
public PrinterAdapter printIdentifierList(String prefix, int count) {
for (int i = 0; i < count; i++) {
print(prefix + i + ", ");
}
if (count > 0) {
removeLastChars(2);
}
return this;
}
/**
* Print either a string, or a tree if the string is null.
*/
public void print(String exprStr, ExtendedElement expr) {
if (exprStr == null) {
print(expr);
} else {
print(exprStr);
}
}
/**
* Prints an indentation for the current indentation value.
*/
public PrinterAdapter printIndent() {
printer.printIndent();
return this;
}
/**
* Increments the current indentation value.
*/
public final PrinterAdapter startIndent() {
printer.startIndent();
return this;
}
/**
* Decrements the current indentation value.
*/
public final PrinterAdapter endIndent() {
printer.endIndent();
return this;
}
/**
* Adds a space to the output.
*/
public final PrinterAdapter space() {
printer.space();
return this;
}
/**
* removes last character if expectedChar
*/
public final boolean removeLastChar(char expectedChar) {
return printer.removeLastChar(expectedChar);
}
/**
* Removes the last output character.
*/
public final PrinterAdapter removeLastChar() {
printer.removeLastChar();
return this;
}
/**
* Removes the last output characters.
*/
public final PrinterAdapter removeLastChars(int count) {
printer.removeLastChars(count);
return this;
}
/**
* Removes the last printed indentation.
*/
public final PrinterAdapter removeLastIndent() {
printer.removeLastIndent();
return this;
}
/**
* Gets currently scanned element.
*/
public final ExtendedElement getCurrentElement() {
return ExtendedElementFactory.INSTANCE.create(printer.getCurrent());
}
/**
* Gets the parent element in the printer's scanning stack.
*/
public final ExtendedElement getParentElement() {
return printer.getParentElement();
}
/**
* Gets the parent element in the printer's scanning stack.
*/
public final T getParentElement(Class type) {
return printer.getParentElement(type);
}
/**
* Looks-up the executable that is invoked by the given invocation.
*/
public final ExecutableElement findExecutableDeclarationInType(TypeElement type,
MethodInvocationElement invocation) {
return util().findMethodDeclarationInType((TypeElement) type,
((MethodInvocationElementSupport) invocation).getTree());
}
/**
* Gets the qualified name of an element, relatively to a possible
* @Root
annotation.
*/
public final String getRootRelativeName(Element element) {
return printer.getRootRelativeName((Element) element);
}
/**
* Reports a problem during the printing phase.
*
* @param element the code where the problem occurred
* @param problem the reported problem
* @param params the parameters if any
*/
protected void report(ExtendedElement element, JSweetProblem problem, Object... params) {
printer.report(((ExtendedElementSupport) element).getTree(), problem, params);
}
/**
* Reports a problem during the printing phase.
*
* @param element the code where the problem occurred
* @param problem the reported problem
* @param params the parameters if any
*/
protected void report(Element element, JSweetProblem problem, Object... params) {
printer.report(util().lookupTree(context, element), problem, params);
}
/**
* Substitutes the value of an array access expression.
*
* @param arrayAccess the array access being printed
* @return true if substituted
*/
public boolean substituteArrayAccess(ArrayAccessElement arrayAccess) {
return parentAdapter == null ? false : parentAdapter.substituteArrayAccess(arrayAccess);
}
/**
* Substitutes the value of a binary operator.
*
* @param binaryOperator the binary operator being printed
* @return true if substituted
*/
public boolean substituteBinaryOperator(BinaryOperatorElement binaryOperator) {
return parentAdapter == null ? false : parentAdapter.substituteBinaryOperator(binaryOperator);
}
/**
* Substitutes the value of a unary operator.
*
* @param unaryOperator the unary operator being printed
* @return true if substituted
*/
public boolean substituteUnaryOperator(UnaryOperatorElement unaryOperator) {
return parentAdapter == null ? false : parentAdapter.substituteUnaryOperator(unaryOperator);
}
/**
* Substitutes the value of an identifier.
*
* @param identifier the identifier being printed
* @return true if substituted
*/
public boolean substituteIdentifier(IdentifierElement identifier) {
return parentAdapter == null ? false : parentAdapter.substituteIdentifier(identifier);
}
/**
* Manually substitutes the super class of a given type.
*
* This method should print " extends X" and return true to force the
* substitution of the extends clause of a given class. This is a low-level
* operation and it is not encouraged to use it except for very specific
* contexts.
*/
public boolean substituteExtends(TypeElement type) {
return parentAdapter == null ? false : parentAdapter.substituteExtends(type);
}
/**
* Manually substitutes the super interfaces of a given type.
*
* This method should print " implements X,Y,Z" and return true to force the
* substitution of the implements clause of a given class. This is a low-level
* operation and it is not encouraged to use it except for very specific
* contexts.
*/
public boolean substituteImplements(TypeElement type) {
return parentAdapter == null ? false : parentAdapter.substituteImplements(type);
}
/**
* To override to tune the printing of a new class expression.
*
* @param newClass the new class expression
* @return true if substituted
*/
public boolean substituteNewClass(NewClassElement newClass) {
return parentAdapter == null ? false : parentAdapter.substituteNewClass(newClass);
}
/**
* Substitutes an assigned expression if necessary.
*
* Expressions are assigned to a type when used in an assignment expression (=)
* or as function arguments. JSweet implements a default behavior to adapt the
* expression when necessary (for instance to implement implicit type conversion
* that would be necessary in TypeScript). One can override or extend the
* default behavior by overriding this method.
*
* @param type the type the expression is being assigned to
* @param assignedExpression the assigned expression
* @return true if the expression has been substituted
*/
public boolean substituteAssignedExpression(TypeMirror type, ExtendedElement assignedExpression) {
return parentAdapter == null ? false : parentAdapter.substituteAssignedExpression(type, assignedExpression);
}
/**
* Upcalled by the transpiler to forward to the right subtitution method
* depending on the actual extended element type.
*/
public final boolean substitute(ExtendedElement extendedElement) {
if (extendedElement instanceof VariableAccessElement) {
return substituteVariableAccess((VariableAccessElement) extendedElement);
} else if (extendedElement instanceof IdentifierElement) {
return substituteIdentifier((IdentifierElement) extendedElement);
} else {
return false;
}
}
/**
* Substitutes the given variable access.
*
* @param variableAccess the variable access being printed
* @return true if substituted
*/
public boolean substituteVariableAccess(VariableAccessElement variableAccess) {
return parentAdapter == null ? false : parentAdapter.substituteVariableAccess(variableAccess);
}
/**
* Substitutes overloaded method implementation
*
* @param parentTypeElement parent class
* @param overload overloaded method descriptors
* @return true if substituted
*/
public boolean substituteOverloadMethodBody(TypeElement parentTypeElement, Overload overload) {
return parentAdapter == null ? false : parentAdapter.substituteOverloadMethodBody(parentTypeElement, overload);
}
/**
* Substitutes method's body
*
* @param parentTypeElement parent class
* @param method method's symbol
* @return true if substituted
*/
public boolean substituteMethodBody(TypeElement parentTypeElement, ExecutableElement method) {
return parentAdapter == null ? false : parentAdapter.substituteMethodBody(parentTypeElement, method);
}
/**
* Called if the initializer of a variable is undefined (in order to force a
* default value).
*
* @param variable the variable to return an initializer for
* @return an initializer expression, null to keep undefined
*/
public String getVariableInitialValue(VariableElement variable) {
return parentAdapter == null ? util().getTypeInitialValue(variable.asType())
: parentAdapter.getVariableInitialValue(variable);
}
/**
* Returns the import qualified id if the given import requires an import
* statement to be printed.
*
* @param importElement the given import declaration
* @param qualifiedName the qualified import id
* @return the possibly adapted qualified id or null if the import should be
* ignored by the printer
*/
public String needsImport(ImportElement importElement, String qualifiedName) {
return parentAdapter == null
? (importElement.getImportedType() == null ? null
: getRootRelativeName(importElement.getImportedType()))
: parentAdapter.needsImport(importElement, qualifiedName);
}
/**
* This method implements the default behavior to generate module imports. It
* may be overridden by subclasses to implement specific behaviors.
*
* @param currentCompilationUnit the currently transpiled compilation unit
* @param importedName the name to be imported
* @param importedClass the class being imported
* @return a {@link ModuleImportDescriptor} instance that will be used to
* generate the TypeScript import statement
*/
public ModuleImportDescriptor getModuleImportDescriptor(CompilationUnitElement currentCompilationUnit,
String importedName, TypeElement importedClass) {
if (parentAdapter != null) {
return parentAdapter.getModuleImportDescriptor(currentCompilationUnit, importedName, importedClass);
} else {
if (util().isSourceElement(importedClass)
&& !importedClass.getQualifiedName().toString().startsWith(JSweetConfig.LIBS_PACKAGE + ".")) {
String importedModule = util().getSourceFilePath(importedClass);
if (importedModule.equals(currentCompilationUnit.getSourceFilePath())) {
return null;
}
Element parent = importedClass.getEnclosingElement();
while (!(parent instanceof PackageElement)) {
importedName = parent.getSimpleName().toString();
parent = parent.getEnclosingElement();
}
while (importedClass.getEnclosingElement() instanceof TypeElement) {
importedClass = (TypeElement) importedClass.getEnclosingElement();
}
if (parent != null && !hasAnnotationType(importedClass, JSweetConfig.ANNOTATION_ERASED)
&& !(importedClass.getKind() == ElementKind.ANNOTATION_TYPE
&& !context.elementHasAnnotationType(importedClass, JSweetConfig.ANNOTATION_DECORATOR))) {
// '@' represents a common root in case there is no common root
// package => pathToImportedClass cannot be null because of the
// common '@' root
String pathToImportedClass = util().getRelativePath(
"@/" + currentCompilationUnit.getPackage().toString().replace('.', '/'),
"@/" + importedClass.toString().replace('.', '/'));
if (!pathToImportedClass.startsWith(".")) {
pathToImportedClass = "./" + pathToImportedClass;
}
return new ModuleImportDescriptor(false, (PackageElement) parent, importedName,
pathToImportedClass.replace('\\', '/'), importedClass);
}
}
return null;
}
}
/**
* Substitutes the value of a method invocation expression.
*
* @param invocation the invocation being printed
* @return true if substituted
*/
public boolean substituteMethodInvocation(MethodInvocationElement invocation) {
return parentAdapter == null ? false : parentAdapter.substituteMethodInvocation(invocation);
}
/**
* Substitutes new array expressions, which length are initialized with a variable.
*
* @param newArray
* the new array being printed
* @return true if substituted
*/
public boolean substituteNewArrayWithVariableLength(NewArrayElement newArray) {
return parentAdapter == null ? false : parentAdapter.substituteNewArrayWithVariableLength(newArray);
}
/**
* Substitutes a given type declaration.
*
* @param type the type being printed
* @return true if substituted
*/
public boolean substituteType(TypeElement type) {
return parentAdapter == null ? false : parentAdapter.substituteType(type);
}
/**
* Substitutes a given executable declaration.
*
* @param executable the executable being printed
* @return true if substituted
*/
public boolean substituteExecutable(ExecutableElement executable) {
return parentAdapter == null ? false : parentAdapter.substituteExecutable(executable);
}
/**
* Substitutes a given variable declaration.
*
* @param variable the variable being printed
* @return true if substituted
*/
public boolean substituteVariable(VariableElement variable) {
return parentAdapter == null ? false : parentAdapter.substituteVariable(variable);
}
/**
* Substitutes the value of a field assignment expression.
*
* @param assignment the field assignment being printed
* @return true if substituted
*/
public boolean substituteAssignment(AssignmentElement assignment) {
return parentAdapter == null ? false : parentAdapter.substituteAssignment(assignment);
}
/**
* Substitutes the value of an assignment with operator (a += b) expression.
*
* @param assignment the assignment being printed
* @return true if substituted
*/
public boolean substituteAssignmentWithOperator(AssignmentWithOperatorElement assignment) {
return parentAdapter == null ? false : parentAdapter.substituteAssignmentWithOperator(assignment);
}
/**
* Gets the printer on which rely the adapter (this is not recommended).
*
*
* Accessing the printer with this method allows the user to access the internal
* javac API directly ({@link com.sun.tools.javac}), which is non-standard and
* may get deprecated in future Java versions. As a consequence, to write
* sustainable adapters, it is not recommended to use the printer API.
*
*
* Instead, use the adapter's API directly, which relies on an abstraction of
* the AST: {@link javax.lang.model} and {@link org.jsweet.transpiler.model}. If
* some feature seems to be missing, please contact JSweet.org to help improving
* this API.
*/
public AbstractTreePrinter getPrinter() {
return printer;
}
/**
* Sets the printer attached to this adapter.
*/
public void setPrinter(AbstractTreePrinter printer) {
this.printer = printer;
if (parentAdapter != null) {
parentAdapter.setPrinter(printer);
}
}
public Set getErasedTypes() {
if (parentAdapter == null) {
throw new RuntimeException("unimplemented behavior");
} else {
return parentAdapter.getErasedTypes();
}
}
/**
* Substitutes if necessary the given foreach loop.
*
* @param foreachLoop the foreach loop to print
* @param targetHasLength true if the iterable defines a public length field
* @param indexVarName a possible (fresh) variable name that can used to
* iterate
*/
public boolean substituteForEachLoop(ForeachLoopElement foreachLoop, boolean targetHasLength, String indexVarName) {
return parentAdapter == null ? false
: parentAdapter.substituteForEachLoop(foreachLoop, targetHasLength, indexVarName);
}
/**
* Tells if a super class has to be erased in the generated source.
*/
public boolean eraseSuperClass(TypeElement type, TypeElement superClass) {
return parentAdapter == null ? false : parentAdapter.eraseSuperClass(type, superClass);
}
/**
* Tells if a super interface has to be erased in the generated source.
*/
public boolean eraseSuperInterface(TypeElement type, TypeElement superInterface) {
return parentAdapter == null ? false : parentAdapter.eraseSuperInterface(type, superInterface);
}
/**
* Tells if this adapter substitutes types in extends or implements clauses.
*/
public boolean isSubstituteSuperTypes() {
return parentAdapter == null ? false : parentAdapter.isSubstituteSuperTypes();
}
/**
* Substitutes if necessary an instanceof expression.
*
* @param exprStr the expression being tested as a string (null if provided as a
* tree)
* @param expr the expression being tested as a tree (null if provided as a
* string)
* @param type the type of the instanceof expression
* @return true if substituted
*/
public boolean substituteInstanceof(String exprStr, ExtendedElement expr, TypeMirror type) {
return parentAdapter == null ? false : parentAdapter.substituteInstanceof(exprStr, expr, type);
}
/**
* Substitutes if necessary a type cast expression.
*/
public boolean substituteTypeCast(TypeCastElement castExpression) {
return parentAdapter == null ? false : parentAdapter.substituteTypeCast(castExpression);
}
/**
* Substitutes if necessary the pattern of a case statement.
*/
public boolean substituteCaseStatementPattern(CaseElement caseStatement, ExtendedElement pattern) {
return parentAdapter == null ? false : parentAdapter.substituteCaseStatementPattern(caseStatement, pattern);
}
/**
* Substitutes if necessary the selector expression of a switch statement.
*/
public boolean substituteSwitchStatementSelector(ExtendedElement selector) {
return parentAdapter == null ? false : parentAdapter.substituteSwitchStatementSelector(selector);
}
/**
* This method is called after a type was printed.
*/
public void afterType(TypeElement type) {
if (parentAdapter != null) {
parentAdapter.afterType(type);
}
}
/**
* This method is called before starting printing a compilation unit (its namespace and content).
* getCompilationUnit() is available on this adapter at this stage.
*/
public void beforeCompilationUnit() {
if (parentAdapter != null) {
parentAdapter.beforeCompilationUnit();
}
}
/**
* This method is called before starting printing the body of a type.
*/
public void beforeTypeBody(TypeElement type) {
if (parentAdapter != null) {
parentAdapter.beforeTypeBody(type);
}
}
/**
* This method is called after printing the body of a type.
*/
public void afterTypeBody(TypeElement type) {
if (parentAdapter != null) {
parentAdapter.afterTypeBody(type);
}
}
/**
* Adapts the JavaDoc comment for a given element.
*
* @param element the documented element
* @param commentText the comment text if any (null when no comment)
* @return the adapted comment (null will remove the JavaDoc comment)
*/
public String adaptDocComment(Element element, String commentText) {
return parentAdapter == null ? commentText : parentAdapter.adaptDocComment(element, commentText);
}
/**
* Gets the parent adapter. By default, an adapter delegates to the parent
* adapter when the behavior is not overridden.
*/
public PrinterAdapter getParentAdapter() {
return parentAdapter;
}
/**
* Sets the parent adapter. By default, an adapter delegates to the parent
* adapter when the behavior is not overridden.
*/
public void setParentAdapter(PrinterAdapter parentAdapter) {
this.parentAdapter = parentAdapter;
}
/**
* Gets the types API, which provides a set of utilities on TypeMirror.
*
* @see TypeMirror
* @see Element#asType()
* @see ExtendedElement#getType()
*/
public Types types() {
return context.types;
}
/**
* Gets the util API, which provides a set of utilities.
*/
public Util util() {
return context.util;
}
protected Trees trees() {
return context.trees;
}
/**
* Print the macro name in the code.
*/
protected final void printMacroName(String macroName) {
print("/* " + macroName + " */");
}
/**
* Tells if the given element is ambient (part of a def.* package or within an
* @Ambient
-annotated scope).
*/
public final boolean isAmbientDeclaration(Element element) {
return context.isAmbientDeclaration((Element) element);
}
/**
* Gets the transpiler options.
*/
public final JSweetOptions getTranspilerOptions() {
return context.options;
}
/**
* This method sets a header to the currently printed file. This header can be
* TypeScript code, but use with caution since it may raise compilation errors.
*
*
* Several headers can be added to the same file. Note that a new line will be
* automatically added at the end of the last header (if any), but not between
* each header. Headers will be printer in the order they have been added to the
* file. Headers are reset for each new file.
*
* @param key a key to identify the header (see {@link #getHeader(String)})
* @param header any string that will be printed at the beginning of the file
* (only when not in bundle mode)
*
* @see #getHeader(String)
*/
public final void addHeader(String key, String header) {
context.addHeader(key, header);
}
/**
* Gets the header associated to the given key (null if non-existing key).
*
* @param key the header's key as set by {@link #addHeader(String, String)}
* @return the associated header (null if non-existing key)
*
* @see #addHeader(String, String)
*/
public final String getHeader(String key) {
return context.getHeader(key);
}
/**
* Tells if this element is an inlined expression. An inlined expression
* typically requires parenthesis (on contrary to top-level statement for
* instance).
*
* @param element the element to check
* @return true if an inlined expression
*/
public final boolean isInlinedExpression(ExtendedElement element) {
return printer.isInlinedExpression(((ExtendedElementSupport) element).getTree());
}
/**
* Returns a comparator that will control the order of class members for output
* (default will keep order of appearance in the source code).
*/
public Comparator getClassMemberComparator() {
if (parentAdapter == null) {
return new Comparator() {
@Override
public int compare(ExtendedElement e1, ExtendedElement e2) {
SourcePosition sourcePosition1 = e1.getSourcePosition();
SourcePosition sourcePosition2 = e2.getSourcePosition();
return sourcePosition1.getStartPosition().compareTo(sourcePosition2.getStartPosition());
}
};
} else {
return parentAdapter.getClassMemberComparator();
}
}
/**
* Optionally substitutes the default variable declaration keyword
* (var
, let
, const
) for the given
* variable. By default, regular variables are declared with the
* let
keyword, except for unmodified variables, which are declared
* with the const
keyword.
*
* @param variable the target variable
* @return true if the default keyword has been substituted, false otherwise
* (default is false)
*/
public boolean substituteVariableDeclarationKeyword(VariableElement variable) {
return parentAdapter == null ? false : parentAdapter.substituteVariableDeclarationKeyword(variable);
}
/**
* Returns the current compilation unit element.
*/
public CompilationUnitElement getCompilationUnit() {
return new CompilationUnitElementSupport(getCompilationUnitTree());
}
protected final CompilationUnitTree getCompilationUnitTree() {
return printer.getCompilationUnit();
}
protected final PackageElement getPackageElement() {
return Util.getElement(getCompilationUnitTree().getPackage());
}
// protected final T toElement(Tree tree) {
// return Util.getElement(tree);
// }
//
// @SuppressWarnings("unchecked")
// protected final T toType(Tree tree) {
// return (T) util().getTypeForTree(tree, getCompilationUnitTree());
// }
}