Skip to content

Commit 8aa8f0b

Browse files
nharrandmonperrus
authored andcommitted
fix(CtElementImpl#toString): fix toString for SniperPrinter (INRIA#3147)
1 parent 2272a6a commit 8aa8f0b

File tree

7 files changed

+193
-23
lines changed

7 files changed

+193
-23
lines changed

src/main/java/spoon/reflect/visitor/DefaultJavaPrettyPrinter.java

+33-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
*/
66
package spoon.reflect.visitor;
77

8+
import org.apache.log4j.Logger;
89
import spoon.SpoonException;
910
import spoon.compiler.Environment;
1011
import spoon.experimental.CtUnresolvedImport;
@@ -234,6 +235,37 @@ public DefaultJavaPrettyPrinter setLineSeparator(String lineSeparator) {
234235
return this;
235236
}
236237

238+
239+
protected static final Logger LOGGER = Logger.getLogger(DefaultJavaPrettyPrinter.class);
240+
public static final String ERROR_MESSAGE_TO_STRING = "Error in printing the node. One parent isn't initialized!";
241+
/**
242+
* Prints an element. This method shall be called by the toString() method of an element.
243+
* It is responsible for any initialization required to print an arbitrary element.
244+
* @param element
245+
* @return A string containing the pretty printed element (and descendants).
246+
*/
247+
public String printElement(CtElement element) {
248+
249+
String errorMessage = "";
250+
try {
251+
// now that pretty-printing can change the model, we only do it on a clone
252+
CtElement clone = element.clone();
253+
254+
// required: in DJPP some decisions are taken based on the content of the parent
255+
if (element.isParentInitialized()) {
256+
clone.setParent(element.getParent());
257+
}
258+
applyPreProcessors(clone);
259+
scan(clone);
260+
} catch (ParentNotInitializedException ignore) {
261+
LOGGER.error(ERROR_MESSAGE_TO_STRING, ignore);
262+
errorMessage = ERROR_MESSAGE_TO_STRING;
263+
}
264+
// in line-preservation mode, newlines are added at the beginning to matches the lines
265+
// removing them from the toString() representation
266+
return toString().replaceFirst("^\\s+", "") + errorMessage;
267+
}
268+
237269
/**
238270
* Enters an expression.
239271
*/
@@ -1956,7 +1988,7 @@ public String getResult() {
19561988
public void reset() {
19571989
printer.reset();
19581990
context = new PrintingContext();
1959-
}
1991+
}
19601992

19611993

19621994
/**

src/main/java/spoon/reflect/visitor/PrettyPrinter.java

+8
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,14 @@ public interface PrettyPrinter {
4242
*/
4343
String printTypes(CtType<?>... type);
4444

45+
/**
46+
* Prints an element. This method shall be called by the toString() method of an element.
47+
* It is responsible for any initialization required to print an arbitrary element.
48+
* @param element
49+
* @return A string containing the pretty printed element (and descendants).
50+
*/
51+
String printElement(CtElement element);
52+
4553
/**
4654
* Gets the contents of the compilation unit.
4755
*/

src/main/java/spoon/support/reflect/declaration/CtElementImpl.java

+1-20
Original file line numberDiff line numberDiff line change
@@ -292,26 +292,7 @@ public String toStringDebug() {
292292
public String toString() {
293293
DefaultJavaPrettyPrinter printer = (DefaultJavaPrettyPrinter) getFactory().getEnvironment().createPrettyPrinter();
294294

295-
String errorMessage = "";
296-
try {
297-
// now that pretty-printing can change the model, we only do it on a clone
298-
CtElement clone = this.clone();
299-
300-
// required: in DJPP some decisions are taken based on the content of the parent
301-
if (this.isParentInitialized()) {
302-
clone.setParent(this.getParent());
303-
}
304-
305-
printer.applyPreProcessors(clone);
306-
307-
printer.scan(clone);
308-
} catch (ParentNotInitializedException ignore) {
309-
LOGGER.error(ERROR_MESSAGE_TO_STRING, ignore);
310-
errorMessage = ERROR_MESSAGE_TO_STRING;
311-
}
312-
// in line-preservation mode, newlines are added at the beginning to matches the lines
313-
// removing them from the toString() representation
314-
return printer.toString().replaceFirst("^\\s+", "") + errorMessage;
295+
return printer.printElement(this);
315296
}
316297

317298
@Override

src/main/java/spoon/support/sniper/SniperJavaPrettyPrinter.java

+59
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import spoon.SpoonException;
1414
import spoon.compiler.Environment;
1515
import spoon.reflect.code.CtComment;
16+
import spoon.reflect.cu.CompilationUnit;
17+
import spoon.reflect.cu.position.NoSourcePosition;
1618
import spoon.reflect.declaration.CtCompilationUnit;
1719
import spoon.reflect.declaration.CtElement;
1820
import spoon.reflect.path.CtRole;
@@ -176,8 +178,65 @@ public void printSourceFragment(SourceFragment fragment, Boolean isModified) {
176178
}
177179

178180

181+
182+
private static boolean hasImplicitAncestor(CtElement el) {
183+
if (el == null) {
184+
return false;
185+
}
186+
if (el == el.getFactory().getModel().getRootPackage()) {
187+
return false;
188+
} else if (el.isImplicit()) {
189+
return true;
190+
} else {
191+
return hasImplicitAncestor(el.getParent());
192+
}
193+
}
194+
195+
/**
196+
* SniperPrettyPrinter does not apply preprocessor to a CtElement when calling toString()
197+
* @param element
198+
* @return
199+
*/
200+
@Override
201+
public String printElement(CtElement element) {
202+
if (element != null && !hasImplicitAncestor(element)) {
203+
CompilationUnit compilationUnit = element.getPosition().getCompilationUnit();
204+
if (compilationUnit != null
205+
&& !(compilationUnit instanceof NoSourcePosition.NullCompilationUnit)) {
206+
207+
//use line separator of origin source file
208+
setLineSeparator(detectLineSeparator(compilationUnit.getOriginalSourceCode()));
209+
210+
CtRole role = getRoleInCompilationUnit(element);
211+
ElementSourceFragment esf = element.getOriginalSourceFragment();
212+
213+
runInContext(
214+
new SourceFragmentContextList(mutableTokenWriter,
215+
element,
216+
Collections.singletonList(esf),
217+
new ChangeResolver(getChangeCollector(), element)),
218+
() -> onPrintEvent(new ElementPrinterEvent(role, element) {
219+
@Override
220+
public void print(Boolean muted) {
221+
superScanInContext(element, SourceFragmentContextPrettyPrint.INSTANCE, muted);
222+
}
223+
224+
@Override
225+
public void printSourceFragment(SourceFragment fragment, Boolean isModified) {
226+
scanInternal(role, element, fragment, isModified);
227+
}
228+
})
229+
);
230+
}
231+
}
232+
233+
return toString().replaceFirst("^\\s+", "");
234+
}
235+
236+
179237
/**
180238
* Called whenever {@link DefaultJavaPrettyPrinter} scans/prints an element
239+
* Warning: DO not call on a cloned element. Use scanClone instead.
181240
*/
182241
@Override
183242
public SniperJavaPrettyPrinter scan(CtElement element) {

src/main/java/spoon/support/visitor/equals/CloneHelper.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public <T extends CtElement> Map<String, T> clone(Map<String, T> elements) {
9595
}
9696

9797
/**
98-
* clones a element and adds it's clone as value into targetCollection
98+
* clones an element and adds it's clone as value into targetCollection
9999
* @param targetCollection - the collection which will receive a clone of element
100100
* @param element to be cloned element
101101
*/

src/test/java/spoon/test/prettyprinter/TestSniperPrinter.java

+68-1
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,31 @@
1818

1919
import org.junit.Test;
2020
import spoon.Launcher;
21+
import spoon.SpoonException;
22+
import spoon.compiler.Environment;
23+
import spoon.processing.AbstractProcessor;
2124
import spoon.processing.Processor;
25+
import spoon.processing.ProcessorProperties;
26+
import spoon.processing.TraversalStrategy;
27+
import spoon.reflect.CtModel;
2228
import spoon.reflect.code.CtStatement;
2329
import spoon.reflect.declaration.CtClass;
24-
import spoon.reflect.declaration.CtCompilationUnit;
2530
import spoon.reflect.declaration.CtElement;
2631
import spoon.reflect.declaration.CtField;
2732
import spoon.reflect.declaration.CtMethod;
33+
import spoon.reflect.declaration.CtModule;
34+
import spoon.reflect.declaration.CtPackage;
2835
import spoon.reflect.declaration.CtType;
2936
import spoon.reflect.declaration.ModifierKind;
3037
import spoon.reflect.factory.Factory;
38+
import spoon.reflect.reference.CtPackageReference;
3139
import spoon.reflect.reference.CtTypeReference;
40+
import spoon.reflect.visitor.CtScanner;
3241
import spoon.reflect.visitor.ImportCleaner;
3342
import spoon.reflect.visitor.ImportConflictDetector;
43+
import spoon.reflect.visitor.filter.TypeFilter;
3444
import spoon.support.modelobs.ChangeCollector;
45+
import spoon.support.modelobs.SourceFragmentCreator;
3546
import spoon.support.sniper.SniperJavaPrettyPrinter;
3647
import spoon.test.prettyprinter.testclasses.ToBeChanged;
3748

@@ -40,14 +51,23 @@
4051
import java.io.IOException;
4152
import java.io.InputStream;
4253
import java.io.UnsupportedEncodingException;
54+
import java.nio.charset.Charset;
55+
import java.nio.charset.StandardCharsets;
56+
import java.nio.file.Files;
57+
import java.nio.file.Paths;
4358
import java.util.Arrays;
4459
import java.util.Collections;
60+
import java.util.LinkedList;
61+
import java.util.List;
62+
import java.util.Set;
4563
import java.util.function.BiConsumer;
4664
import java.util.function.Consumer;
4765
import java.util.regex.Matcher;
4866
import java.util.regex.Pattern;
4967

5068
import static org.junit.Assert.assertEquals;
69+
import static org.junit.Assert.assertTrue;
70+
import static org.junit.Assert.fail;
5171

5272
public class TestSniperPrinter {
5373

@@ -263,4 +283,51 @@ private String sourceWithoutImports(String source) {
263283
}
264284
return source.substring(lastImportEnd).trim();
265285
}
286+
287+
private static String fileAsString(String path, Charset encoding)
288+
throws IOException
289+
{
290+
byte[] encoded = Files.readAllBytes(Paths.get(path));
291+
return new String(encoded, encoding);
292+
}
293+
294+
public void testToStringWithSniperPrinter(String inputSourcePath) throws Exception {
295+
296+
final Launcher launcher = new Launcher();
297+
launcher.addInputResource(inputSourcePath);
298+
String originalContent = fileAsString(inputSourcePath, StandardCharsets.UTF_8).replace("\t","");
299+
CtModel model = launcher.buildModel();
300+
301+
new SourceFragmentCreator().attachTo(launcher.getFactory().getEnvironment());
302+
303+
launcher.getEnvironment().setPrettyPrinterCreator(
304+
() -> {
305+
SniperJavaPrettyPrinter sp = new SniperJavaPrettyPrinter(launcher.getEnvironment());
306+
sp.setIgnoreImplicit(true);
307+
return sp;
308+
}
309+
);
310+
List<CtElement> ops = model.getElements(new TypeFilter<>(CtElement.class));
311+
312+
313+
ops.stream()
314+
.filter(el -> !(el instanceof spoon.reflect.CtModelImpl.CtRootPackage) &&
315+
!(el instanceof spoon.reflect.factory.ModuleFactory.CtUnnamedModule)
316+
).forEach(el -> {
317+
try {
318+
//Contract, calling toString on unmodified AST elements should draw only from original.
319+
assertTrue("ToString() on element (" + el.getClass().getName() + ") = \"" + el + "\" is not in original content",
320+
originalContent.contains(el.toString().replace("\t","")));
321+
} catch (UnsupportedOperationException | SpoonException e) {
322+
//Printer should not throw exception on printable element. (Unless there is a bug in the printer...)
323+
fail("ToString() on Element (" + el.getClass().getName() + "): at " + el.getPath() + " lead to an exception: " + e);
324+
}
325+
});
326+
}
327+
328+
@Test
329+
public void testToStringWithSniperOnElementScan() throws Exception {
330+
testToStringWithSniperPrinter("src/test/java/spoon/test/prettyprinter/testclasses/ElementScan.java");
331+
}
332+
266333
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package spoon.test.prettyprinter.testclasses;
2+
3+
/**
4+
* Copyright (C) 2006-2019 INRIA and contributors
5+
*
6+
* 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.
7+
*/
8+
import spoon.reflect.cu.SourcePositionHolder;
9+
import spoon.reflect.declaration.CtElement;
10+
import spoon.reflect.visitor.EarlyTerminatingScanner;
11+
12+
public class ElementScan {
13+
14+
public void isElementExists(SourcePositionHolder element) {
15+
EarlyTerminatingScanner<Boolean> scanner = new EarlyTerminatingScanner<Boolean>() {
16+
@Override
17+
protected void enter(CtElement e) {
18+
if (element == e) {
19+
}
20+
}
21+
};
22+
}
23+
}

0 commit comments

Comments
 (0)