Skip to content

Commit

Permalink
support of printing of changes in list (e.g. type members)
Browse files Browse the repository at this point in the history
  • Loading branch information
pvojtechovsky committed Apr 7, 2018
1 parent d083f4e commit 2aee09f
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 41 deletions.
7 changes: 3 additions & 4 deletions src/main/java/spoon/reflect/visitor/TokenWriter.java
Expand Up @@ -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();
}
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}
});
}
Expand All @@ -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);
Expand All @@ -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;
}
}
Expand Down
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
}
@@ -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();
}
@@ -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<CtElement, FragmentPair> listElementToFragmentPair = new IdentityHashMap<>();
private final List<Runnable> 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<CtElement> 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<FragmentDescriptor> 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();
}
}

0 comments on commit 2aee09f

Please sign in to comment.