Skip to content

Commit

Permalink
refactor(SniperPrettyPrinter): introduce ModificationStatus for bette…
Browse files Browse the repository at this point in the history
…r readability (#3396)
  • Loading branch information
monperrus authored Jun 8, 2020
1 parent df73729 commit e39950e
Show file tree
Hide file tree
Showing 10 changed files with 111 additions and 190 deletions.
153 changes: 57 additions & 96 deletions src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,14 @@
import spoon.support.sniper.internal.CollectionSourceFragment;
import spoon.support.sniper.internal.ElementPrinterEvent;
import spoon.support.sniper.internal.ElementSourceFragment;
import spoon.support.sniper.internal.ModificationStatus;
import spoon.support.sniper.internal.MutableTokenWriter;
import spoon.support.sniper.internal.PrinterEvent;
import spoon.support.sniper.internal.SourceFragment;
import spoon.support.sniper.internal.SourceFragmentPrinter;
import spoon.support.sniper.internal.SourceFragmentContextList;
import spoon.support.sniper.internal.SourceFragmentContextNormal;
import spoon.support.sniper.internal.DefaultSourceFragmentPrinter;
import spoon.support.sniper.internal.SourceFragmentContextSet;
import spoon.support.sniper.internal.TokenPrinterEvent;
import spoon.support.sniper.internal.TokenType;
import spoon.support.sniper.internal.TokenWriterProxy;
Expand Down Expand Up @@ -158,18 +158,14 @@ private String detectLineSeparator(String text) {
public void onTokenWriterWrite(TokenType tokenType, String token, CtComment comment, Runnable printAction) {
executePrintEventInContext(new TokenPrinterEvent(tokenType, token, comment) {
@Override
public void print() {
printAction.run();
}
@Override
public void printSourceFragment(SourceFragment fragment, Boolean isModified) {
if (isModified == null || isModified) {
public void printSourceFragment(SourceFragment fragment, ModificationStatus isModified) {
if (isModified == ModificationStatus.UNKNOWN || isModified == ModificationStatus.MODIFIED) {
printAction.run();
return;
} else {
if (fragment instanceof CollectionSourceFragment) {
//we started scanning of collection of elements
SourceFragmentPrinter listContext = getCollectionContext(null, (CollectionSourceFragment) fragment, isModified);
SourceFragmentPrinter listContext = getCollectionContext(null, (CollectionSourceFragment) fragment, isModified.toBoolean());
// we need to update the cursor (childFragmentIdx) with the current token
listContext.update(this);
//push the context of this collection
Expand Down Expand Up @@ -219,15 +215,14 @@ public String printElementSniper(CtElement element) {
//use line separator of origin source file
setLineSeparator(detectLineSeparator(compilationUnit.getOriginalSourceCode()));

CtRole role = getRoleInCompilationUnit(element);
ElementSourceFragment esf = element.getOriginalSourceFragment();

runInContext(
new SourceFragmentContextList(mutableTokenWriter,
element,
Collections.singletonList(esf),
new ChangeResolver(getChangeCollector(), element)),
() -> executePrintEventInContext(createPrinterEvent(element, role))
() -> executePrintEventInContext(createPrinterEvent(element))
);
}
}
Expand All @@ -243,22 +238,64 @@ public String printElementSniper(CtElement element) {
@Override
public SniperJavaPrettyPrinter scan(CtElement element) {
if (element != null) {
CtRole role = getRoleInCompilationUnit(element);
executePrintEventInContext(createPrinterEvent(element, role));
executePrintEventInContext(createPrinterEvent(element));
}
return this;
}

private PrinterEvent createPrinterEvent(CtElement element, CtRole role) {
private PrinterEvent createPrinterEvent(CtElement element) {
CtRole role = getRoleInCompilationUnit(element);
return new ElementPrinterEvent(role, element) {
@Override
public void print() {
superScanInContext(element, DefaultSourceFragmentPrinter.INSTANCE);
}

@Override
public void printSourceFragment(SourceFragment fragment, Boolean isModified) {
scanInternal(role, element, fragment, isModified);
public void printSourceFragment(SourceFragment fragment, ModificationStatus isModified) {
if (mutableTokenWriter.isMuted()) {
throw new SpoonException("Unexpected state of sniper pretty printer. TokenWriter is muted.");
}


// we don't have any source fragment for this element, so we simply pretty-print it normally
if (fragment == null) {
superScanInContext(this.element, DefaultSourceFragmentPrinter.INSTANCE);
return;
} else if (fragment instanceof CollectionSourceFragment) {
//we started scanning of collection of elements
SourceFragmentPrinter listContext = getCollectionContext(this.element, (CollectionSourceFragment) fragment, isModified.toBoolean());
//push the context of this collection
pushContext(listContext);


//and scan first element of that collection again in new context of that collection
if (ModificationStatus.NOT_MODIFIED.equals(isModified)) {
// we print the original source code
mutableTokenWriter.getPrinterHelper().directPrint(fragment.getSourceCode());
} else {
// we print with the new list context
listContext.print(this);
}
} else if (fragment instanceof ElementSourceFragment) {
ElementSourceFragment sourceFragment = (ElementSourceFragment) fragment;
//it is fragment with single value
ChangeResolver changeResolver1 = null;
if (isModified == ModificationStatus.UNKNOWN) {
changeResolver1 = new ChangeResolver(getChangeCollector(), this.element);
isModified = ModificationStatus.fromBoolean(changeResolver1.hasChangedRole());
}
if (isModified == ModificationStatus.NOT_MODIFIED) {
//nothing is changed, we can print origin sources of this element
mutableTokenWriter.getPrinterHelper().directPrint(fragment.getSourceCode());
return;
}
//check what roles of this element are changed
if (changeResolver1 == null) {
changeResolver1 = new ChangeResolver(getChangeCollector(), this.element);
}
//changeResolver.hasChangedRole() is false when element is added
//something is changed in this element
superScanInContext(this.element, new SourceFragmentContextNormal(mutableTokenWriter, sourceFragment, changeResolver1));
} else {
throw new SpoonException("Unsupported fragment type: " + fragment.getClass());
}
}
};
}
Expand Down Expand Up @@ -304,67 +341,8 @@ private SourceFragmentPrinter detectCurrentContext(PrinterEvent event) {
return sfc;
}

/**
* scans the `element` which exist on `role` in its parent
* @param role {@link CtRole} of `element` in scope of it's parent
* @param element a scanned element
* @param fragment origin source fragment of element
* @param isFragmentModified true if any part of `fragment` is modified, false if whole fragment is not modified, null if caller doesn't know
*/
private void scanInternal(CtRole role, CtElement element, SourceFragment fragment, Boolean isFragmentModified) {
if (mutableTokenWriter.isMuted()) {
throw new SpoonException("Unexpected state of sniper pretty printer. TokenWriter is muted.");
}


//it is not muted yet, so this element or any sibling is modified
if (fragment == null) {
throw new SpoonException("Missing source fragment. Call PrintEvent#print instead.");
}
//we have sources of fragment
if (fragment instanceof CollectionSourceFragment) {
//we started scanning of collection of elements
SourceFragmentPrinter listContext = getCollectionContext(element, (CollectionSourceFragment) fragment, isFragmentModified);
//push the context of this collection
pushContext(listContext);


//and scan first element of that collection again in new context of that collection
if (Boolean.FALSE.equals(isFragmentModified)) {
// we print the original source code
mutableTokenWriter.getPrinterHelper().directPrint(fragment.getSourceCode());
} else {
// we print it normally
scan(element);
}
} else if (fragment instanceof ElementSourceFragment) {
ElementSourceFragment sourceFragment = (ElementSourceFragment) fragment;
//it is fragment with single value
ChangeResolver changeResolver = null;
if (isFragmentModified == null) {
changeResolver = new ChangeResolver(getChangeCollector(), element);
isFragmentModified = changeResolver.hasChangedRole();
}
if (isFragmentModified == false) {
//nothing is changed, we can print origin sources of this element
mutableTokenWriter.getPrinterHelper().directPrint(fragment.getSourceCode());
return;
}
//check what roles of this element are changed
if (changeResolver == null) {
changeResolver = new ChangeResolver(getChangeCollector(), element);
}
//changeResolver.hasChangedRole() is false when element is added
//something is changed in this element
superScanInContext(element, new SourceFragmentContextNormal(mutableTokenWriter, sourceFragment, changeResolver));
} else {
throw new SpoonException("Unsupported fragment type: " + fragment.getClass());
}
}

private SourceFragmentPrinter getCollectionContext(CtElement element, CollectionSourceFragment csf, boolean isModified) {
return csf.isOrdered()
? new SourceFragmentContextList(mutableTokenWriter, element, csf.getItems(), getChangeResolver()) {
return new SourceFragmentContextList(mutableTokenWriter, element, csf.getItems(), getChangeResolver()) {
@Override
public void onPush() {
super.onPush();
Expand All @@ -381,23 +359,6 @@ public void onFinished() {
}
}

}
: new SourceFragmentContextSet(mutableTokenWriter, element, csf.getItems(), getChangeResolver()) {
@Override
public void onPush() {
super.onPush();
if (!isModified) {
mutableTokenWriter.setMuted(true);
}
}

@Override
public void onFinished() {
super.onFinished();
if (!isModified) {
mutableTokenWriter.setMuted(false);
}
}
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ public boolean knowsHowToPrint(PrinterEvent event) {
}

@Override
protected Boolean isFragmentModified(SourceFragment fragment) {
protected ModificationStatus isFragmentModified(SourceFragment fragment) {
//we cannot fast detect if it is modified using our changeResolver.
//So return null. The code later will detect it including modified roles.
return null;
// The code later will detect it including modified roles.
return ModificationStatus.UNKNOWN;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ public int update(PrinterEvent event) {
//but may be it is not good idea

//send all inc/dec tab to printer helper to have configured expected indentation
event.print();
event.printSourceFragment(null, ModificationStatus.UNKNOWN);
return -1;
}
if (tpe.getType().isWhiteSpace()) {
//collect all DJPP separators for future use or ignore
separatorActions.add(() -> event.print());
separatorActions.add(() -> event.printSourceFragment(null, ModificationStatus.UNKNOWN));
return -1;
}
}
Expand All @@ -91,7 +91,7 @@ public int update(PrinterEvent event) {
* It can happen e.g. when type parameter like <T> was added. Then bracket tokens are not in origin sources
*/
printSpaces(-1);
event.print();
event.printSourceFragment(null, ModificationStatus.UNKNOWN);
return -1;
}
// case 2: it's an element printer
Expand Down Expand Up @@ -131,31 +131,31 @@ protected void printSpaces(int fragmentIndex) {
* @param fragment
* @return true if at least part of `fragment` is modified.
* false if whole `fragment` is not modified.
* null if it is not possible to detect it here. Then it will be detected later.
* ModificationStatus.UNKNOWN if it is not possible to detect it here. Then it will be detected later.
*/
protected Boolean isFragmentModified(SourceFragment fragment) {
protected ModificationStatus isFragmentModified(SourceFragment fragment) {
if (fragment instanceof TokenSourceFragment) {
switch (((TokenSourceFragment) fragment).getType()) {
//we do not know the role of the identifier token, so we do not know whether it is modified or not
case IDENTIFIER:
return null;
return ModificationStatus.UNKNOWN;
case COMMENT:
return null;
return ModificationStatus.UNKNOWN;
default:
//all others are constant tokens, which cannot be modified
return Boolean.FALSE;
return ModificationStatus.NOT_MODIFIED;
}
} else if (fragment instanceof ElementSourceFragment) {
return changeResolver.isRoleModified(((ElementSourceFragment) fragment).getRoleInParent());
return ModificationStatus.fromBoolean(changeResolver.isRoleModified(((ElementSourceFragment) fragment).getRoleInParent()));
} else if (fragment instanceof CollectionSourceFragment) {
CollectionSourceFragment csf = (CollectionSourceFragment) fragment;
for (SourceFragment sourceFragment : csf.getItems()) {
Boolean modified = isFragmentModified(sourceFragment);
if (!Boolean.FALSE.equals(modified)) {
ModificationStatus modified = isFragmentModified(sourceFragment);
if (!ModificationStatus.NOT_MODIFIED.equals(modified)) {
return modified;
}
}
return Boolean.FALSE;
return ModificationStatus.NOT_MODIFIED;
} else {
throw new SpoonException("Unexpected SourceFragment type " + fragment.getClass());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public class ChangeResolver {
public ChangeResolver(ChangeCollector changeCollector, CtElement element) {
this.changeCollector = changeCollector;
this.element = element;
changedRoles = element != null ? changeCollector.getChanges(element) : null;
changedRoles = element != null ? changeCollector.getChanges(element) : Collections.EMPTY_SET;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,10 @@
*/
package spoon.support.sniper.internal;

import java.util.List;

import spoon.reflect.meta.ContainerKind;
import spoon.reflect.path.CtRole;
import spoon.support.Experimental;

import java.util.List;

/**
* {@link SourceFragment} of List or Set of {@link ElementSourceFragment}s which belong to collection role.
* For example list of Type members or list of parameters, etc.
Expand Down Expand Up @@ -48,30 +46,4 @@ public String toString() {
return items.toString();
}

/**
* @return true if collection contains only children of one role handler with container kind LIST
*/
public boolean isOrdered() {
CtRole role = null;
for (SourceFragment childSourceFragment : items) {
if (childSourceFragment instanceof ElementSourceFragment) {
ElementSourceFragment esf = (ElementSourceFragment) childSourceFragment;
if (role == null) {
role = esf.getRoleInParent();
ContainerKind kind = esf.getContainerKindInParent();
if (kind != ContainerKind.LIST) {
return false;
}
} else {
if (role != esf.getRoleInParent()) {
//the collection contains elements of different roles. It cannot be ordered
return false;
}
//else there is another element of the same role - ok
}
}
}
//there are only elements of one role of container kind LIST
return true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public void onPush() {

@Override
public void print(PrinterEvent event) {
event.print();
event.printSourceFragment(null, ModificationStatus.UNKNOWN);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
* Represents an action of Printer, which prints whole element
*/
public abstract class ElementPrinterEvent implements PrinterEvent {
private final CtRole role;
private final CtElement element;
protected final CtRole role;
protected final CtElement element;

public ElementPrinterEvent(CtRole role, CtElement element) {
this.role = role;
Expand Down
Loading

0 comments on commit e39950e

Please sign in to comment.