Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

review: feature: add support for sniper mode #1927

Merged
merged 10 commits into from Sep 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
39 changes: 29 additions & 10 deletions doc/launcher.md
Expand Up @@ -4,15 +4,15 @@ tags: [usage]
keywords: usage, java
---

## Basic Launcher
## The Launcher class

The Spoon `Launcher` ([JavaDoc](http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/Launcher.html)) is used to create the AST model of a project. It can be as short as:

```java
CtClass l = Launcher.parseClass("class A { void m() { System.out.println(\"yeah\");} }");
```

The Launcher is highly configurable:
Or with a plain object:

```java
Launcher launcher = new Launcher();
Expand All @@ -21,19 +21,39 @@ Launcher launcher = new Launcher();
// addInputResource can be called several times
launcher.addInputResource("<path_to_source>");

launcher.buildModel();

CtModel model = launcher.getModel();
```

### Pretty-printing modes

**Autoimport** Spoon can pretty-print code where all classes and methods are fully-qualified. This is not readable for humans but enables fast compilation and is useful when name collisions happen.

```java
launcher.getEnvironment().setAutoImports(false);
```

The autoimport mode computes the required imports, add the imports in the pretty-printed files, and writes class names unqualified (w/o package names):

```java
// if true, the pretty-printed code is readable without fully-qualified names
launcher.getEnvironment().setAutoImports(true); // optional
launcher.getEnvironment().setAutoImports(true);
```

// if true, the model can be built even if the dependencies of the analyzed source code are not known or incomplete
// the classes that are in the current classpath are taken into account
launcher.getEnvironment().setNoClasspath(true); // optional
**Sniper mode** By default, when pretty-printing, Spoon reformats the code according to its own formatting rules.

launcher.buildModel();
CtModel model = launcher.getModel();
The sniper mode enables to rewrite only the transformed AST elements, so that the rest of the code is printed identically to the origin version. This is useful to get small diffs after automated refactoring.

```java
launcher.getEnvironment().setPrettyPrinterCreator(() -> {
return new SniperJavaPrettyPrinter(launcher.getEnvironment());
}
);
```


## Maven Launcher
## The MavenLauncher class

The Spoon `MavenLauncher` ([JavaDoc](http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/MavenLauncher.html)) is used to create the AST model of a Maven project.
It automatically infers the list of source folders and the dependencies from the `pom.xml` file.
Expand Down Expand Up @@ -71,7 +91,6 @@ To avoid invoking maven over and over to build a classpath that has not changed,

## About the classpath


Spoon analyzes source code. However, this source code may refer to libraries (as a field, parameter, or method return type). There are two cases:

