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

feature: add fluent API for Launcher #3258

Merged
merged 17 commits into from
Feb 26, 2020
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions doc/launcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,3 +200,26 @@ CtModel newModel = launcher.buildModel();
launcher.saveCache();
//Cache is now up to date
```
## Fluent LauncherAPI

`FluentLauncher` ([JavaDoc](http://spoon.gforge.inria.fr/mvnsites/spoon-core/apidocs/spoon/FluentLauncher.html)) allows simple, fluent launcher usage with setting most options directly.

For the classic launcher it's simply:

```java
CtModel model = new FluentLauncher()
.inputResource("<path_to_sources>")
.noClasspath(true)
.outputDirectory("<path_to_outputdir>")
.processor(....)
.buildModel();
```
If you want to use other launchers like the MavenLauncher:

```java
MavenLauncher launcher = new MavenLauncher(....);
CtModel model = new FluentLauncher(launcher)
.processor(....)
.encoding(...)
.buildModel();
```
203 changes: 203 additions & 0 deletions src/main/java/spoon/FluentLauncher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
/**
* 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.
*/
package spoon;

import java.io.File;
import java.nio.charset.Charset;

import spoon.processing.Processor;
import spoon.reflect.CtModel;
import spoon.reflect.declaration.CtElement;

/**
* FluentLauncher provides a different, fluent interface for the launcher class.
* Setting options is done via calls like {@link #noClasspath(boolean)}. This
* class exists for giving a user a simpler fluent api, which is useable for
* most use cases, but not all.
*/
public class FluentLauncher {
/**
* wrapped launcher object used for delegating calls.
*/
private final SpoonAPI launcher;

/**
* Creates a new FluentLauncher, wrapping default {@link Launcher}. After
* setting options, call {@link #buildModel()} for creating the CtModel with
* given settings.
*
*/
public FluentLauncher() {
this.launcher = new Launcher();
}

/**
* Creates a new FluentLauncher, wrapping the given launcher. This constructor
* allows using different launchers eg. {@link MavenLauncher} with fluent api.
* After setting options, call {@link #buildModel()} for creating the CtModel
* with given settings.
*
* @param launcher used for delegating methods.
*/
public FluentLauncher(SpoonAPI launcher) {
this.launcher = launcher;
}

/**
* Adds an input resource to be processed by Spoon (either a file or a folder).
*/
public FluentLauncher inputResource(String path) {
launcher.addInputResource(path);
return this;
}

/**
* Adds an input resource to be processed by Spoon (either a file or a folder).
*/
public FluentLauncher inputResource(Iterable<String> paths) {
for (String path : paths) {
launcher.addInputResource(path);
}
return this;
}

/**
* Adds an instance of a processor. The user is responsible for keeping a
* pointer to it for later retrieving some processing information.
*/
public <T extends CtElement> FluentLauncher processor(Processor<T> processor) {
launcher.addProcessor(processor);
return this;
}

/**
* Adds an instance of a processor. The user is responsible for keeping a
* pointer to it for later retrieving some processing information.
*/
public <T extends CtElement> FluentLauncher processor(Iterable<Processor<T>> processors) {
for (Processor<T> processor : processors) {
launcher.addProcessor(processor);
}
return this;
}

/**
* Builds the model
*/

public CtModel buildModel() {
MartinWitt marked this conversation as resolved.
Show resolved Hide resolved
launcher.run();
return launcher.getModel();
}

/**
* Sets the output directory for source generated.
*
* @param path Path for the output directory.
*/
public FluentLauncher outputDirectory(String path) {
launcher.setSourceOutputDirectory(path);
return this;
}

/**
* Sets the output directory for source generated.
*
* @param outputDirectory {@link File} for output directory.
*/
public FluentLauncher outputDirectory(File outputDirectory) {
launcher.setSourceOutputDirectory(outputDirectory);
return this;
}

/**
* Tell to the Java printer to automatically generate imports and use simple
* names instead of fully-qualified name.
*
* @param autoImports toggles autoImports on or off.
* @return the launcher after setting the option.
*/
public FluentLauncher autoImports(boolean autoImports) {
launcher.getEnvironment().setAutoImports(autoImports);
return this;
}

/**
* Disable all consistency checks on the AST. Dangerous! The only valid usage of
* this is to keep full backward-compatibility.
*
* @return the launcher after setting the option.
*/
public FluentLauncher disableConsistencyChecks() {
launcher.getEnvironment().disableConsistencyChecks();
return this;
}

/**
* Sets the Java version compliance level.
*
* @param level of java version
* @return the launcher after setting the option.
*/
public FluentLauncher complianceLevel(int level) {
launcher.getEnvironment().setComplianceLevel(level);
return this;
}

/**
* Sets the source class path of the Spoon model. Only .jar files or directories
* with *.class files are accepted. The *.jar or *.java files contained in given
* directories are ignored.
*
* @throws InvalidClassPathException if a given classpath does not exists or
* does not have the right format (.jar file
* or directory)
* @param sourceClasspath path to sources.
* @return the launcher after setting the option.
*/
public FluentLauncher sourceClassPath(String[] sourceClasspath) {
launcher.getEnvironment().setSourceClasspath(sourceClasspath);
return this;
}

/**
* Sets the option "noclasspath", use with caution (see explanation below).
*
* With this option, Spoon does not require the full classpath to build the
* model. In this case, all references to classes that are not in the classpath
* are handled with the reference mechanism. The "simplename" of the reference
* object refers to the unbound identifier.
*
* This option facilitates the use of Spoon when is is hard to have the complete
* and correct classpath, for example for mining software repositories.
*
* For writing analyses, this option works well if you don't cross the reference
* by a call to getDeclaration() (if you really want to do so, then check for
* nullness of the result before).
*
* In normal mode, compilation errors are signaled as exception, with this
* option enabled they are signaled as message only. The reason is that in most
* cases, there are necessarily errors related to the missing classpath
* elements.
*
* @return the launcher after setting the option.
*/
public FluentLauncher noClasspath(boolean option) {
launcher.getEnvironment().setNoClasspath(option);
return this;
}

/**
* Set the encoding to use for parsing source code
*
* @param encoding used for parsing.
* @return the launcher after setting the option.
*/
public FluentLauncher encoding(Charset encoding) {
launcher.getEnvironment().setEncoding(encoding);
return this;
}
}
102 changes: 102 additions & 0 deletions src/test/java/spoon/FluentLauncherTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/**
* 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.
*/
package spoon;

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

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import spoon.processing.AbstractProcessor;
import spoon.reflect.CtModel;
import spoon.reflect.declaration.CtType;
import spoon.reflect.visitor.AstParentConsistencyChecker;

/**
* FluentLauncherTest
*/
public class FluentLauncherTest {
@Rule
public TemporaryFolder folderFactory = new TemporaryFolder();

@Test
public void testSimpleUsage() throws IOException {
CtModel model = new FluentLauncher().inputResource("src/test/resources/deprecated/input")
.noClasspath(true)
.outputDirectory(folderFactory.newFolder().getPath())
.buildModel();
assertNotNull(model);
}

@Test
public void testProcessor() throws IOException {
List<String> output = new ArrayList<>();
List<String> output2 = new ArrayList<>();
new FluentLauncher().inputResource("src/test/resources/deprecated/input")
.outputDirectory(folderFactory.newFolder().getPath())
.processor(Arrays.asList(new AbstractProcessor<CtType<?>>() {
public void process(CtType<?> element) {
output.add(element.toString());
}
}))
.processor(new AbstractProcessor<CtType<?>>() {
public void process(CtType<?> element) {
output2.add(element.toString());
}
})
.noClasspath(true)
.buildModel();
// shouldn't be empty after processor usage.
assertTrue(!output.isEmpty());
// both lists should be same because same processor type
assertEquals(output, output2);
}

@Test
public void testConsistency() throws IOException {
new FluentLauncher().inputResource("src/test/resources/deprecated/input")
.noClasspath(true)
.complianceLevel(11)
.disableConsistencyChecks()
.outputDirectory(folderFactory.newFolder().getPath())
.buildModel()
.getUnnamedModule()
.accept(new AstParentConsistencyChecker());
}

@Test
public void testSettings() throws IOException {
CtModel model = new FluentLauncher().inputResource("src/test/resources/deprecated/input")
.noClasspath(true)
.encoding(Charset.defaultCharset())
.autoImports(true)
.outputDirectory(folderFactory.newFolder())
.buildModel();
assertNotNull(model);
}

/**
* shows using the FluentLauncher with different launchers.
*
* @throws IOException
*/
@Test
public void testMavenLauncher() throws IOException {
CtModel model = new FluentLauncher(new MavenLauncher("./pom.xml", MavenLauncher.SOURCE_TYPE.ALL_SOURCE))
.outputDirectory(folderFactory.newFolder().getPath())
.buildModel();
assertNotNull(model);
}
}