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

spoon.reflect.visitor.ElementPrinterHelper Maven / Gradle / Ivy

Go to download

Spoon is a tool for meta-programming, analysis and transformation of Java programs.

There is a newer version: 11.1.1-beta-14
Show newest version
/*
 * SPDX-License-Identifier: (MIT OR CECILL-C)
 *
 * Copyright (C) 2006-2023 INRIA and contributors
 *
 * Spoon is available either under the terms of the MIT License (see LICENSE-MIT.txt) or the Cecill-C License (see LICENSE-CECILL-C.txt). You as the user are entitled to choose the terms under which to adopt Spoon.
 */
package spoon.reflect.visitor;

import spoon.compiler.Environment;
import spoon.experimental.CtUnresolvedImport;
import spoon.reflect.code.CtBlock;
import spoon.reflect.code.CtComment;
import spoon.reflect.code.CtFor;
import spoon.reflect.code.CtForEach;
import spoon.reflect.code.CtIf;
import spoon.reflect.code.CtStatement;
import spoon.reflect.code.CtTypeAccess;
import spoon.reflect.declaration.CtAnnotation;
import spoon.reflect.declaration.CtCompilationUnit;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtFormalTypeDeclarer;
import spoon.reflect.declaration.CtImport;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtModifiable;
import spoon.reflect.declaration.CtNamedElement;
import spoon.reflect.declaration.CtSealable;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.CtTypeMember;
import spoon.reflect.declaration.CtTypeParameter;
import spoon.reflect.declaration.CtTypedElement;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtActualTypeContainer;
import spoon.reflect.reference.CtExecutableReference;
import spoon.reflect.reference.CtFieldReference;
import spoon.reflect.reference.CtPackageReference;
import spoon.reflect.reference.CtTypeMemberWildcardImportReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.PrintingContext.Writable;
import spoon.reflect.visitor.printer.CommentOffset;
import spoon.support.reflect.CtExtendedModifier;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.function.Consumer;

public class ElementPrinterHelper {
	private final DefaultJavaPrettyPrinter prettyPrinter;
	private final Environment env;
	private TokenWriter printer;

	public ElementPrinterHelper(TokenWriter printerTokenWriter, DefaultJavaPrettyPrinter prettyPrinter, Environment env) {
		this.printer = printerTokenWriter;
		this.prettyPrinter = prettyPrinter;
		this.env = env;
	}

	/**
	 * Writes the annotations for the given element.
	 */
	public void writeAnnotations(CtElement element) {
		for (CtAnnotation annotation : element.getAnnotations()) {

			// if element is a type reference and the parent is a typed element
			// which contains exactly the same annotation, then we are certainly in this case:
			// @myAnnotation String myField
			// in which case the annotation is attached to the type and the variable
			// in that case, we only print the annotation once.
			if (element.isParentInitialized() && element instanceof CtTypeReference && (element.getParent() instanceof CtTypedElement) && element.getParent().getAnnotations().contains(annotation)) {
					continue;
			}

			prettyPrinter.scan(annotation);
			printer.writeln();
		}
	}

	/**  writes the modifiers of this modifiable in a specific order */
	public void writeModifiers(CtModifiable modifiable) {
		// write the modifiers according to the convention on order
		List firstPosition = new ArrayList<>(); // visibility: public, private, protected
		List secondPosition = new ArrayList<>(); // keywords: static, abstract
		List thirdPosition = new ArrayList<>(); // all other things

		for (CtExtendedModifier extendedModifier : modifiable.getExtendedModifiers()) {
			if (!extendedModifier.isImplicit()) {
				ModifierKind modifierKind = extendedModifier.getKind();
				if (modifierKind == ModifierKind.PUBLIC || modifierKind == ModifierKind.PRIVATE || modifierKind == ModifierKind.PROTECTED) {
					firstPosition.add(modifierKind.toString());
				} else if (modifierKind == ModifierKind.ABSTRACT || modifierKind == ModifierKind.STATIC) {
					secondPosition.add(modifierKind.toString());
				} else {
					thirdPosition.add(modifierKind.toString());
				}
			}
		}

		for (String s : firstPosition) {
			printer.writeKeyword(s).writeSpace();
		}

		for (String s : secondPosition) {
			printer.writeKeyword(s).writeSpace();
		}

		for (String s : thirdPosition) {
			printer.writeKeyword(s).writeSpace();
		}

		if (modifiable instanceof CtMethod) {
			CtMethod m = (CtMethod) modifiable;
			if (m.isDefaultMethod()) {
				printer.writeKeyword("default").writeSpace();
			}
		}
	}