* Full classpath: all dependencies are in the JVM classpath or are given to the Laucher with `launcher.getEnvironment().setSourceClasspath("<classpath_project>");` (optional)
Expand Down
6 changes: 3 additions & 3 deletions src/main/java/spoon/Launcher.java
Expand Up @@ -39,7 +39,6 @@
import spoon.reflect.declaration.CtType;
import spoon.reflect.factory.Factory;
import spoon.reflect.factory.FactoryImpl;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
import spoon.reflect.visitor.Filter;
import spoon.reflect.visitor.PrettyPrinter;
import spoon.reflect.visitor.filter.AbstractFilter;
Expand Down Expand Up @@ -676,7 +675,8 @@ public Factory getFactory() {

@Override
public Environment createEnvironment() {
return new StandardEnvironment();
Environment env = new StandardEnvironment();
return env;
}

public JavaOutputProcessor createOutputWriter() {
Expand All @@ -686,7 +686,7 @@ public JavaOutputProcessor createOutputWriter() {
}

public PrettyPrinter createPrettyPrinter() {
return new DefaultJavaPrettyPrinter(getEnvironment());
return getEnvironment().createPrettyPrinter();
}

/**
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/spoon/compiler/Environment.java
Expand Up @@ -26,12 +26,15 @@
import spoon.processing.ProcessorProperties;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.declaration.CtMethod;
import spoon.reflect.visitor.PrettyPrinter;
import spoon.support.OutputDestinationHandler;
import spoon.support.CompressionType;
import spoon.support.compiler.SpoonProgress;
import spoon.support.sniper.SniperJavaPrettyPrinter;

import java.io.File;
import java.nio.charset.Charset;
import java.util.function.Supplier;

/**
* This interface represents the environment in which Spoon is launched -
Expand Down Expand Up @@ -412,4 +415,16 @@ void report(Processor<?> processor, Level level,
* Set the type of serialization to be used by default
*/
void setCompressionType(CompressionType serializationType);

/**
* @return new instance of {@link PrettyPrinter} which is configured for this environment
*/
PrettyPrinter createPrettyPrinter();

/**
* @param creator a {@link Supplier}, which creates new instance of pretty printer.
* Can be used to create a {@link SniperJavaPrettyPrinter} for enabling the sniper mode.
*
*/
void setPrettyPrinterCreator(Supplier<PrettyPrinter> creator);
}
Expand Up @@ -32,7 +32,7 @@
* Describes what next has to be matched.
* It consists of current `parameters` represented by {@link ImmutableMap}
* and by a to be matched target elements.
* See children of {@link TobeMatched} for supported collections of targer elements.
* See children of {@link TobeMatched} for supported collections of target elements.
*/
public class TobeMatched {
//TODO remove parameters. Send them individually into matching methods and return MatchResult
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/spoon/reflect/cu/SourcePositionHolder.java
Expand Up @@ -16,7 +16,7 @@
*/
package spoon.reflect.cu;

import spoon.reflect.visitor.printer.internal.ElementSourceFragment;
import spoon.support.sniper.internal.ElementSourceFragment;
import spoon.support.Experimental;

/**
Expand Down
Expand Up @@ -195,7 +195,7 @@ public class DefaultJavaPrettyPrinter implements CtVisitor, PrettyPrinter {
/**
* Environment which Spoon is executed.
*/
private Environment env;
protected Environment env;

/**
* Token detector, which delegates tokens to {@link TokenWriter}
Expand All @@ -210,7 +210,7 @@ public class DefaultJavaPrettyPrinter implements CtVisitor, PrettyPrinter {
/**
* Compilation unit we are printing.
*/
private CompilationUnit sourceCompilationUnit;
protected CompilationUnit sourceCompilationUnit;

/**
* Imports computed
Expand Down Expand Up @@ -1982,6 +1982,10 @@ public void calculate(CompilationUnit sourceCompilationUnit, List<CtType<?>> typ
imports.addAll(computeImports(t));
}
this.writeHeader(types, imports);
printTypes(types);
}

protected void printTypes(List<CtType<?>> types) {
for (CtType<?> t : types) {
scan(t);
if (!env.isPreserveLineNumbers()) {
Expand Down
23 changes: 23 additions & 0 deletions src/main/java/spoon/support/StandardEnvironment.java
Expand Up @@ -29,6 +29,8 @@
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Supplier;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import spoon.Launcher;
Expand All @@ -38,6 +40,7 @@
import spoon.compiler.InvalidClassPathException;
import spoon.compiler.SpoonFile;
import spoon.compiler.SpoonFolder;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
import spoon.support.modelobs.EmptyModelChangeListener;
import spoon.support.modelobs.FineModelChangeListener;
import spoon.processing.FileGenerator;
Expand All @@ -50,6 +53,7 @@
import spoon.reflect.declaration.CtExecutable;
import spoon.reflect.declaration.CtType;
import spoon.reflect.declaration.ParentNotInitializedException;
import spoon.reflect.visitor.PrettyPrinter;
import spoon.support.compiler.FileSystemFolder;
import spoon.support.compiler.SpoonProgress;

Expand Down Expand Up @@ -108,6 +112,10 @@ public class StandardEnvironment implements Serializable, Environment {

private CompressionType compressionType = CompressionType.GZIP;

private boolean sniperMode = false;

private Supplier<PrettyPrinter> prettyPrinterCreator;

/**
* Creates a new environment with a <code>null</code> default file
* generator.
Expand Down Expand Up @@ -613,4 +621,19 @@ public CompressionType getCompressionType() {
public void setCompressionType(CompressionType serializationType) {
this.compressionType = serializationType;
}

@Override
public PrettyPrinter createPrettyPrinter() {
if (prettyPrinterCreator == null) {
// DJPP is the default mode
// fully backward compatible
return new DefaultJavaPrettyPrinter(this);
}
return prettyPrinterCreator.get();
}

@Override
public void setPrettyPrinterCreator(Supplier<PrettyPrinter> creator) {
this.prettyPrinterCreator = creator;
}
}
Expand Up @@ -49,10 +49,12 @@
import spoon.reflect.visitor.Filter;
import spoon.reflect.visitor.PrettyPrinter;
import spoon.reflect.visitor.Query;
import spoon.support.sniper.SniperJavaPrettyPrinter;
import spoon.support.QueueProcessingManager;
import spoon.support.comparator.FixedOrderBasedOnFileNameCompilationUnitComparator;
import spoon.support.compiler.SpoonProgress;
import spoon.support.compiler.VirtualFolder;
import spoon.support.modelobs.SourceFragmentCreator;

import java.io.ByteArrayInputStream;
import java.io.File;
Expand Down Expand Up @@ -128,6 +130,12 @@ public boolean build(JDTBuilder builder) {
factory.getEnvironment().debugMessage("built in " + (System.currentTimeMillis() - t) + " ms");
checkModel();
factory.getModel().setBuildModelIsFinished(true);

if (factory.getEnvironment().createPrettyPrinter() instanceof SniperJavaPrettyPrinter) {
//setup a model change collector
new SourceFragmentCreator().attachTo(factory.getEnvironment());
}

return srcSuccess && templateSuccess;
}

Expand Down Expand Up @@ -661,7 +669,7 @@ protected InputStream getCompilationUnitInputStream(String path) {
spoon.reflect.cu.CompilationUnit cu = factory.CompilationUnit().getMap().get(path);
List<CtType<?>> toBePrinted = cu.getDeclaredTypes();

PrettyPrinter printer = new DefaultJavaPrettyPrinter(env);
PrettyPrinter printer = env.createPrettyPrinter();
printer.calculate(cu, toBePrinted);

return new ByteArrayInputStream(printer.getResult().getBytes());
Expand Down
22 changes: 21 additions & 1 deletion src/main/java/spoon/support/modelobs/ChangeCollector.java
Expand Up @@ -52,6 +52,26 @@ public static ChangeCollector getChangeCollector(Environment env) {
return null;
}

/**
* Allows to run code using change collector switched off.
* It means that any change of spoon model done by the `runnable` is ignored by the change collector.
* Note: it is actually needed to wrap CtElement#toString() calls which sometime modifies spoon model.
* See TestSniperPrinter#testPrintChangedReferenceBuilder()
* @param env Spoon environment
* @param runnable the code to be run
*/
public static void runWithoutChangeListener(Environment env, Runnable runnable) {
FineModelChangeListener mcl = env.getModelChangeListener();
if (mcl instanceof ChangeListener) {
env.setModelChangeListener(new EmptyModelChangeListener());
try {
runnable.run();
} finally {
env.setModelChangeListener(mcl);
}
}
}

/**
* Attaches itself to {@link CtModel} to listen to all changes of it's child elements
* TODO: it would be nicer if we might listen on changes on {@link CtElement}
Expand Down Expand Up @@ -96,7 +116,7 @@ public ScanningMode enter(CtElement element) {
checkedRole = scanner.getScannedRole();
}
if (changes.contains(checkedRole)) {
//we already know that som echild of `checkedRole` attribute is modified. Skip others
//we already know that some child of `checkedRole` attribute is modified. Skip others
return ScanningMode.SKIP_ALL;
}
if (elementToChangeRole.containsKey(element)) {
Expand Down
40 changes: 40 additions & 0 deletions src/main/java/spoon/support/modelobs/SourceFragmentCreator.java
@@ -0,0 +1,40 @@
/**
* Copyright (C) 2006-2018 INRIA and contributors
* Spoon - http://spoon.gforge.inria.fr/
*
* This software is governed by the CeCILL-C License under French law and
* abiding by the rules of distribution of free software. You can use, modify
* and/or redistribute the software under the terms of the CeCILL-C license as
* circulated by CEA, CNRS and INRIA at http://www.cecill.info.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the CeCILL-C License for more details.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C license and that you accept its terms.
*/
package spoon.support.modelobs;

import spoon.reflect.cu.CompilationUnit;
import spoon.reflect.declaration.CtElement;
import spoon.reflect.path.CtRole;
import spoon.support.sniper.internal.ElementSourceFragment;

/**
* A {@link ChangeCollector}, which builds a tree of {@link ElementSourceFragment}s of {@link CompilationUnit} of the modified element
* lazily just before the element is changed
*/
public class SourceFragmentCreator extends ChangeCollector {
@Override
protected void onChange(CtElement currentElement, CtRole role) {
CompilationUnit cu = currentElement.getPosition().getCompilationUnit();
if (cu != null) {

//getOriginalSourceFragment is not only a getter, it actually
//builds a tree of SourceFragments of compilation unit of the modified element
cu.getOriginalSourceFragment();
}
super.onChange(currentElement, role);
}
}
Expand Up @@ -20,7 +20,7 @@
import spoon.reflect.cu.SourcePosition;
import spoon.reflect.cu.SourcePositionHolder;
import spoon.reflect.declaration.ModifierKind;
import spoon.reflect.visitor.printer.internal.ElementSourceFragment;
import spoon.support.sniper.internal.ElementSourceFragment;

import java.io.Serializable;

Expand Down
Expand Up @@ -27,7 +27,7 @@
import spoon.reflect.factory.Factory;
import spoon.reflect.visitor.DefaultJavaPrettyPrinter;
import spoon.reflect.visitor.filter.TypeFilter;
import spoon.reflect.visitor.printer.internal.ElementSourceFragment;
import spoon.support.sniper.internal.ElementSourceFragment;
import spoon.support.reflect.cu.position.PartialSourcePositionImpl;

import java.io.File;
Expand Down
Expand Up @@ -53,7 +53,7 @@
import spoon.reflect.visitor.chain.CtFunction;
import spoon.reflect.visitor.chain.CtQuery;
import spoon.reflect.visitor.filter.AnnotationFilter;
import spoon.reflect.visitor.printer.internal.ElementSourceFragment;
import spoon.support.sniper.internal.ElementSourceFragment;
import spoon.reflect.visitor.CtIterator;
import spoon.support.DefaultCoreFactory;
import spoon.support.DerivedProperty;
Expand Down