From 96f7b13812ac070074637abcb9582c1bd61d4361 Mon Sep 17 00:00:00 2001 From: Pavel Vojtechovsky Date: Wed, 11 Apr 2018 22:01:49 +0200 Subject: [PATCH] javadoc and some cleaning --- .../ChangesAwareDefaultJavaPrettyPrinter.java | 172 ++++++++++-------- .../printer/change/DirectPrinterHelper.java | 15 +- .../visitor/printer/change/FragmentType.java | 2 +- .../printer/change/MutableTokenWriter.java | 21 ++- .../visitor/printer/change/PrintAction.java | 24 --- .../printer/change/SourceFragment.java | 122 ++++++++++--- .../printer/change/SourceFragmentContext.java | 3 + .../change/SourceFragmentContextList.java | 91 ++++----- .../change/SourceFragmentContextNormal.java | 18 +- .../reflect/cu/CompilationUnitImpl.java | 4 +- .../test/position/SourceFragmentTest.java | 8 +- 11 files changed, 286 insertions(+), 194 deletions(-) delete mode 100644 src/main/java/spoon/reflect/visitor/printer/change/PrintAction.java diff --git a/src/main/java/spoon/reflect/visitor/printer/change/ChangesAwareDefaultJavaPrettyPrinter.java b/src/main/java/spoon/reflect/visitor/printer/change/ChangesAwareDefaultJavaPrettyPrinter.java index 34b2af9c258..2e5041a3b44 100644 --- a/src/main/java/spoon/reflect/visitor/printer/change/ChangesAwareDefaultJavaPrettyPrinter.java +++ b/src/main/java/spoon/reflect/visitor/printer/change/ChangesAwareDefaultJavaPrettyPrinter.java @@ -16,10 +16,6 @@ */ package spoon.reflect.visitor.printer.change; -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; import java.util.ArrayDeque; import java.util.Deque; @@ -30,17 +26,23 @@ import spoon.reflect.declaration.CtElement; import spoon.reflect.path.CtRole; import spoon.reflect.visitor.DefaultJavaPrettyPrinter; +import spoon.reflect.visitor.PrettyPrinter; +import spoon.reflect.visitor.PrinterHelper; import spoon.reflect.visitor.TokenWriter; /** - * SourcePositionUtils#descriptors + * {@link PrettyPrinter} implementation which copies as much as possible from origin sources + * and prints only changed elements */ public class ChangesAwareDefaultJavaPrettyPrinter extends DefaultJavaPrettyPrinter { - final MutableTokenWriter mutableTokenWriter; + private final MutableTokenWriter mutableTokenWriter; private final ChangeCollector changeCollector; private final Deque sourceFragmentContextStack = new ArrayDeque<>(); + /** + * Creates a new {@link PrettyPrinter} which copies origin sources and prints only changes. + */ public ChangesAwareDefaultJavaPrettyPrinter(Environment env) { super(env); this.changeCollector = ChangeCollector.getChangeCollector(env); @@ -60,70 +62,89 @@ public ChangesAwareDefaultJavaPrettyPrinter(Environment env) { * @return a wrapped {@link TokenWriter} */ private TokenWriter createTokenWriterListener(TokenWriter tokenWriter) { - return (TokenWriter) Proxy.newProxyInstance( - getClass().getClassLoader(), - new Class[] {TokenWriter.class}, - new InvocationHandler() { - @Override - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { - Runnable printAction = () -> { - try { - method.invoke(tokenWriter, args); - } catch (IllegalAccessException | IllegalArgumentException e) { - throw new SpoonException("Cannot invoke TokenWriter method", e); - } catch (InvocationTargetException e) { - if (e.getTargetException() instanceof RuntimeException) { - throw (RuntimeException) e.getTargetException(); - } - throw new SpoonException("Invokation target exception TokenWriter method", e); - } - }; - if (method.getName().startsWith("write")) { - Class[] paramTypes = method.getParameterTypes(); - if (paramTypes.length == 1) { - if (paramTypes[0] == String.class) { - //writeXxx(String) - onTokenWriterWrite(method.getName(), (String) args[0], null, printAction); - } else if (paramTypes[0] == CtComment.class) { - //writeComment(CtComment) - onTokenWriterWrite(method.getName(), null, (CtComment) args[0], printAction); - } - return proxy; - } else if (paramTypes.length == 0) { - //writeSpace() and writeln() - String token = method.getName().equals("writeSpace") ? " " : "\n"; - onTokenWriterWrite(method.getName(), token, null, printAction); - return proxy; - } - } - if (method.getName().endsWith("Tab")) { - //incTab(), decTab() - onTokenWriterWrite(method.getName(), (String) null, null, printAction); - return proxy; - } - if (method.getName().equals("getPrinterHelper") || method.getName().equals("reset")) { - try { - return method.invoke(tokenWriter, args); - } catch (IllegalAccessException | IllegalArgumentException e) { - throw new SpoonException("Cannot invoke TokenWriter method", e); - } catch (InvocationTargetException e) { - throw e.getTargetException(); - } - } - throw new SpoonException("Unexpected method TokenWriter#" + method.getName()); - } - }); + return new TokenWriterProxy(tokenWriter); + } + + private class TokenWriterProxy implements TokenWriter { + private final TokenWriter delegate; + + TokenWriterProxy(TokenWriter delegate) { + super(); + this.delegate = delegate; + } + + public TokenWriter writeSeparator(String token) { + onTokenWriterWrite("writeSeparator", token, null, () -> delegate.writeSeparator(token)); + return this; + } + + public TokenWriter writeOperator(String token) { + onTokenWriterWrite("writeOperator", token, null, () -> delegate.writeOperator(token)); + return this; + } + + public TokenWriter writeLiteral(String token) { + onTokenWriterWrite("writeLiteral", token, null, () -> delegate.writeLiteral(token)); + return this; + } + + public TokenWriter writeKeyword(String token) { + onTokenWriterWrite("writeKeyword", token, null, () -> delegate.writeKeyword(token)); + return this; + } + + public TokenWriter writeIdentifier(String token) { + onTokenWriterWrite("writeIdentifier", token, null, () -> delegate.writeIdentifier(token)); + return this; + } + + public TokenWriter writeCodeSnippet(String token) { + onTokenWriterWrite("writeCodeSnippet", token, null, () -> delegate.writeCodeSnippet(token)); + return this; + } + + public TokenWriter writeComment(CtComment comment) { + onTokenWriterWrite("writeComment", null, comment, () -> delegate.writeComment(comment)); + return this; + } + + public TokenWriter writeln() { + onTokenWriterWrite("writeln", "\n", null, () -> delegate.writeln()); + return this; + } + + public TokenWriter incTab() { + onTokenWriterWrite("incTab", null, null, () -> delegate.incTab()); + return this; + } + + public TokenWriter decTab() { + onTokenWriterWrite("decTab", null, null, () -> delegate.decTab()); + return this; + } + + public PrinterHelper getPrinterHelper() { + return delegate.getPrinterHelper(); + } + + public void reset() { + delegate.reset(); + } + + public TokenWriter writeSpace() { + onTokenWriterWrite("writeSpace", " ", null, () -> delegate.writeSpace()); + return this; + } } /** * Is called for each printed token * @param tokenWriterMethodName the name of {@link TokenWriter} method - * @param token the actual token value - * @param comment TODO + * @param token the actual token value. It may be null for some `tokenWriterMethodName` + * @param comment the comment when `tokenWriterMethodName` == `writeComment` * @param printAction the executor of the action, we are listening for. - * @throws Exception */ - protected void onTokenWriterWrite(String tokenWriterMethodName, String token, CtComment comment, Runnable printAction) { + private void onTokenWriterWrite(String tokenWriterMethodName, String token, CtComment comment, Runnable printAction) { SourceFragmentContext sfc = sourceFragmentContextStack.peek(); if (sfc != null) { sfc.onTokenWriterToken(tokenWriterMethodName, token, printAction); @@ -132,14 +153,18 @@ protected void onTokenWriterWrite(String tokenWriterMethodName, String token, Ct printAction.run(); } - private final SourceFragmentContextNormal EMPTY_FRAGMENT_CONTEXT = new SourceFragmentContextNormal(this); + private final SourceFragmentContextNormal EMPTY_FRAGMENT_CONTEXT = new SourceFragmentContextNormal(); + /** + * Called whenever {@link DefaultJavaPrettyPrinter} scans/prints an element + */ @Override public ChangesAwareDefaultJavaPrettyPrinter scan(CtElement element) { SourceFragmentContext sfc = sourceFragmentContextStack.peek(); if (sfc != null) { CtRole role = element.getRoleInParent(); if (role != null) { + //there is an context in the child element, let it handle scanning sfc.onScanElementOnRole(element, role, () -> scanInternal(element)); return this; } @@ -161,20 +186,19 @@ private void scanInternal(CtElement element) { //detect SourceFragments of element and whether they are modified or not SourceFragment rootFragmentOfElement = SourcePositionUtils.getSourceFragmentsOfElement(changeCollector, element); if (rootFragmentOfElement == null) { - //we have no origin sources. Use normal printing + //we have no origin sources or this element has one source fragment only and it is modified. Use normal printing sourceFragmentContextStack.push(EMPTY_FRAGMENT_CONTEXT); super.scan(element); sourceFragmentContextStack.pop(); return; } - try { - SourceFragmentContextNormal sfx = new SourceFragmentContextNormal(this, element, rootFragmentOfElement); - sourceFragmentContextStack.push(sfx); - super.scan(element); - } finally { - //at the end we always un-mute the token writer - mutableTokenWriter.setMuted(false); - sourceFragmentContextStack.pop(); - } + //the element is modified and it consists of more source fragments, and some of them are not modified + //so we can copy the origin sources for them + SourceFragmentContextNormal sfx = new SourceFragmentContextNormal(mutableTokenWriter, element, rootFragmentOfElement); + sourceFragmentContextStack.push(sfx); + super.scan(element); + sourceFragmentContextStack.pop(); + //at the end we always un-mute the token writer + mutableTokenWriter.setMuted(false); } } diff --git a/src/main/java/spoon/reflect/visitor/printer/change/DirectPrinterHelper.java b/src/main/java/spoon/reflect/visitor/printer/change/DirectPrinterHelper.java index d31089e2dcf..21ecd852fd3 100644 --- a/src/main/java/spoon/reflect/visitor/printer/change/DirectPrinterHelper.java +++ b/src/main/java/spoon/reflect/visitor/printer/change/DirectPrinterHelper.java @@ -29,15 +29,20 @@ class DirectPrinterHelper extends PrinterHelper { } /** - * Prints `str` directly into output buffer ignoring any Environment rules. - * @param str to be printed string + * Prints `text` directly into output buffer ignoring any Environment rules. + * @param text to be printed string */ - void directPrint(String str) { + void directPrint(String text) { autoWriteTabs(); - sbf.append(str); + sbf.append(text); } - public void setShouldWriteTabs(boolean shouldWriteTabs) { + /** + * Allows to set the protected field of {@link PrinterHelper}. + * + * @param shouldWriteTabs true if we just printed EndOfLine and we should pring tabs if next character is not another EndOfLine + */ + void setShouldWriteTabs(boolean shouldWriteTabs) { this.shouldWriteTabs = shouldWriteTabs; } } diff --git a/src/main/java/spoon/reflect/visitor/printer/change/FragmentType.java b/src/main/java/spoon/reflect/visitor/printer/change/FragmentType.java index 702a7c1baad..e790913b9c9 100644 --- a/src/main/java/spoon/reflect/visitor/printer/change/FragmentType.java +++ b/src/main/java/spoon/reflect/visitor/printer/change/FragmentType.java @@ -27,7 +27,7 @@ public enum FragmentType { */ MAIN_FRAGMENT, /** - * modifiers of an Type or Variable. {@link DeclarationSourcePosition#getModifierSourceStart()}, {@link DeclarationSourcePosition#getModifierSourceEnd()} + * modifiers and annotations of an Type, Executable or Variable. {@link DeclarationSourcePosition#getModifierSourceStart()}, {@link DeclarationSourcePosition#getModifierSourceEnd()} */ MODIFIERS, /** diff --git a/src/main/java/spoon/reflect/visitor/printer/change/MutableTokenWriter.java b/src/main/java/spoon/reflect/visitor/printer/change/MutableTokenWriter.java index 9f1d3fc4727..47540825454 100644 --- a/src/main/java/spoon/reflect/visitor/printer/change/MutableTokenWriter.java +++ b/src/main/java/spoon/reflect/visitor/printer/change/MutableTokenWriter.java @@ -32,17 +32,24 @@ class MutableTokenWriter implements TokenWriter { MutableTokenWriter(Environment env) { super(); - this.delegate = new DefaultTokenWriter(new DirectPrinterHelper(env));; + this.delegate = new DefaultTokenWriter(new DirectPrinterHelper(env)); } + /** + * @return true if tokens are ignored. false if they are forwarded to `delegate` + */ boolean isMuted() { return muted; } + /** + * @param muted true if tokens are ignored. false if they are forwarded to `delegate` + */ void setMuted(boolean muted) { this.muted = muted; } + @Override public TokenWriter writeSeparator(String token) { if (isMuted()) { getPrinterHelper().setShouldWriteTabs(false); @@ -51,6 +58,7 @@ public TokenWriter writeSeparator(String token) { delegate.writeSeparator(token); return this; } + @Override public TokenWriter writeOperator(String token) { if (isMuted()) { getPrinterHelper().setShouldWriteTabs(false); @@ -59,6 +67,7 @@ public TokenWriter writeOperator(String token) { delegate.writeOperator(token); return this; } + @Override public TokenWriter writeLiteral(String token) { if (isMuted()) { getPrinterHelper().setShouldWriteTabs(false); @@ -67,6 +76,7 @@ public TokenWriter writeLiteral(String token) { delegate.writeLiteral(token); return this; } + @Override public TokenWriter writeKeyword(String token) { if (isMuted()) { getPrinterHelper().setShouldWriteTabs(false); @@ -75,6 +85,7 @@ public TokenWriter writeKeyword(String token) { delegate.writeKeyword(token); return this; } + @Override public TokenWriter writeIdentifier(String token) { if (isMuted()) { getPrinterHelper().setShouldWriteTabs(false); @@ -83,6 +94,7 @@ public TokenWriter writeIdentifier(String token) { delegate.writeIdentifier(token); return this; } + @Override public TokenWriter writeCodeSnippet(String token) { if (isMuted()) { getPrinterHelper().setShouldWriteTabs(false); @@ -91,6 +103,7 @@ public TokenWriter writeCodeSnippet(String token) { delegate.writeCodeSnippet(token); return this; } + @Override public TokenWriter writeComment(CtComment comment) { if (isMuted()) { getPrinterHelper().setShouldWriteTabs(false); @@ -99,6 +112,7 @@ public TokenWriter writeComment(CtComment comment) { delegate.writeComment(comment); return this; } + @Override public TokenWriter writeln() { if (isMuted()) { getPrinterHelper().setShouldWriteTabs(true); @@ -107,6 +121,7 @@ public TokenWriter writeln() { delegate.writeln(); return this; } + @Override public TokenWriter incTab() { if (isMuted()) { return this; @@ -114,6 +129,7 @@ public TokenWriter incTab() { delegate.incTab(); return this; } + @Override public TokenWriter decTab() { if (isMuted()) { return this; @@ -121,15 +137,18 @@ public TokenWriter decTab() { delegate.decTab(); return this; } + @Override public DirectPrinterHelper getPrinterHelper() { return (DirectPrinterHelper) delegate.getPrinterHelper(); } + @Override public void reset() { if (isMuted()) { return; } delegate.reset(); } + @Override public TokenWriter writeSpace() { if (isMuted()) { getPrinterHelper().setShouldWriteTabs(false); diff --git a/src/main/java/spoon/reflect/visitor/printer/change/PrintAction.java b/src/main/java/spoon/reflect/visitor/printer/change/PrintAction.java deleted file mode 100644 index 7513854cd20..00000000000 --- a/src/main/java/spoon/reflect/visitor/printer/change/PrintAction.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (C) 2006-2017 INRIA and contributors - * Spoon - http://spoon.gforge.inria.fr/ - * - * This software is governed by the CeCILL-C License under French law and - * abiding by the rules of distribution of free software. You can use, modify - * and/or redistribute the software under the terms of the CeCILL-C license as - * circulated by CEA, CNRS and INRIA at http://www.cecill.info. - * - * 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 CeCILL-C License for more details. - * - * The fact that you are presently reading this means that you have had - * knowledge of the CeCILL-C license and that you accept its terms. - */ -package spoon.reflect.visitor.printer.change; - -/** - * Represents type of {@link SourceFragment} - */ -interface PrintAction { - void run(); -} diff --git a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java index 3ee2451244e..e6f0abf126a 100644 --- a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java +++ b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java @@ -20,10 +20,12 @@ import java.util.Deque; import spoon.SpoonException; +import spoon.reflect.code.CtComment; import spoon.reflect.cu.CompilationUnit; import spoon.reflect.cu.SourcePosition; import spoon.reflect.cu.position.BodyHolderSourcePosition; import spoon.reflect.cu.position.DeclarationSourcePosition; +import spoon.reflect.declaration.CtClass; import spoon.reflect.declaration.CtElement; import spoon.reflect.visitor.CtScanner; import spoon.reflect.visitor.printer.change.SourcePositionUtils.FragmentDescriptor; @@ -31,6 +33,9 @@ /** * Represents a part of source code of an {@link CtElement} + * It is connected into a tree of {@link SourceFragment}s. + * That tree can be build by {@link CompilationUnit#getRootSourceFragment()} + * And the tree of {@link SourceFragment}s related to one element can be returned by {@link CompilationUnit#getSourceFragment(CtElement)} */ public class SourceFragment { @@ -42,10 +47,28 @@ public class SourceFragment { private SourceFragment nextSibling; private SourceFragment firstChild; - public SourceFragment(CtElement element, SourcePosition sourcePosition) { - this(element, sourcePosition, FragmentType.MAIN_FRAGMENT); + /** + * Creates a main fragment of {@link CtElement} + * Note: it automatically creates child fragments if `sourcePosition` + * is instance of {@link DeclarationSourcePosition} or {@link BodyHolderSourcePosition} + * + * @param element target {@link CtElement} + */ + public SourceFragment(CtElement element) { + this(element, element.getPosition(), FragmentType.MAIN_FRAGMENT); + } + /** + * creates a main fragment for {@link SourcePosition} + * Note: it automatically creates child fragments if `sourcePosition` + * is instance of {@link DeclarationSourcePosition} or {@link BodyHolderSourcePosition} + * + * @param sourcePosition target {@link SourcePosition} + */ + public SourceFragment(SourcePosition sourcePosition) { + this(null, sourcePosition, FragmentType.MAIN_FRAGMENT); } - public SourceFragment(CtElement element, SourcePosition sourcePosition, FragmentType fragmentType) { + + private SourceFragment(CtElement element, SourcePosition sourcePosition, FragmentType fragmentType) { super(); this.element = element; this.sourcePosition = sourcePosition; @@ -55,12 +78,15 @@ public SourceFragment(CtElement element, SourcePosition sourcePosition, Fragment } } + /** + * @return type of fragment + */ public FragmentType getFragmentType() { return fragmentType; } /** - * @return offset of first character which belongs to this fragmen + * @return offset of first character which belongs to this fragment */ public int getStart() { switch (fragmentType) { @@ -81,7 +107,7 @@ public int getStart() { } /** - * @return offset of first next character after this Fragment + * @return offset of character after this fragment */ public int getEnd() { switch (fragmentType) { @@ -104,18 +130,28 @@ public int getEnd() { throw new SpoonException("Unsupported fragment type: " + fragmentType); } + /** + * @return {@link SourcePosition} of this fragment + */ public SourcePosition getSourcePosition() { return sourcePosition; } @Override public String toString() { + return "|" + getStart() + ", " + getEnd() + "|" + getSourceCode() + "|"; + } + + /** + * @return origin source code of this fragment + */ + public String getSourceCode() { return getSourceCode(getStart(), getEnd()); } /** - * @param start start offset - * @param end end offset (after last element) + * @param start start offset relative to compilation unit + * @param end end offset (after last character) relative to compilation unit * @return source code of this Fragment between start/end offsets */ public String getSourceCode(int start, int end) { @@ -129,10 +165,16 @@ public String getSourceCode(int start, int end) { return null; } + /** + * @return true if the attribute of {@link CtElement} whose source code is in this fragment is modified + */ public boolean isModified() { return modified; } + /** + * @param modified true if the attribute of {@link CtElement} whose source code is in this fragment is modified + */ public void setModified(boolean modified) { this.modified = modified; } @@ -145,13 +187,14 @@ private boolean isFromSameSource(SourcePosition position) { } /** - * Builds tree of {@link SourcePosition} elements + * Builds tree of {@link SourcePosition}s of `element` and all it's children * @param element the root element of the tree */ - public void addSourceFragments(CtElement element) { + public void addTreeOfSourceFragmentsOfElement(CtElement element) { SourcePosition sp = element.getPosition(); Deque parents = new ArrayDeque<>(); parents.push(this); + //scan all children of `element` and build tree of SourceFragments new CtScanner() { int noSource = 0; @Override @@ -173,20 +216,43 @@ protected void exit(CtElement e) { } }.scan(element); } - private static SourceFragment addChild(SourceFragment thisFragment, CtElement otherElement) { + /** + * @param parentFragment the parent {@link SourceFragment}, which will receive {@link SourceFragment} made for `otherElement` + * @param otherElement {@link CtElement} whose {@link SourceFragment} has to be added to `parentFragment` + * @return + */ + private SourceFragment addChild(SourceFragment parentFragment, CtElement otherElement) { SourcePosition otherSourcePosition = otherElement.getPosition(); if (otherSourcePosition instanceof SourcePositionImpl && otherSourcePosition.getCompilationUnit() != null) { SourcePositionImpl childSPI = (SourcePositionImpl) otherSourcePosition; - if (thisFragment.sourcePosition != childSPI) { - if (thisFragment.isFromSameSource(otherSourcePosition)) { - SourceFragment otherFragment = new SourceFragment(otherElement, otherSourcePosition, FragmentType.MAIN_FRAGMENT); + if (parentFragment.sourcePosition != childSPI) { + if (parentFragment.isFromSameSource(otherSourcePosition)) { + SourceFragment otherFragment = new SourceFragment(otherElement); //parent and child are from the same file. So we can connect their positions into one tree - CMP cmp = thisFragment.compare(otherFragment); + CMP cmp = parentFragment.compare(otherFragment); if (cmp == CMP.OTHER_IS_CHILD) { //child belongs under parent - OK - thisFragment.addChild(otherFragment); + parentFragment.addChild(otherFragment); + return otherFragment; } else { + if (cmp == CMP.OTHER_IS_AFTER || cmp == CMP.OTHER_IS_BEFORE) { + if (otherElement instanceof CtComment) { + /* + * comments of elements are sometime not included in source position of element. + * because comments are ignored tokens for java compiler, which computes start/end of elements + * Example: + * + * //a comment + * aStatement(); + * + * No problem. Simply add comment at correct position into SourceFragment tree, starting from root + */ + addChild(otherFragment); + return otherFragment; + } + } //the source position of child element is not included in source position of parent element + //I (Pavel) am not sure how to handle it, so let's wait until it happens... // if (otherElement instanceof CtAnnotation) { // /* // * it can happen for annotations of type TYPE_USE and FIELD @@ -196,11 +262,10 @@ private static SourceFragment addChild(SourceFragment thisFragment, CtElement ot // return null; // } //something is wrong ... - SourcePosition.class.getClass(); - throw new SpoonException("TODO"); + throw new SpoonException("The SourcePosition of elements are not consistent\nparentFragment: " + parentFragment + "\notherFragment: " + otherFragment); } } else { - throw new SpoonException("SourcePosition from unexpected compilation unit: " + otherSourcePosition + " expected is: " + thisFragment.sourcePosition); + throw new SpoonException("SourcePosition from unexpected compilation unit: " + otherSourcePosition + " expected is: " + parentFragment.sourcePosition); } } //else these two elements has same instance of SourcePosition. @@ -213,6 +278,7 @@ private static SourceFragment addChild(SourceFragment thisFragment, CtElement ot /** * adds `other` {@link SourceFragment} into tree of {@link SourceFragment}s represented by this root element + * * @param other to be added {@link SourceFragment} * @return new root of the tree of the {@link SourceFragment}s. It can be be this or `other` */ @@ -319,6 +385,9 @@ private CMP compare(SourceFragment other) { throw new SpoonException("Cannot compare this: [" + getStart() + ", " + getEnd() + "] with other: [\"" + other.getStart() + "\", \"" + other.getEnd() + "\"]"); } + /** + * creates child fragments for {@link DeclarationSourcePosition} and {@link BodyHolderSourcePosition} + */ private void createChildFragments() { if (sourcePosition instanceof DeclarationSourcePosition) { DeclarationSourcePosition dsp = (DeclarationSourcePosition) sourcePosition; @@ -348,9 +417,17 @@ private void createChildFragments() { } } } + + /** + * @return {@link SourceFragment} which belongs to the same parent and is next in the sources + */ public SourceFragment getNextSibling() { return nextSibling; } + + /** + * @return {@link SourceFragment}, which is first child of this fragment + */ public SourceFragment getFirstChild() { return firstChild; } @@ -361,7 +438,7 @@ public SourceFragment getFirstChild() { * * @param start the start offset of this fragment * @param end the offset of next character after the end of this fragment - * @return SourceFragment which represents the root of the CtElement whose sources are in interval [start, end] + * @return {@link SourceFragment} which represents the root of the CtElement whose sources are in interval [start, end] */ public SourceFragment getSourceFragmentOf(int start, int end) { int myEnd = getEnd(); @@ -400,7 +477,8 @@ private SourceFragment getRootFragmentOfElement(SourceFragment childFragment) { return childFragment; } /** - * @return {@link CtElement} whose source code is contained in this fragment + * @return {@link CtElement} whose source code is contained in this fragment. + * May be null */ public CtElement getElement() { return element; @@ -408,7 +486,9 @@ public CtElement getElement() { /** * @return direct child {@link SourceFragment} if it has same element like this. - * It means that this {@link SourceFragment} knows the source parts of this element + * It means that this {@link SourceFragment} knows the source parts of this element. + * E.g. {@link SourceFragment} of {@link CtClass} has fragments for modifiers, name, body, etc. + * So in this case it returns first child fragment, which are modifiers fragment of {@link CtClass}. * Else it returns null. */ public SourceFragment getChildFragmentOfSameElement() { diff --git a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContext.java b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContext.java index d2a110e43c0..c375a5c87f8 100644 --- a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContext.java +++ b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContext.java @@ -4,6 +4,9 @@ import spoon.reflect.path.CtRole; import spoon.reflect.visitor.DefaultJavaPrettyPrinter; +/** + * Knows how to handle actually printed {@link CtElement} or it's part + */ abstract class SourceFragmentContext { /** * Called when TokenWriter token is sent by {@link DefaultJavaPrettyPrinter} diff --git a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContextList.java b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContextList.java index aae7f1a88f5..10442f4d886 100644 --- a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContextList.java +++ b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContextList.java @@ -4,67 +4,57 @@ import java.util.IdentityHashMap; import java.util.List; import java.util.Map; -import java.util.function.Predicate; import spoon.reflect.declaration.CtElement; import spoon.reflect.meta.impl.RoleHandlerHelper; import spoon.reflect.path.CtRole; -import spoon.reflect.visitor.printer.change.SourcePositionUtils.FragmentDescriptor; /** - * Handles printing of changes of the list of elements + * Handles printing of changes of the list of elements. + * E.g. list of type members of type */ class SourceFragmentContextList extends SourceFragmentContext { - private final ChangesAwareDefaultJavaPrettyPrinter printer; + private final MutableTokenWriter mutableTokenWriter; private final SourceFragment rootFragment; - private final Map listElementToFragmentPair = new IdentityHashMap<>(); + private final Map listElementToPrefixSpace = new IdentityHashMap<>(); private final List separatorActions = new ArrayList<>(); //-1 means print start list separator //0..n print elements of list private int elementIndex = -1; - private static class FragmentPair { - /** - * optional spaces before the element (e.g. spaces before the method or field) - */ - String prefixSpaces; - /** - * the fragment of element - */ - SourceFragment elementFragment; - - FragmentPair(String prefixSpaces, SourceFragment elementFragment) { - super(); - this.prefixSpaces = prefixSpaces; - this.elementFragment = elementFragment; - } - } - - SourceFragmentContextList(ChangesAwareDefaultJavaPrettyPrinter changesAwareDefaultJavaPrettyPrinter, CtElement element, SourceFragment rootFragment) { + /** + * @param mutableTokenWriter {@link MutableTokenWriter}, which is used for printing + * @param element the {@link CtElement} whose list attribute is handled + * @param rootFragment the {@link SourceFragment}, which represents whole list of elements. E.g. body of method or all type members of type + */ + SourceFragmentContextList(MutableTokenWriter mutableTokenWriter, CtElement element, SourceFragment rootFragment) { super(); - printer = changesAwareDefaultJavaPrettyPrinter; + this.mutableTokenWriter = mutableTokenWriter; this.rootFragment = rootFragment; CtRole listRole = rootFragment.fragmentDescriptor.getListRole(); List listElements = RoleHandlerHelper.getRoleHandler(element.getClass(), listRole).asList(element); for (CtElement ctElement : listElements) { SourceFragment elementFragment = SourcePositionUtils.getSourceFragmentOfElement(ctElement); if (elementFragment != null) { - FragmentPair fragmentPair = createFragmentPair(rootFragment, elementFragment); - if (fragmentPair != null) { - listElementToFragmentPair.put(ctElement, fragmentPair); + String prefixSpace = getPrefixSpace(rootFragment, elementFragment); + if (prefixSpace != null) { + listElementToPrefixSpace.put(ctElement, prefixSpace); } } } } - private FragmentPair createFragmentPair(SourceFragment rootFragment, SourceFragment elementFragment) { + /** + * @param rootFragment + * @param elementFragment + * @return the spaces, new lines and separators located in the list of elements before the `elementFragment` + */ + private String getPrefixSpace(SourceFragment rootFragment, SourceFragment elementFragment) { SourceFragment child = rootFragment.getFirstChild(); SourceFragment lastChild = null; while (child != null) { if (child == elementFragment) { - return new FragmentPair( - lastChild == null ? null : rootFragment.getSourceCode(lastChild.getEnd(), elementFragment.getStart()), - elementFragment); + return lastChild == null ? null : rootFragment.getSourceCode(lastChild.getEnd(), elementFragment.getStart()); } lastChild = child; child = child.getNextSibling(); @@ -72,30 +62,21 @@ private FragmentPair createFragmentPair(SourceFragment rootFragment, SourceFragm return null; } - boolean testFagmentDescriptor(SourceFragment sourceFragment, Predicate predicate) { - if (sourceFragment != null) { - if (sourceFragment.fragmentDescriptor != null) { - return predicate.test(sourceFragment.fragmentDescriptor); - } - } - return false; - } - @Override void onTokenWriterToken(String tokenWriterMethodName, String token, Runnable printAction) { if (elementIndex == -1) { - if (printer.mutableTokenWriter.isMuted() == false) { + if (mutableTokenWriter.isMuted() == false) { //print list prefix String prefix = rootFragment.getTextBeforeFirstChild(); if (prefix != null) { //we have origin source code for that - printer.mutableTokenWriter.getPrinterHelper().directPrint(prefix); + mutableTokenWriter.getPrinterHelper().directPrint(prefix); //ignore all list prefix tokens - printer.mutableTokenWriter.setMuted(true); + mutableTokenWriter.setMuted(true); } } //print the list prefix actions. It can be more token writer events ... - //so enter it several times. Note: it may be muted, then these tokens are ignored + //so enter it several times, by several calls of onTokenWriterToken by caller. Note: it may be muted, then these tokens are ignored printAction.run(); } else { //print list separator @@ -109,17 +90,17 @@ void onScanElementOnRole(CtElement element, CtRole role, Runnable printAction) { //the printing of child element must not be muted here //it can be muted later internally, if element is not modified //but something in list is modified and we do not know what - printer.mutableTokenWriter.setMuted(false); + mutableTokenWriter.setMuted(false); elementIndex++; if (elementIndex > 0) { // print spaces between elements - FragmentPair fragmentPair = listElementToFragmentPair.get(element); - if (fragmentPair != null) { + String prefixSpaces = listElementToPrefixSpace.get(element); + if (prefixSpaces != null) { //there is origin fragment for `element` - if (fragmentPair.prefixSpaces != null) { + if (prefixSpaces != null) { //there are origin spaces before this `element` //use them - printer.mutableTokenWriter.getPrinterHelper().directPrint(fragmentPair.prefixSpaces); + mutableTokenWriter.getPrinterHelper().directPrint(prefixSpaces); //forget DJPP spaces separatorActions.clear(); } @@ -133,18 +114,18 @@ void onScanElementOnRole(CtElement element, CtRole role, Runnable printAction) { //run the DJPP scanning action, which we are listening for printAction.run(); //the child element is printed, now it will print separators or list end - printer.mutableTokenWriter.setMuted(true); + mutableTokenWriter.setMuted(true); } @Override void onParentFinished() { - //we are the end of the list of elements. Printer just tries to print list suffix and parent fragment detected that - printer.mutableTokenWriter.setMuted(false); + //we are at the end of the list of elements. Printer just tries to print list suffix and parent fragment detected that + mutableTokenWriter.setMuted(false); //print list suffix String suffix = rootFragment.getTextAfterLastChild(); if (suffix != null) { //we have origin source code for that list suffix - printer.mutableTokenWriter.getPrinterHelper().directPrint(suffix); + mutableTokenWriter.getPrinterHelper().directPrint(suffix); separatorActions.clear(); } else { //printer must print the spaces and suffix. Send collected events @@ -152,6 +133,10 @@ void onParentFinished() { } } + /** + * print all tokens, which represents separator of items or suffix of last item + * and then forget them, so we can collect next tokens. + */ private void printStandardSpaces() { for (Runnable runnable : separatorActions) { runnable.run(); diff --git a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContextNormal.java b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContextNormal.java index 84c04f019a9..9588bfe4f47 100644 --- a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContextNormal.java +++ b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContextNormal.java @@ -11,7 +11,7 @@ class SourceFragmentContextNormal extends SourceFragmentContext { /** * */ - private final ChangesAwareDefaultJavaPrettyPrinter printer; + private final MutableTokenWriter mutableTokenWriter; private SourceFragment currentFragment; private CtElement element; /** @@ -19,16 +19,16 @@ class SourceFragmentContextNormal extends SourceFragmentContext { */ private SourceFragmentContext childContext; - SourceFragmentContextNormal(ChangesAwareDefaultJavaPrettyPrinter changesAwareDefaultJavaPrettyPrinter, CtElement element, SourceFragment rootFragment) { + SourceFragmentContextNormal(MutableTokenWriter mutableTokenWriter, CtElement element, SourceFragment rootFragment) { super(); - printer = changesAwareDefaultJavaPrettyPrinter; + this.mutableTokenWriter = mutableTokenWriter; this.element = element; this.currentFragment = rootFragment; handlePrinting(); } - SourceFragmentContextNormal(ChangesAwareDefaultJavaPrettyPrinter changesAwareDefaultJavaPrettyPrinter) { - printer = changesAwareDefaultJavaPrettyPrinter; + SourceFragmentContextNormal() { + mutableTokenWriter = null; currentFragment = null; } @@ -52,18 +52,18 @@ void handlePrinting() { if (currentFragment.isModified() == false) { //we are going to print not modified fragment //print origin sources of this fragment directly - printer.mutableTokenWriter.getPrinterHelper().directPrint(currentFragment.toString()); - printer.mutableTokenWriter.setMuted(true); + mutableTokenWriter.getPrinterHelper().directPrint(currentFragment.getSourceCode()); + mutableTokenWriter.setMuted(true); } else { //we are printing modified fragment. - printer.mutableTokenWriter.setMuted(false); + mutableTokenWriter.setMuted(false); switch (currentFragment.fragmentDescriptor.kind) { case NORMAL: //Let it print normally break; case LIST: //we are printing list, create a child context for the list - childContext = new SourceFragmentContextList(printer, element, currentFragment); + childContext = new SourceFragmentContextList(mutableTokenWriter, element, currentFragment); break; default: throw new SpoonException("Unexpected fragment kind " + currentFragment.fragmentDescriptor.kind); diff --git a/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java b/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java index e0b44d627f8..deec05cc606 100644 --- a/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java +++ b/src/main/java/spoon/support/reflect/cu/CompilationUnitImpl.java @@ -281,9 +281,9 @@ public void setAutoImport(boolean autoImport) { public SourceFragment getRootSourceFragment() { if (rootFragment == null) { String originSourceCode = getOriginalSourceCode(); - rootFragment = new SourceFragment(null, new SourcePositionImpl(this, 0, originSourceCode.length() - 1, getLineSeparatorPositions())); + rootFragment = new SourceFragment(new SourcePositionImpl(this, 0, originSourceCode.length() - 1, getLineSeparatorPositions())); for (CtType ctType : declaredTypes) { - rootFragment.addSourceFragments(ctType); + rootFragment.addTreeOfSourceFragmentsOfElement(ctType); } } return rootFragment; diff --git a/src/test/java/spoon/test/position/SourceFragmentTest.java b/src/test/java/spoon/test/position/SourceFragmentTest.java index 057c30e477a..b32f6652e2d 100644 --- a/src/test/java/spoon/test/position/SourceFragmentTest.java +++ b/src/test/java/spoon/test/position/SourceFragmentTest.java @@ -19,7 +19,7 @@ public class SourceFragmentTest { @Test public void testSourcePositionFragment() throws Exception { SourcePosition sp = new SourcePositionImpl(null, 10, 20, null); - SourceFragment sf = new SourceFragment(null, sp); + SourceFragment sf = new SourceFragment(sp); assertEquals(10, sf.getStart()); assertEquals(21, sf.getEnd()); assertSame(sp, sf.getSourcePosition()); @@ -30,7 +30,7 @@ public void testSourcePositionFragment() throws Exception { @Test public void testDeclarationSourcePositionFragment() throws Exception { SourcePosition sp = new DeclarationSourcePositionImpl(null, 100, 110, 90, 95, 90, 130, null); - SourceFragment sf = new SourceFragment(null, sp); + SourceFragment sf = new SourceFragment(sp); assertEquals(90, sf.getStart()); assertEquals(131, sf.getEnd()); assertSame(sp, sf.getSourcePosition()); @@ -63,7 +63,7 @@ public void testDeclarationSourcePositionFragment() throws Exception { @Test public void testBodyHolderSourcePositionFragment() throws Exception { SourcePosition sp = new BodyHolderSourcePositionImpl(null, 100, 110, 90, 95, 90, 130, 120, 130, null); - SourceFragment sf = new SourceFragment(null, sp); + SourceFragment sf = new SourceFragment(sp); assertEquals(90, sf.getStart()); assertEquals(131, sf.getEnd()); assertSame(sp, sf.getSourcePosition()); @@ -158,6 +158,6 @@ public void testLocalizationOfSourceFragment() throws Exception { private SourceFragment createFragment(int start, int end) { - return new SourceFragment(null, new SourcePositionImpl(null, start, end - 1, null)); + return new SourceFragment(new SourcePositionImpl(null, start, end - 1, null)); } }