	public void visitCtNamedElement(CtNamedElement namedElement, CtCompilationUnit sourceCompilationUnit) {
		writeAnnotations(namedElement);
		if (env.isPreserveLineNumbers()) {
			getPrinterHelper().adjustStartPosition(namedElement);
		}
	}

	public void writeExtendsClause(CtType type) {
		if (type.getSuperclass() != null) {
			printer.writeSpace().writeKeyword("extends").writeSpace();
			prettyPrinter.scan(type.getSuperclass());
		}
	}

	/** writes the implemented interfaces with a ListPrinter */
	public void writeImplementsClause(CtType type) {
		if (!type.getSuperInterfaces().isEmpty()) {
			printList(type.getSuperInterfaces(), "implements",
				false, null, false, true, ",", true, false, null,
				ref -> prettyPrinter.scan(ref));
		}
	}

	/**
	 * Writes the executable parameters of the given executable. This includes the receiver parameter if it is present.
	 * For example, for a method `void foo(int a, int b)`, this method will write `(int a, int b)`.
	 * @param executable The executable to write the parameters for. This can be a method, constructor, or lambda.
	 */
	public void writeExecutableParameters(CtExecutable executable) {
		List parameters = new ArrayList<>();
		if (executable.getReceiverParameter() != null) {
			parameters.add(executable.getReceiverParameter());
		}
		parameters.addAll(executable.getParameters());
		printList(parameters, null,
			false, "(", false, false, ",", true, false, ")",
			prettyPrinter::scan);
	}

	/** writes the thrown exception with a ListPrinter */
	public void writeThrowsClause(CtExecutable executable) {
		if (!executable.getThrownTypes().isEmpty()) {
			printList(executable.getThrownTypes(), "throws",
				false, null, false, false, ",", true, false, null,
				ref -> prettyPrinter.scan(ref));
		}
	}

	/**
	 * Writes a statement.
	 */
	public void writeStatement(CtStatement statement) {
		try (Writable _context = prettyPrinter.getContext().modify().setStatement(statement)) {
			prettyPrinter.scan(statement);
		}
	}

	/**
	 * Writes a list of elements to the printer by using `scan` from the internal pretty-printer.
	 * @param elements List of elements to be written
	 */
	public void writeElementList(List elements) {
		for (CtTypeMember element : elements) {
			if (!element.isImplicit()) {
				printer.writeln();
				prettyPrinter.scan(element);
				if (!env.isPreserveLineNumbers()) {
					printer.writeln();
				}
			}
		}
	}

	/**
	 * Writes an annotation element.
	 */
	public void writeAnnotationElement(Factory factory, Object value) {
		if (value instanceof CtTypeAccess) {
			prettyPrinter.scan((CtTypeAccess) value);
			printer.writeSeparator(".").writeKeyword("class");
		} else if (value instanceof CtFieldReference) {
			prettyPrinter.scan(((CtFieldReference) value).getDeclaringType());
			printer.writeSeparator(".").writeIdentifier(((CtFieldReference) value).getSimpleName());
		} else if (value instanceof CtElement) {
			prettyPrinter.scan((CtElement) value);
		} else if (value instanceof String) {
			printer.writeLiteral("\"" + LiteralHelper.getStringLiteral((String) value, true) + "\"");
		} else if (value instanceof Collection) {
			printList((Collection) value, null,
				false, "{", false, true, ",", false, false, "}",
				obj -> writeAnnotationElement(factory, obj));
		} else if (value instanceof Object[]) {
			printList(Arrays.asList((Object[]) value), null,
				false, "{", false, true, ",", false, false, "}",
				obj -> writeAnnotationElement(factory, obj));
		} else if (value instanceof Enum) {
			try (Writable c = prettyPrinter.getContext().modify().ignoreGenerics(true)) {
				prettyPrinter.scan(factory.Type().createReference(((Enum) value).getDeclaringClass()));
			}
			printer.writeSeparator(".");
			printer.writeIdentifier(value.toString());
		} else {
			//it probably prints, boolean, number, ...
			printer.writeLiteral(value.toString());
		}
	}

	/**
	 * Writes formal type parameters given in parameter.
	 *
	 * @param ctFormalTypeDeclarer
	 * 		Reference with formal type arguments.
	 */
	public void writeFormalTypeParameters(CtFormalTypeDeclarer ctFormalTypeDeclarer) {
		final Collection parameters = ctFormalTypeDeclarer.getFormalCtTypeParameters();
		if (parameters == null) {
			return;
		}
		if (!parameters.isEmpty()) {
			printList(parameters,
				null,	false, "<", false, false, ",", true, false, ">",
				parameter -> prettyPrinter.scan(parameter));
		}
	}

	/**
	 * Writes actual type arguments in a {@link CtActualTypeContainer} element.
	 * Passes {@link PrintTypeArguments#ONLY_PRINT_EXPLICIT_TYPES}.
	 *
	 * @param ctGenericElementReference Reference with actual type arguments.
	 * @see #writeActualTypeArguments(CtActualTypeContainer, PrintTypeArguments)
	 * @deprecated use {@link #writeActualTypeArguments(CtActualTypeContainer, PrintTypeArguments)}. This method is
	 * only kept for backwards compatibility.
	 */
	@Deprecated
	public void writeActualTypeArguments(CtActualTypeContainer ctGenericElementReference) {
		writeActualTypeArguments(ctGenericElementReference, PrintTypeArguments.ONLY_PRINT_EXPLICIT_TYPES);
	}

	/**
	 * Writes actual type arguments in a {@link CtActualTypeContainer} element.
	 *
	 * @param ctGenericElementReference Reference with actual type arguments.
	 * @param handleImplicit Whether to print type arguments if they are all implicit
	 */
	public void writeActualTypeArguments(
		CtActualTypeContainer ctGenericElementReference,
		PrintTypeArguments handleImplicit
	) {
		Collection> arguments = ctGenericElementReference.getActualTypeArguments();
		if (arguments == null || arguments.isEmpty()) {
			return;
		}

		boolean allImplicit = arguments.stream().allMatch(CtElement::isImplicit);
		if (allImplicit && handleImplicit == PrintTypeArguments.ONLY_PRINT_EXPLICIT_TYPES) {
			return;
		}

		printList(arguments.stream().filter(a -> !a.isImplicit())::iterator,
			null, false, "<", false, false, ",", true, false, ">",
			argument -> {
				if (prettyPrinter.getContext().forceWildcardGenerics()) {
					printer.writeSeparator("?");
				} else {
					prettyPrinter.scan(argument);
				}
			});
	}

	private boolean isJavaLangClasses(String importType) {
		return importType.matches("^(java\\.lang\\.)[^.]*$");
	}

	/** writes the imports in a specific order (eg all static imports together */
	public void writeImports(Collection imports) {
		Set setImports = new HashSet<>();
		Set setStaticImports = new HashSet<>();
		for (CtImport ctImport : imports) {
			String importTypeStr;
			switch (ctImport.getImportKind()) {
				case TYPE:
					CtTypeReference typeRef = (CtTypeReference) ctImport.getReference();
					importTypeStr = typeRef.getQualifiedName();
					if (!isJavaLangClasses(importTypeStr)) {
						setImports.add(this.removeInnerTypeSeparator(importTypeStr));
					}
					break;

				case ALL_TYPES:
					CtPackageReference packageRef = (CtPackageReference) ctImport.getReference();
					importTypeStr = packageRef.getQualifiedName() + ".*";
					if (!isJavaLangClasses(importTypeStr)) {
						setImports.add(this.removeInnerTypeSeparator(importTypeStr));
					}
					break;

				case METHOD:
					CtExecutableReference execRef = (CtExecutableReference) ctImport.getReference();
					if (execRef.getDeclaringType() != null) {
						setStaticImports.add(this.removeInnerTypeSeparator(execRef.getDeclaringType().getQualifiedName()) + "." + execRef.getSimpleName());
					}
					break;

				case FIELD:
					CtFieldReference fieldRef = (CtFieldReference) ctImport.getReference();
					setStaticImports.add(this.removeInnerTypeSeparator(fieldRef.getDeclaringType().getQualifiedName()) + "." + fieldRef.getSimpleName());
					break;

				case ALL_STATIC_MEMBERS:
					CtTypeMemberWildcardImportReference typeStarRef = (CtTypeMemberWildcardImportReference) ctImport.getReference();
					importTypeStr = typeStarRef.getTypeReference().getQualifiedName();
					if (!isJavaLangClasses(importTypeStr)) {
						setStaticImports.add(this.removeInnerTypeSeparator(importTypeStr) + ".*");
					}
					break;

				case UNRESOLVED:
					CtUnresolvedImport unresolvedImport = (CtUnresolvedImport) ctImport;
					importTypeStr = unresolvedImport.getUnresolvedReference();
					if (!isJavaLangClasses(importTypeStr)) {
						if (unresolvedImport.isStatic()) {
							setStaticImports.add(importTypeStr);
						} else {
							setImports.add(importTypeStr);
						}
					}
					break;
			}
		}

		List sortedImports = new ArrayList<>(setImports);
		Collections.sort(sortedImports);
		boolean isFirst = true;
		for (String importLine : sortedImports) {
			if (isFirst) {
				printer.writeln();
				printer.writeln();
				isFirst = false;
			}
			printer.writeKeyword("import").writeSpace();
			writeQualifiedName(importLine).writeSeparator(";").writeln();
		}
		if (!setStaticImports.isEmpty()) {
			if (isFirst) {
				printer.writeln();
			}
			printer.writeln();
			List sortedStaticImports = new ArrayList<>(setStaticImports);
			Collections.sort(sortedStaticImports);
			for (String importLine : sortedStaticImports) {
				printer.writeKeyword("import").writeSpace().writeKeyword("static").writeSpace();
				writeQualifiedName(importLine).writeSeparator(";").writeln();
			}
		}
	}

	/**
	 * Write a package statement and a newline.
	 */
	public void writePackageLine(String packageQualifiedName) {
		writePackageStatement(packageQualifiedName);
		printer.writeln();
	}

	/**
	 * Write a package statement.
	 */
	public void writePackageStatement(String packageQualifiedName) {
		printer.writeKeyword("package").writeSpace();
		writeQualifiedName(packageQualifiedName).writeSeparator(";");
	}

	private String removeInnerTypeSeparator(String fqn) {
		return fqn.replace(CtType.INNERTTYPE_SEPARATOR, ".");
	}

	public void writeComment(CtComment comment) {
		if (!env.isCommentsEnabled() || comment == null) {
			return;
		}
		prettyPrinter.scan(comment);
		printer.writeln();
	}

	private void writeComment(List comments) {
		if (!env.isCommentsEnabled() || comments == null) {
			return;
		}
		for (CtComment comment : comments) {
			writeComment(comment);
		}
	}

	public void writeComment(CtElement element) {
		if (element == null) {
			return;
		}
		writeComment(element.getComments());
	}

	public void writeComment(CtElement element, CommentOffset offset) {
		writeComment(getComments(element, offset));
	}

	public List getComments(CtElement element, CommentOffset offset) {
		List commentsToPrint = new ArrayList<>();
		if (!env.isCommentsEnabled() || element == null) {
			return commentsToPrint;
		}
		for (CtComment comment : element.getComments()) {
			if (comment.getCommentType() == CtComment.CommentType.FILE && offset == CommentOffset.TOP_FILE && element.getPosition().getSourceEnd() > comment.getPosition().getSourceStart()) {
				commentsToPrint.add(comment);
				continue;
			}
			if (comment.getCommentType() == CtComment.CommentType.FILE && offset == CommentOffset.BOTTOM_FILE && element.getPosition().getSourceEnd() < comment.getPosition().getSourceStart()) {
				commentsToPrint.add(comment);
				continue;
			}
			if (comment.getCommentType() == CtComment.CommentType.FILE) {
				continue;
			}
			if (!comment.getPosition().isValidPosition() || !element.getPosition().isValidPosition()) {
				if (offset == CommentOffset.BEFORE) {
					commentsToPrint.add(comment);
				}
				continue;
			}
			final int line = element.getPosition().getLine();
			final int sourceEnd = element.getPosition().getSourceEnd();
			final int sourceStart = element.getPosition().getSourceStart();
			if (offset == CommentOffset.BEFORE && (comment.getPosition().getLine() < line || (sourceStart <= comment.getPosition().getSourceStart() && sourceEnd > comment.getPosition().getSourceEnd()))) {
				commentsToPrint.add(comment);
			} else if (offset == CommentOffset.AFTER && (comment.getPosition().getSourceStart() > sourceEnd || comment.getPosition().getSourceEnd() == sourceEnd)) {
				commentsToPrint.add(comment);
			} else {
				final int endLine = element.getPosition().getEndLine();
				if (offset == CommentOffset.INSIDE && comment.getPosition().getLine() >= line && comment.getPosition().getEndLine() <= endLine) {
					commentsToPrint.add(comment);
				}
			}
		}
		return commentsToPrint;
	}

	public boolean isElseIf(CtIf ifStmt) {
		if (ifStmt.getElseStatement() == null) {
			return false;
		}
		if (ifStmt.getElseStatement() instanceof CtIf)  {
			return true;
		}
		if (ifStmt.getElseStatement() instanceof CtBlock) {
			CtBlock block = (CtBlock) ifStmt.getElseStatement();
			return ((block.getStatements().size() == 1) && (block.getStatement(0) instanceof CtIf));
		}
		return false;
	}

	/** write all non-implicit parts of a block, with special care for indentation */
	public void writeIfOrLoopBlock(CtStatement block) {
		if (block != null) {
			if (!block.isImplicit() && (block instanceof CtBlock || block instanceof CtIf)) {
				printer.writeSpace();
			}
			if (!(block instanceof CtBlock) && !(block instanceof CtIf)) {
				printer.incTab();
				printer.writeln();
			}
			writeStatement(block);
			if (!(block instanceof CtBlock) && !(block instanceof CtIf)) {
				printer.decTab().writeln();
			}
			if (!block.isImplicit()) {
				if (!block.isParentInitialized() || (!(block.getParent() instanceof CtFor) && !(block.getParent() instanceof CtForEach) && !(block.getParent() instanceof CtIf))) {
					printer.writeSpace();
				}
			}
		} else {
			printer.writeSeparator(";");
		}
	}
	/**
	 * Creates new handler which assures consistent printing of lists
	 * prefixed with `start`, separated by `next` and suffixed by `end`
	 * @param startPrefixSpace if true then `start` token is prefixed with space
	 * @param start the string which has to be printed at the beginning of the list
	 * @param startSufficSpace if true then `start` token is suffixed with space
	 * @param nextPrefixSpace if true then `next` token is prefixed with space
	 * @param next the string which has to be used as separator before each next item
	 * @param nextSuffixSpace if true then `next` token is suffixed with space
	 * @param endPrefixSpace if true then `end` token is prefixed with space
	 * @param end the string which has to be printed after the list
	 * @return the {@link ListPrinter} whose {@link ListPrinter#printSeparatorIfAppropriate()} has to be called
	 * before printing of each item.
	 */
	private ListPrinter createListPrinter(boolean startPrefixSpace, String start, boolean startSufficSpace, boolean nextPrefixSpace, String next, boolean nextSuffixSpace, boolean endPrefixSpace, String end) {
		return new ListPrinter(printer, startPrefixSpace, start, startSufficSpace, nextPrefixSpace, next, nextSuffixSpace, endPrefixSpace, end);
	}

	private static final String QUALIFIED_NAME_SEPARATORS = ".$";

	/**
	 * splits qualified name to primitive tokens and sends them to TokenWriter individually
	 * @param qualifiedName to be sent qualified name
	 */
	public TokenWriter writeQualifiedName(String qualifiedName) {
		StringTokenizer st = new StringTokenizer(qualifiedName, QUALIFIED_NAME_SEPARATORS, true);
		while (st.hasMoreTokens()) {
			String token = st.nextToken();
			if (token.length() == 1 && QUALIFIED_NAME_SEPARATORS.indexOf(token.charAt(0)) >= 0) {
				printer.writeSeparator(token);
			} else {
				printer.writeIdentifier(token);
			}
		}
		return printer;
	}

	private PrinterHelper getPrinterHelper() {
		return printer.getPrinterHelper();
	}

	/**
	 * Prints list of elements with defined delimiters using `printer`
	 * @param iterable the iterable of to be printed elements
	 * @param startKeyword the optional start keyword. It is always printed if the value is not null
	 * @param startPrefixSpace if true then `start` token is prefixed with space
	 * @param start the string which has to be printed at the beginning of the list
	 * @param startSuffixSpace if true then `start` token is suffixed with space
	 * @param nextPrefixSpace if true then `next` token is prefixed with space
	 * @param next the string which has to be used as separator before each next item
	 * @param nextSuffixSpace if true then `next` token is suffixed with space
	 * @param endPrefixSpace if true then `end` token is prefixed with space
	 * @param end the string which has to be printed after the list
	 * @param elementPrinter the {@link Consumer}, which is called once for each printer element of the `iterable`
	 */
	public  void printList(Iterable iterable,
			String startKeyword,
			boolean startPrefixSpace, String start, boolean startSuffixSpace,
			boolean nextPrefixSpace, String next, boolean nextSuffixSpace,
			boolean endPrefixSpace, String end,
			Consumer elementPrinter) {

		if (startKeyword != null) {
			printer.writeSpace().writeKeyword(startKeyword).writeSpace();
		}
		try (spoon.reflect.visitor.ListPrinter lp = createListPrinter(
				startPrefixSpace, start, startSuffixSpace,
				nextPrefixSpace, next, nextSuffixSpace,
				endPrefixSpace, end
			)) {
			for (T item : iterable) {
				lp.printSeparatorIfAppropriate();
				elementPrinter.accept(item);
			}
		}
	}

	/**
	 * Prints the {@code permits} keyword followed by the permitted
	 * types of a {@link CtSealable}.
	 * 

* If the given sealed type does not have any * explicit permitted types, nothing is printed. * * @param sealable the sealed type to print the permitted types for. */ protected void printPermits(CtSealable sealable) { if (sealable.getPermittedTypes().isEmpty() || sealable.getPermittedTypes().stream().allMatch(CtElement::isImplicit)) { return; } printer.writeln().incTab().writeKeyword("permits").writeSpace(); printList(sealable.getPermittedTypes(), null, false, null, false, false, ",", true, false, null, prettyPrinter::scan); printer.decTab(); } /** * Whether to print generic types for references. This affects e.g. explicit type arguments for constructor * or method calls. * * A diamond operator is only valid in some places. This enum controls whether they can and should be printed at * a given location. */ public enum PrintTypeArguments { /** * Only print explicit type argument. Implicit (i.e. inferred types) are not printed. Consequently, this will * also not print a diamond operator. */ ONLY_PRINT_EXPLICIT_TYPES, /** * Print explicit type arguments, but also print a diamond operator if implicit type arguments were used. */ ALSO_PRINT_DIAMOND_OPERATOR } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy