Skip to content

Commit

Permalink
fix(CtElementImpl#toString): fix toString for SniperPrinter (#3147)
Browse files Browse the repository at this point in the history
  • Loading branch information
nharrand authored and monperrus committed Nov 4, 2019
1 parent 2272a6a commit 8aa8f0b
Show file tree
Hide file tree
Showing 7 changed files with 193 additions and 23 deletions.
Expand Up @@ -5,6 +5,7 @@
*/
package spoon.reflect.visitor;

import org.apache.log4j.Logger;
import spoon.SpoonException;
import spoon.compiler.Environment;
import spoon.experimental.CtUnresolvedImport;
Expand Down Expand Up @@ -234,6 +235,37 @@ public DefaultJavaPrettyPrinter setLineSeparator(String lineSeparator) {
return this;
}


protected static final Logger LOGGER = Logger.getLogger(DefaultJavaPrettyPrinter.class);
public static final String ERROR_MESSAGE_TO_STRING = "Error in printing the node. One parent isn't initialized!";
/**
* Prints an element. This method shall be called by the toString() method of an element.
* It is responsible for any initialization required to print an arbitrary element.
* @param element
* @return A string containing the pretty printed element (and descendants).
*/
public String printElement(CtElement element) {

String errorMessage = "";
try {
// now that pretty-printing can change the model, we only do it on a clone
CtElement clone = element.clone();

// required: in DJPP some decisions are taken based on the content of the parent
if (element.isParentInitialized()) {
clone.setParent(element.getParent());
}
applyPreProcessors(clone);
scan(clone);
} catch (ParentNotInitializedException ignore) {
LOGGER.error(ERROR_MESSAGE_TO_STRING, ignore);
errorMessage = ERROR_MESSAGE_TO_STRING;
}
// in line-preservation mode, newlines are added at the beginning to matches the lines
// removing them from the toString() representation
return toString().replaceFirst("^\\s+", "") + errorMessage;
}

/**
* Enters an expression.
*/
Expand Down Expand Up @@ -1956,7 +1988,7 @@ public String getResult() {
public void reset() {
printer.reset();
context = new PrintingContext();
}
}


/**
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/spoon/reflect/visitor/PrettyPrinter.java
Expand Up @@ -42,6 +42,14 @@ public interface PrettyPrinter {
*/
String printTypes(CtType<?>... type);

/**
* Prints an element. This method shall be called by the toString() method of an element.
* It is responsible for any initialization required to print an arbitrary element.
* @param element
* @return A string containing the pretty printed element (and descendants).
*/
String printElement(CtElement element);

/**
* Gets the contents of the compilation unit.
*/
Expand Down
Expand Up @@ -292,26 +292,7 @@ public String toStringDebug() {
public String toString() {
DefaultJavaPrettyPrinter printer = (DefaultJavaPrettyPrinter) getFactory().getEnvironment().createPrettyPrinter();

String errorMessage = "";
try {
// now that pretty-printing can change the model, we only do it on a clone
CtElement clone = this.clone();

// required: in DJPP some decisions are taken based on the content of the parent
if (this.isParentInitialized()) {
clone.setParent(this.getParent());
}

printer.applyPreProcessors(clone);

printer.scan(clone);
} catch (ParentNotInitializedException ignore) {
LOGGER.error(ERROR_MESSAGE_TO_STRING, ignore);
errorMessage = ERROR_MESSAGE_TO_STRING;
}
// in line-preservation mode, newlines are added at the beginning to matches the lines
// removing them from the toString() representation
return printer.toString().replaceFirst("^\\s+", "") + errorMessage;
return printer.printElement(this);
}

@Override
Expand Down
59 changes: 59 additions & 0 deletions src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java
Expand Up @@ -13,6 +13,8 @@
import spoon.SpoonException;
import spoon.compiler.Environment;
import spoon.reflect.code.CtComment;
import spoon.reflect.cu.CompilationUnit;
import spoon.reflect.cu.position.NoSourcePosition;
import spoon.reflect.declaration.CtCompilationUnit;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.path.CtRole;
Expand Down Expand Up @@ -176,8 +178,65 @@ public void printSourceFragment(SourceFragment fragment, Boolean isModified) {
}



private static boolean hasImplicitAncestor(CtElement el) {
if (el == null) {
return false;
}
if (el == el.getFactory().getModel().getRootPackage()) {
return false;
} else if (el.isImplicit()) {
return true;
} else {
return hasImplicitAncestor(el.getParent());
}
}

/**
* SniperPrettyPrinter does not apply preprocessor to a CtElement when calling toString()
* @param element
* @return
*/
@Override
public String printElement(CtElement element) {
if (element != null && !hasImplicitAncestor(element)) {
CompilationUnit compilationUnit = element.getPosition().getCompilationUnit();
if (compilationUnit != null
&& !(compilationUnit instanceof NoSourcePosition.NullCompilationUnit)) {

//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)),
() -> onPrintEvent(new ElementPrinterEvent(role, element) {
@Override
public void print(Boolean muted) {
superScanInContext(element, SourceFragmentContextPrettyPrint.INSTANCE, muted);
}

@Override
public void printSourceFragment(SourceFragment fragment, Boolean isModified) {
scanInternal(role, element, fragment, isModified);
}
})
);
}
}

return toString().replaceFirst("^\\s+", "");
}


/**
* Called whenever {@link DefaultJavaPrettyPrinter} scans/prints an element
* Warning: DO not call on a cloned element. Use scanClone instead.
*/
@Override
public SniperJavaPrettyPrinter scan(CtElement element) {
Expand Down
Expand Up @@ -95,7 +95,7 @@ public <T extends CtElement> Map<String, T> clone(Map<String, T> elements) {
}

/**
* clones a element and adds it's clone as value into targetCollection
* clones an element and adds it's clone as value into targetCollection
* @param targetCollection - the collection which will receive a clone of element
* @param element to be cloned element
*/
Expand Down
69 changes: 68 additions & 1 deletion src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java
Expand Up @@ -18,20 +18,31 @@

import org.junit.Test;
import spoon.Launcher;
import spoon.SpoonException;
import spoon.compiler.Environment;
import spoon.processing.AbstractProcessor;
import spoon.processing.Processor;
import spoon.processing.ProcessorProperties;
import spoon.processing.TraversalStrategy;
import spoon.reflect.CtModel;
import spoon.reflect.code.CtStatement;
import spoon.reflect.declaration.CtClass;
import spoon.reflect.declaration.CtCompilationUnit;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtField;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.declaration.CtModule;
import spoon.reflect.declaration.CtPackage;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.factory.Factory;
import spoon.reflect.reference.CtPackageReference;
import spoon.reflect.reference.CtTypeReference;
import spoon.reflect.visitor.CtScanner;
import spoon.reflect.visitor.ImportCleaner;
import spoon.reflect.visitor.ImportConflictDetector;
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.support.modelobs.ChangeCollector;
import spoon.support.modelobs.SourceFragmentCreator;
import spoon.support.sniper.SniperJavaPrettyPrinter;
import spoon.test.prettyprinter.testclasses.ToBeChanged;

Expand All @@ -40,14 +51,23 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class TestSniperPrinter {

Expand Down Expand Up @@ -263,4 +283,51 @@ private String sourceWithoutImports(String source) {
}
return source.substring(lastImportEnd).trim();
}

private static String fileAsString(String path, Charset encoding)
throws IOException
{
byte[] encoded = Files.readAllBytes(Paths.get(path));
return new String(encoded, encoding);
}

public void testToStringWithSniperPrinter(String inputSourcePath) throws Exception {

final Launcher launcher = new Launcher();
launcher.addInputResource(inputSourcePath);
String originalContent = fileAsString(inputSourcePath, StandardCharsets.UTF_8).replace("\t","");
CtModel model = launcher.buildModel();

new SourceFragmentCreator().attachTo(launcher.getFactory().getEnvironment());

launcher.getEnvironment().setPrettyPrinterCreator(
() -> {
SniperJavaPrettyPrinter sp = new SniperJavaPrettyPrinter(launcher.getEnvironment());
sp.setIgnoreImplicit(true);
return sp;
}
);
List<CtElement> ops = model.getElements(new TypeFilter<>(CtElement.class));


ops.stream()
.filter(el -> !(el instanceof spoon.reflect.CtModelImpl.CtRootPackage) &&
!(el instanceof spoon.reflect.factory.ModuleFactory.CtUnnamedModule)
).forEach(el -> {
try {
//Contract, calling toString on unmodified AST elements should draw only from original.
assertTrue("ToString() on element (" + el.getClass().getName() + ") = \"" + el + "\" is not in original content",
originalContent.contains(el.toString().replace("\t","")));
} catch (UnsupportedOperationException | SpoonException e) {
//Printer should not throw exception on printable element. (Unless there is a bug in the printer...)
fail("ToString() on Element (" + el.getClass().getName() + "): at " + el.getPath() + " lead to an exception: " + e);
}
});
}

@Test
public void testToStringWithSniperOnElementScan() throws Exception {
testToStringWithSniperPrinter("src/test/java/spoon/test/prettyprinter/testclasses/ElementScan.java");
}

}
@@ -0,0 +1,23 @@
package spoon.test.prettyprinter.testclasses;

/**
* Copyright (C) 2006-2019 INRIA and contributors
*
* Spoon is available either under the terms of the MIT License (see LICENSE-MIT.txt) of the Cecill-C License (see LICENSE-CECILL-C.txt). You as the user are entitled to choose the terms under which to adopt Spoon.
*/
import spoon.reflect.cu.SourcePositionHolder;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.visitor.EarlyTerminatingScanner;

public class ElementScan {

public void isElementExists(SourcePositionHolder element) {
EarlyTerminatingScanner<Boolean> scanner = new EarlyTerminatingScanner<Boolean>() {
@Override
protected void enter(CtElement e) {
if (element == e) {
}
}
};
}
}

0 comments on commit 8aa8f0b

Please sign in to comment.