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

org.jsweet.transpiler.OverloadScanner 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;

import static java.util.stream.Collectors.toList;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;

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.TypeMirror;
import javax.lang.model.util.Types;

import org.jsweet.JSweetConfig;
import org.jsweet.transpiler.JSweetContext.DefaultMethodEntry;
import org.jsweet.transpiler.util.AbstractTreeScanner;
import org.jsweet.transpiler.util.Util;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.StatementTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.Trees;

/**
 * This AST scanner detects method overloads and gather them into
 * {@link Overload} objects.
 * 
 * "Valid" overloads are the cases where the overload calls the overloaded
 * method with some constants. It can then be translated as a unique method
 * containing default parameter values.
 * 
 * "Wrong" overloads are all the other cases. A core generic untyped signature
 * is generated that englobles all the overloads and dispatch to the
 * implementations depending on the parameter types.
 * 
 * @author Renaud Pawlak
 * @author Louis Grignon
 */
public class OverloadScanner extends AbstractTreeScanner {

	private int pass = 1;

	/**
	 * This overload is registered for a given classTree but original methodTree can
	 * belong to another ClassTree, which is stored in owningClassTree.
	 * 
	 * NOTE: This should be handled differently but it would require a lot of work
	 * to change. We should first list all existing (written) overloads by type, and
	 * then generate missing overloads (for interface, default methods, abstract...)
	 * during a second phase
	 */
	public static class OverloadMethodEntry {
		public final CompilationUnitTree classCompilationUnit;
		public final ClassTree classTree;
		public final Element classElement;

		public final CompilationUnitTree methodCompilationUnit;
		public final ClassTree owningClassTree;
		public final MethodTree methodTree;
		public final ExecutableElement methodElement;
		public final TypeMirror methodType;

		public OverloadMethodEntry(CompilationUnitTree classCompilationUnit, ClassTree classTree, Element classElement,
				CompilationUnitTree methodCompilationUnit, ClassTree owningClassTree, MethodTree methodTree,
				ExecutableElement methodElement, TypeMirror methodType) {
			super();
			this.classCompilationUnit = classCompilationUnit;
			this.classTree = classTree;
			this.classElement = classElement;
			this.methodCompilationUnit = methodCompilationUnit;
			this.owningClassTree = owningClassTree;
			this.methodTree = methodTree;
			this.methodElement = methodElement;
			this.methodType = methodType;
		}

		@Override
		public int hashCode() {
			return methodTree.hashCode();
		}

		@Override
		public boolean equals(Object other) {
			return other instanceof OverloadMethodEntry && methodTree == ((OverloadMethodEntry) other).methodTree;
		}
	}

	/**
	 * Gathers methods overloading each other.
	 * 
	 * @author Renaud Pawlak
	 * @author Louis Grignon
	 */
	public static class Overload {
		/**
		 * The method name.
		 */
		public String methodName;

		private final List entries = new ArrayList<>();

		/**
		 * The methods carrying the same name.
		 */
		public Iterable getMethods() {
			return entries.stream().map(entry -> entry.methodTree).collect(toList());
		}

		public MethodTree getMethodAt(int i) {
			return entries.get(i).methodTree;
		}

		/**
		 * Tells if this overload is valid wrt to JSweet conventions.
		 */
		public boolean isValid = true;

		private OverloadMethodEntry coreEntry;

		/**
		 * @see #getCoreEntry()
		 */
		public MethodTree getCoreMethod() {
			return coreEntry == null ? null : coreEntry.methodTree;
		}

		/**
		 * The core method of the overload, that is to say the one holding the
		 * implementation.
		 */
		public OverloadMethodEntry getCoreEntry() {
			return coreEntry;
		}

		/**
		 * The default values for the parameters of the core method.
		 */
		public Map defaultValues;

		/**
		 * A flag to tell if this overload was printed out (used by the printer).
		 */
		public boolean printed = false;

		private final List parameterNames = new ArrayList<>();
		private final JSweetContext context;
		private final Util util;
		private final Types types;

		public Overload(JSweetContext context) {
			this.context = context;
			this.util = context.util;
			this.types = context.types;
		}

		@Override
		public String toString() {
			StringBuilder sb = new StringBuilder(
					"overload(" + methodName + ")[" + getMethodsCount() + "," + isValid + "]");
			if (getMethodsCount() > 1) {
				for (OverloadMethodEntry entry : entries) {
					ExecutableElement methodElement = entry.methodElement;
					sb.append("\n      # " + methodElement.getEnclosingElement() + "." + methodElement);
				}
			}
			return sb.toString();
		}

		public int getMethodsCount() {
			return entries.size();
		}

		/**
		 * Returns parameter count of the method having the fewer parameters.
		 */
		public int getSmallerParameterCount() {
			return getMethodAt(getMethodsCount() - 1).getParameters().size();
		}

		/**
		 * Gets the parameter name at the given index for an invalid overload.
		 * 
		 * @param index the parameter's index
		 * @return a unique parameter name that reflects the overloaded parameter
		 */
		public String getParameterName(int index) {
			return parameterNames.get(index);
		}

		/**
		 * Checks the validity of the overload and calculates the default values.
		 */
		public void calculate() {
			if (getMethodsCount() < 2) {
				coreEntry = entries.get(0);
				return;
			}
			entries.sort((OverloadMethodEntry m1Entry, OverloadMethodEntry m2Entry) -> {
				MethodTree m1 = m1Entry.methodTree;
				MethodTree m2 = m2Entry.methodTree;

				ExecutableElement m1Element = m1Entry.methodElement;
				ExecutableElement m2Element = m2Entry.methodElement;
				int i = m2.getParameters().size() - m1.getParameters().size();
				if (i == 0) {
					isValid = false;
					for (int j = 0; j < m1.getParameters().size(); j++) {

						TypeMirror m1ParamType = types
								.erasure(Util.getType(m1.getParameters().get(j)));
						TypeMirror m2ParamType = types
								.erasure(Util.getType(m2.getParameters().get(j)));
						if (types.isAssignable(m1ParamType, m2ParamType)) {
							i--;
						}
						if (types.isAssignable(m2ParamType, m1ParamType)) {
							i++;
						}

						if (i == 0) {
							boolean core1 = util.isCoreType(m1ParamType);
							if (util.isStringType(m1ParamType)) {
								core1 = false;
							}
							boolean core2 = util.isCoreType(m2ParamType);
							if (util.isStringType(m2ParamType)) {
								core2 = false;
							}
							if (!core1 && core2) {
								i--;
							}
							if (core1 && !core2) {
								i++;
							}
						}

						if (i == 0) {
							boolean abstract1 = m1.getModifiers().getFlags().contains(Modifier.ABSTRACT)
									|| (util.isInterface(m1Element.getEnclosingElement())
											&& !m1.getModifiers().getFlags().contains(Modifier.DEFAULT));
							boolean abstract2 = m2.getModifiers().getFlags().contains(Modifier.ABSTRACT)
									|| (util.isInterface(m2Element.getEnclosingElement())
											&& !m2.getModifiers().getFlags().contains(Modifier.DEFAULT));
							if (abstract1 && !abstract2) {
								i++;
							}
							if (!abstract1 && abstract2) {
								i--;
							}
						}
					}
				}

				// valid overloads can only be in the same class because of
				// potential side effects in subclasses
				if (m1Element.getEnclosingElement() != m2Element.getEnclosingElement()) {
					isValid = false;
				}
				return i;
			});
			coreEntry = entries.get(0);

			TypeMirror coreMethodType = coreEntry.methodType;
			coreMethodType = util.erasureRecursive(coreMethodType);
			for (OverloadMethodEntry currentEntry : new ArrayList<>(entries)) {
				if (currentEntry == coreEntry) {
					continue;
				}
				if (coreMethodType.toString().equals(currentEntry.methodType.toString())) {
					entries.remove(currentEntry);
				}
			}

			if (isValid) {
				defaultValues = new HashMap<>();
			}

			if (getMethodsCount() > 1 && isValid) {
				for (OverloadMethodEntry entry : entries) {
					MethodTree methodDecl = entry.methodTree;
					if (methodDecl.getBody() != null && methodDecl.getBody().getStatements().size() == 1) {
						if (entry != coreEntry) {
							List statements = methodDecl.getBody().getStatements();
							MethodInvocationTree invocation = null;
							StatementTree stat = statements.get(0);
							if (stat instanceof ReturnTree) {
								if (((ReturnTree) stat).getExpression() instanceof MethodInvocationTree) {
									invocation = (MethodInvocationTree) ((ReturnTree) stat).getExpression();
								}
							} else if (stat instanceof ExpressionStatementTree) {
								if (((ExpressionStatementTree) stat).getExpression() instanceof MethodInvocationTree) {
									invocation = (MethodInvocationTree) ((ExpressionStatementTree) stat)
											.getExpression();
								}
							}
							if (invocation == null) {
								isValid = false;
							} else {
								ExecutableElement methodElement = entry.methodElement;
								ExecutableElement invokedMethodElement = context.util.findMethodDeclarationInType(
										(TypeElement) methodElement.getEnclosingElement(), invocation);
								if (invokedMethodElement != null
										&& invokedMethodElement.getSimpleName().toString().equals(methodName)) {
									String inv = invocation.getMethodSelect().toString();
									if (!(inv.equals(methodName) || inv.equals("this." + methodName)
											|| /* constructor case */ inv.equals("this"))) {
										isValid = false;
									}
									if (isValid && invocation.getArguments() != null) {
										for (int i = 0; i < invocation.getArguments().size(); i++) {
											ExpressionTree expr = invocation.getArguments().get(i);
											if (context.util.isConstant(expr)) {
												defaultValues.put(i, expr);
											} else {
												if (!(expr instanceof IdentifierTree
														&& i < methodDecl.getParameters().size()
														&& methodDecl.getParameters().get(i).getName().toString()
																.equals(((IdentifierTree) expr).getName()
																		.toString()))) {
													isValid = false;
													break;
												}
											}
										}
									}
								} else {
									isValid = false;
								}
							}
						}
					} else {
						isValid = false;
					}
				}
			}
		}

		private boolean hasMethodType(OverloadMethodEntry searchedMethodEntry) {

			Element searchedMethodElement = searchedMethodEntry.methodElement;
			TypeMirror searchedMethodType = util.erasureRecursive(searchedMethodEntry.methodType);
			for (OverloadMethodEntry thisMethodEntry : getEntries()) {
				Element thisMethodElement = thisMethodEntry.methodElement;
				TypeMirror thisMethodType = util.erasureRecursive(thisMethodEntry.methodType);

				boolean match = thisMethodType.toString().equals(searchedMethodType.toString());
				if (match && thisMethodElement.getEnclosingElement() != searchedMethodElement.getEnclosingElement()) {
					this.isValid = false;
				}
				if (match) {
					return true;
				}
			}
			return false;
		}

		private void safeAdd(OverloadMethodEntry methodEntry) {
			if (!entries.contains(methodEntry) && !hasMethodType(methodEntry)) {
				entries.add(methodEntry);
			}
		}

		/**
		 * Merges the given overload with a subclass one.
		 */
		public void merge(Overload subOverload) {
			// merge default methods
			for (OverloadMethodEntry overloadMethodEntry : entries) {
				MethodTree overloadMethod = overloadMethodEntry.methodTree;
				if (overloadMethod.getModifiers().getFlags().contains(Modifier.DEFAULT)) {
					boolean overriden = false;

					for (OverloadMethodEntry subOverloadMethodEntry : subOverload.getEntries()) {
						MethodTree subOverloadMethod = subOverloadMethodEntry.methodTree;
						if (subOverloadMethod.getParameters().size() == overloadMethod.getParameters().size()) {
							overriden = true;
							for (int i = 0; i < subOverloadMethod.getParameters().size(); i++) {
								TypeMirror overloadParamType = Util.getType(
										overloadMethod.getParameters().get(i));
								TypeMirror subOverloadParamType = Util.getType(
										subOverloadMethod.getParameters().get(i));

								if (!types.isAssignable(overloadParamType, subOverloadParamType)) {
									overriden = false;
								}
							}
						}
					}
					if (!overriden) {
						subOverload.safeAdd(overloadMethodEntry);
					}
				}
			}
			// merge other methods
			boolean merge = false;
			for (OverloadMethodEntry subOverloadEntry : new ArrayList<>(subOverload.entries)) {
				MethodTree subOverloadMethod = subOverloadEntry.methodTree;
				boolean overrides = false;
				for (OverloadMethodEntry overloadEntry : new ArrayList<>(entries)) {
					MethodTree overloadMethod = overloadEntry.methodTree;
					if (subOverloadMethod.getParameters().size() == overloadMethod.getParameters().size()) {
						overrides = true;
						for (int i = 0; i < subOverloadMethod.getParameters().size(); i++) {
							TypeMirror overloadParamType = Util.getType(overloadMethod.getParameters().get(i));
							TypeMirror subOverloadParamType = Util.getType(
									subOverloadMethod.getParameters().get(i));
							if (!types.isAssignable(overloadParamType, subOverloadParamType)) {
								overrides = false;
							}
						}
					}
				}
				merge = merge || !overrides;
			}

			merge = merge || getMethodsCount() > 1;

			if (merge) {
				for (OverloadMethodEntry overloadEntry : entries) {
					subOverload.safeAdd(overloadEntry);
				}
			}
		}

		public Iterable getEntries() {
			return entries;
		}

		public ExecutableElement getCoreMethodElement() {
			return coreEntry.methodElement;
		}

		/**
		 * @see OverloadMethodEntry
		 */
		public void register( //
				CompilationUnitTree classCompilationUnit, //
				ClassTree classTree, //
				CompilationUnitTree methodCompilationUnit, //
				ClassTree owningClassTree, //
				MethodTree methodTree) {

			Element classElement = Util.getElement(classTree);
			ExecutableElement methodElement = Util.getElement(methodTree);
			safeAdd(new OverloadMethodEntry(classCompilationUnit, //
					classTree, //
					classElement, //
					methodCompilationUnit, //
					owningClassTree, //
					methodTree, //
					methodElement, //
					methodElement.asType()));
		}
	}

	/**
	 * Creates a new overload scanner.
	 */
	public OverloadScanner(TranspilationHandler logHandler, JSweetContext context) {
		super(logHandler, context, null);
	}

	private void inspectSuperTypes(CompilationUnitTree compilationUnit, TypeElement clazz, Overload overload,
			MethodTree method, ExecutableElement methodElement) {
		if (clazz == null) {
			return;
		}
		Overload superOverload = context.getOverload(clazz, methodElement);
		if (superOverload != null && superOverload != overload) {
			superOverload.merge(overload);
		}
		inspectSuperTypes(compilationUnit, (TypeElement) context.types.asElement(clazz.getSuperclass()), overload,
				method, methodElement);
		for (TypeMirror interfaceType : clazz.getInterfaces()) {
			inspectSuperTypes(compilationUnit, (TypeElement) context.types.asElement(interfaceType), overload, method,
					methodElement);
		}
	}

	@Override
	public Void visitClass(ClassTree classTree, Trees trees) {
		TypeElement classElement = Util.getElement(classTree);
		if (classElement.getQualifiedName().toString().startsWith(JSweetConfig.LIBS_PACKAGE + ".") || context
				.hasAnnotationType(classElement, JSweetConfig.ANNOTATION_ERASED, JSweetConfig.ANNOTATION_AMBIENT)) {
			return null;
		}

		for (Tree member : classTree.getMembers()) {
			if (member instanceof MethodTree) {
				processMethod(getCompilationUnit(), classTree, getCompilationUnit(), classTree, (MethodTree) member);
			}
		}

		HashSet defaultMethods = new HashSet<>();
		util().findDefaultMethodsInType(defaultMethods, context, classElement);
		for (DefaultMethodEntry defaultMethod : defaultMethods) {
			processMethod(getCompilationUnit(), classTree, defaultMethod.compilationUnit,
					defaultMethod.enclosingClassTree, defaultMethod.methodTree);
		}
		// scan all AST because of anonymous classes that may appear everywhere
		// (including in field initializers)
		return super.visitClass(classTree, trees);
	}

	private void processMethod( //
			CompilationUnitTree currentClassCompilationUnit, //
			ClassTree currentClassTree, //
			CompilationUnitTree methodCompilationUnit, //
			ClassTree owningClassTree, //
			MethodTree methodTree) {
		ExecutableElement methodElement = Util.getElement(methodTree);
		if (context.hasAnnotationType(methodElement, JSweetConfig.ANNOTATION_ERASED, JSweetConfig.ANNOTATION_AMBIENT)) {
			return;
		}
		TypeElement classElement = Util.getElement(currentClassTree);
		Overload overload = context.getOrCreateOverload(classElement, methodElement);
		if (pass == 1) {
			overload.register(currentClassCompilationUnit, currentClassTree, methodCompilationUnit, owningClassTree,
					methodTree);
		} else {
			if (methodElement.getKind() != ElementKind.CONSTRUCTOR) {
				inspectSuperTypes(compilationUnit, classElement, overload, methodTree, methodElement);
			}
		}
	}

	@Override
	public Void visitCompilationUnit(CompilationUnitTree cu, Trees trees) {
		setCompilationUnit(cu);
		super.visitCompilationUnit(cu, trees);
		setCompilationUnit(null);

		return null;
	}

	/**
	 * Processes all the overload of a given compilation unit list.
	 */
	public void process(List cuList) {
		for (CompilationUnitTree cu : cuList) {
			scan(cu, getContext().trees);
		}
		pass++;
		for (CompilationUnitTree cu : cuList) {
			scan(cu, getContext().trees);
		}
		for (Overload overload : context.getAllOverloads()) {
			overload.calculate();
			if (overload.getMethodsCount() > 1 && !overload.isValid) {
				if (overload.getCoreMethodElement().getKind() == ElementKind.CONSTRUCTOR) {
					context.classesWithWrongConstructorOverload
							.add((TypeElement) overload.getCoreMethodElement().getEnclosingElement());
				}
			}
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy