From 2aee09fb8b53072bd883087eaf7b6e926c0e363e Mon Sep 17 00:00:00 2001
From: Pavel Vojtechovsky
Date: Sat, 7 Apr 2018 22:52:52 +0200
Subject: [PATCH] support of printing of changes in list (e.g. type members)
---
.../spoon/reflect/visitor/TokenWriter.java | 7 +-
.../ChangesAwareDefaultJavaPrettyPrinter.java | 60 +++++--
.../printer/change/SourceFragment.java | 35 +++-
.../printer/change/SourceFragmentContext.java | 20 ++-
.../change/SourceFragmentContextList.java | 161 ++++++++++++++++++
.../change/SourceFragmentContextNormal.java | 40 +++--
.../printer/change/SourcePositionUtils.java | 16 +-
7 files changed, 298 insertions(+), 41 deletions(-)
create mode 100644 src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContextList.java
diff --git a/src/main/java/spoon/reflect/visitor/TokenWriter.java b/src/main/java/spoon/reflect/visitor/TokenWriter.java
index 67f08662d77..ec662850f1b 100644
--- a/src/main/java/spoon/reflect/visitor/TokenWriter.java
+++ b/src/main/java/spoon/reflect/visitor/TokenWriter.java
@@ -133,9 +133,8 @@ public interface TokenWriter {
*/
void reset();
- /** Writes a single space.
- *
- * Note that this method is only there for low-level implementation reasons. A default implementation simply calls {@link PrinterHelper#writeSpace()} ()}. This method will be removed in a later refactoring.
- * */
+ /**
+ * Writes a single space.
+ */
TokenWriter writeSpace();
}
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 1bd63951f33..34b2af9c258 100644
--- a/src/main/java/spoon/reflect/visitor/printer/change/ChangesAwareDefaultJavaPrettyPrinter.java
+++ b/src/main/java/spoon/reflect/visitor/printer/change/ChangesAwareDefaultJavaPrettyPrinter.java
@@ -26,6 +26,7 @@
import spoon.SpoonException;
import spoon.compiler.Environment;
import spoon.experimental.modelobs.ChangeCollector;
+import spoon.reflect.code.CtComment;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.path.CtRole;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
@@ -65,29 +66,51 @@ private TokenWriter createTokenWriterListener(TokenWriter tokenWriter) {
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 && paramTypes[0] == String.class) {
- onTokenWriterWrite(method.getName(), (String) args[0], () -> {
- 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 (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;
}
}
- Object result = method.invoke(tokenWriter, args);
- if (method.getReturnType() == TokenWriter.class) {
+ if (method.getName().endsWith("Tab")) {
+ //incTab(), decTab()
+ onTokenWriterWrite(method.getName(), (String) null, null, printAction);
return proxy;
}
- return result;
+ 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());
}
});
}
@@ -96,10 +119,11 @@ public Object invoke(Object proxy, Method method, Object[] args) throws Throwabl
* Is called for each printed token
* @param tokenWriterMethodName the name of {@link TokenWriter} method
* @param token the actual token value
+ * @param comment TODO
* @param printAction the executor of the action, we are listening for.
* @throws Exception
*/
- protected void onTokenWriterWrite(String tokenWriterMethodName, String token, Runnable printAction) {
+ protected void onTokenWriterWrite(String tokenWriterMethodName, String token, CtComment comment, Runnable printAction) {
SourceFragmentContext sfc = sourceFragmentContextStack.peek();
if (sfc != null) {
sfc.onTokenWriterToken(tokenWriterMethodName, token, printAction);
@@ -116,7 +140,7 @@ public ChangesAwareDefaultJavaPrettyPrinter scan(CtElement element) {
if (sfc != null) {
CtRole role = element.getRoleInParent();
if (role != null) {
- sfc.onScanRole(role, () -> scanInternal(element));
+ sfc.onScanElementOnRole(element, role, () -> scanInternal(element));
return this;
}
}
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 895203b70bc..3ee2451244e 100644
--- a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java
+++ b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragment.java
@@ -110,11 +110,20 @@ public SourcePosition getSourcePosition() {
@Override
public String toString() {
+ return getSourceCode(getStart(), getEnd());
+ }
+
+ /**
+ * @param start start offset
+ * @param end end offset (after last element)
+ * @return source code of this Fragment between start/end offsets
+ */
+ public String getSourceCode(int start, int end) {
CompilationUnit cu = sourcePosition.getCompilationUnit();
if (cu != null) {
String src = cu.getOriginalSourceCode();
if (src != null) {
- return src.substring(getStart(), getEnd());
+ return src.substring(start, end);
}
}
return null;
@@ -420,4 +429,28 @@ public SourceFragment getNextFragmentOfSameElement() {
}
return null;
}
+ /**
+ * @return source code of this fragment before the first child of this fragment.
+ * null if there is no child
+ */
+ public String getTextBeforeFirstChild() {
+ if (firstChild != null) {
+ return getSourceCode(getStart(), firstChild.getStart());
+ }
+ return null;
+ }
+ /**
+ * @return source code of this fragment after the last child of this fragment
+ * null if there is no child
+ */
+ public String getTextAfterLastChild() {
+ SourceFragment lastChild = firstChild;
+ while (lastChild != null) {
+ if (lastChild.getNextSibling() == null) {
+ return getSourceCode(lastChild.getEnd(), getEnd());
+ }
+ lastChild = lastChild.getNextSibling();
+ }
+ return null;
+ }
}
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 c9ce60e4147..d2a110e43c0 100644
--- a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContext.java
+++ b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContext.java
@@ -1,8 +1,26 @@
package spoon.reflect.visitor.printer.change;
+import spoon.reflect.declaration.CtElement;
import spoon.reflect.path.CtRole;
+import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
abstract class SourceFragmentContext {
+ /**
+ * Called when TokenWriter token is sent by {@link DefaultJavaPrettyPrinter}
+ * @param tokenWriterMethodName the name of token method
+ * @param token the value of token
+ * @param printAction the {@link Runnable}, which will send the token to the output
+ */
abstract void onTokenWriterToken(String tokenWriterMethodName, String token, Runnable printAction);
- abstract void onScanRole(CtRole role, PrintAction printAction);
+ /**
+ * Called when {@link DefaultJavaPrettyPrinter} starts scanning of `element` on the parent`s role `role`
+ * @param element to be scanned element
+ * @param role the attribute where the element is in parent
+ * @param printAction the {@link Runnable}, which will scan that element in {@link DefaultJavaPrettyPrinter}
+ */
+ abstract void onScanElementOnRole(CtElement element, CtRole role, Runnable printAction);
+ /**
+ * Called when this is child context and parent context is just going to finish it's printing
+ */
+ abstract void onParentFinished();
}
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContextList.java b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContextList.java
new file mode 100644
index 00000000000..aae7f1a88f5
--- /dev/null
+++ b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContextList.java
@@ -0,0 +1,161 @@
+package spoon.reflect.visitor.printer.change;
+
+import java.util.ArrayList;
+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
+ */
+class SourceFragmentContextList extends SourceFragmentContext {
+ private final ChangesAwareDefaultJavaPrettyPrinter printer;
+ private final SourceFragment rootFragment;
+ private final Map listElementToFragmentPair = 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) {
+ super();
+ printer = changesAwareDefaultJavaPrettyPrinter;
+ 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);
+ }
+ }
+ }
+ }
+
+ private FragmentPair createFragmentPair(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);
+ }
+ lastChild = child;
+ child = child.getNextSibling();
+ }
+ 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) {
+ //print list prefix
+ String prefix = rootFragment.getTextBeforeFirstChild();
+ if (prefix != null) {
+ //we have origin source code for that
+ printer.mutableTokenWriter.getPrinterHelper().directPrint(prefix);
+ //ignore all list prefix tokens
+ printer.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
+ printAction.run();
+ } else {
+ //print list separator
+ //collect (postpone) printAction of separators and list ending token, because we print them depending on next element / end of list
+ separatorActions.add(printAction);
+ }
+ }
+
+ @Override
+ 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);
+ elementIndex++;
+ if (elementIndex > 0) {
+ // print spaces between elements
+ FragmentPair fragmentPair = listElementToFragmentPair.get(element);
+ if (fragmentPair != null) {
+ //there is origin fragment for `element`
+ if (fragmentPair.prefixSpaces != null) {
+ //there are origin spaces before this `element`
+ //use them
+ printer.mutableTokenWriter.getPrinterHelper().directPrint(fragmentPair.prefixSpaces);
+ //forget DJPP spaces
+ separatorActions.clear();
+ }
+ /*
+ * else there are no origin spaces we have to let it print normally
+ * - e.g. when new first element is added, then second element has no standard spaces
+ */
+ }
+ } //else it is the first element of list. Do not print spaces here (we already have spaces after the list prefix)
+ printStandardSpaces();
+ //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);
+ }
+
+ @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);
+ //print list suffix
+ String suffix = rootFragment.getTextAfterLastChild();
+ if (suffix != null) {
+ //we have origin source code for that list suffix
+ printer.mutableTokenWriter.getPrinterHelper().directPrint(suffix);
+ separatorActions.clear();
+ } else {
+ //printer must print the spaces and suffix. Send collected events
+ printStandardSpaces();
+ }
+ }
+
+ private void printStandardSpaces() {
+ for (Runnable runnable : separatorActions) {
+ runnable.run();
+ }
+ separatorActions.clear();
+ }
+}
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 b5e38b032ed..84c04f019a9 100644
--- a/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContextNormal.java
+++ b/src/main/java/spoon/reflect/visitor/printer/change/SourceFragmentContextNormal.java
@@ -14,6 +14,10 @@ class SourceFragmentContextNormal extends SourceFragmentContext {
private final ChangesAwareDefaultJavaPrettyPrinter printer;
private SourceFragment currentFragment;
private CtElement element;
+ /**
+ * handles printing of lists of elements. E.g. type members of type.
+ */
+ private SourceFragmentContext childContext;
SourceFragmentContextNormal(ChangesAwareDefaultJavaPrettyPrinter changesAwareDefaultJavaPrettyPrinter, CtElement element, SourceFragment rootFragment) {
super();
@@ -52,15 +56,14 @@ void handlePrinting() {
printer.mutableTokenWriter.setMuted(true);
} else {
//we are printing modified fragment.
+ printer.mutableTokenWriter.setMuted(false);
switch (currentFragment.fragmentDescriptor.kind) {
case NORMAL:
//Let it print normally
- printer.mutableTokenWriter.setMuted(false);
break;
case LIST:
- //we are printing list
- //TODO
- printer.mutableTokenWriter.setMuted(false);
+ //we are printing list, create a child context for the list
+ childContext = new SourceFragmentContextList(printer, element, currentFragment);
break;
default:
throw new SpoonException("Unexpected fragment kind " + currentFragment.fragmentDescriptor.kind);
@@ -84,27 +87,34 @@ void onTokenWriterToken(String tokenWriterMethodName, String token, Runnable pri
//yes, the next fragment should be activated before printAction
nextFragment();
}
- //run the print action, which we are listening for
- printAction.run();
+ if (childContext != null) {
+ childContext.onTokenWriterToken(tokenWriterMethodName, token, printAction);
+ } else {
+ //run the print action, which we are listening for
+ printAction.run();
+ }
if (testFagmentDescriptor(currentFragment, fd -> fd.isTriggeredByToken(false, tokenWriterMethodName, token))) {
//yes, the next fragment should be activated before printAction
+ if (childContext != null) {
+ //notify childContext that it finished
+ childContext.onParentFinished();
+ childContext = null;
+ }
nextFragment();
}
}
@Override
- void onScanRole(CtRole role, PrintAction printAction) {
+ void onScanElementOnRole(CtElement element, CtRole role, Runnable printAction) {
if (testFagmentDescriptor(getNextFragment(), fd -> fd.isStartedByScanRole(role))) {
//yes, the next fragment should be activated before printAction
nextFragment();
}
//run the print action, which we are listening for
- try {
+ if (childContext != null) {
+ childContext.onScanElementOnRole(element, role, printAction);
+ } else {
printAction.run();
- } catch (SpoonException e) {
- throw (SpoonException) e;
- } catch (Exception e) {
- throw new SpoonException(e);
}
// {
// //check if current fragment has to be finished after this action
@@ -113,4 +123,10 @@ void onScanRole(CtRole role, PrintAction printAction) {
// }
// }
}
+
+ @Override
+ void onParentFinished() {
+ //we will see if it is true... I (Pavel) am not sure yet
+ throw new SpoonException("SourceFragmentContextNormal shouldn't be used as child context");
+ }
}
diff --git a/src/main/java/spoon/reflect/visitor/printer/change/SourcePositionUtils.java b/src/main/java/spoon/reflect/visitor/printer/change/SourcePositionUtils.java
index cbdab6aad04..641950e412e 100644
--- a/src/main/java/spoon/reflect/visitor/printer/change/SourcePositionUtils.java
+++ b/src/main/java/spoon/reflect/visitor/printer/change/SourcePositionUtils.java
@@ -140,11 +140,7 @@ enum FragmentKind {
/**
* a fragment, which contains a list of elements
*/
- LIST,
- /**
- * a fragment, which contains no model element, but contains separators or spaces.
- */
- SEPARATOR
+ LIST
}
/**
@@ -226,6 +222,16 @@ boolean isTriggeredByToken(boolean isStart, String tokenWriterMethodName, String
boolean isStartedByScanRole(CtRole role) {
return startScanRole.contains(role);
}
+
+ /**
+ * @return {@link CtRole} of the list attribute handled by this fragment
+ */
+ public CtRole getListRole() {
+ if (kind != FragmentKind.LIST || roles == null || roles.size() != 1) {
+ throw new SpoonException("This fragment does not have list role");
+ }
+ return roles.iterator().next();
+ }
}
/**