Merge branch '5-create-pluginexecutor' into 'master'

Resolve "Create PluginExecutor"

Closes #5

See merge request bartlomiej.pluta/esa-tool!5
This commit is contained in:
Bartłomiej Pluta
2019-04-02 15:13:56 +00:00
22 changed files with 465 additions and 53 deletions

View File

@@ -20,6 +20,7 @@ dependencies {
annotationProcessor "org.projectlombok:lombok:${lombokVersion}"
compile "commons-cli:commons-cli:${commonCliVersion}"
compile "io.vavr:vavr:${vavrVersion}"
compile "com.github.javaparser:javaparser-core:${javaParserVersion}"
}
jar {

View File

@@ -3,4 +3,5 @@ ext {
lombokVersion = '1.18.6'
commonCliVersion = '1.4'
vavrVersion = '1.0.0-alpha-2'
javaParserVersion = '3.13.4'
}

View File

@@ -0,0 +1,20 @@
package com.bartek.esa.core.archetype;
import com.bartek.esa.core.xml.XmlHelper;
import com.bartek.esa.file.matcher.GlobMatcher;
import java.io.File;
public abstract class AndroidManifestPlugin extends XmlPlugin {
private final GlobMatcher globMatcher;
public AndroidManifestPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
this.globMatcher = globMatcher;
}
@Override
public boolean supports(File file) {
return globMatcher.fileMatchesGlobPattern(file, "**/AndroidManifest.xml");
}
}

View File

@@ -0,0 +1,55 @@
package com.bartek.esa.core.archetype;
import com.bartek.esa.core.model.enumeration.Severity;
import com.bartek.esa.core.model.object.Issue;
import org.w3c.dom.Document;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
public abstract class BasePlugin implements Plugin {
private List<Issue> issues = new ArrayList<>();
private Document manifest;
private File file;
@Override
public void update(File file, Document manifest) {
this.file = file;
this.manifest = manifest;
this.issues.clear();
}
@Override
public List<Issue> runForIssues() {
run(file);
return issues;
}
protected abstract void run(File file);
protected void addIssue(Severity severity, Integer lineNumber, String line) {
addIssue(severity, "", lineNumber, line);
}
protected void addIssue(Severity severity, String descriptionCode, Integer lineNumber, String line) {
Issue issue = Issue.builder()
.severity(severity)
.issuer(this.getClass())
.descriptionCode(descriptionCode)
.file(file)
.lineNumber(lineNumber)
.line(line)
.build();
issues.add(issue);
}
protected File getOriginalFile() {
return file;
}
protected Document getManifest() {
return manifest;
}
}

View File

@@ -0,0 +1,30 @@
package com.bartek.esa.core.archetype;
import com.bartek.esa.error.EsaException;
import com.bartek.esa.file.matcher.GlobMatcher;
import com.github.javaparser.StaticJavaParser;
import com.github.javaparser.ast.CompilationUnit;
import io.vavr.control.Try;
import java.io.File;
public abstract class JavaPlugin extends BasePlugin {
private final GlobMatcher globMatcher;
public JavaPlugin(GlobMatcher globMatcher) {
this.globMatcher = globMatcher;
}
@Override
public boolean supports(File file) {
return globMatcher.fileMatchesGlobPattern(file, "**/*.java");
}
@Override
protected void run(File file) {
CompilationUnit compilationUnit = Try.of(() -> StaticJavaParser.parse(file)).getOrElseThrow(EsaException::new);
run(compilationUnit);
}
public abstract void run(CompilationUnit compilationUnit);
}

View File

@@ -0,0 +1,13 @@
package com.bartek.esa.core.archetype;
import com.bartek.esa.core.model.object.Issue;
import org.w3c.dom.Document;
import java.io.File;
import java.util.List;
public interface Plugin {
boolean supports(File file);
void update(File file, Document manifest);
List<Issue> runForIssues();
}

View File

@@ -0,0 +1,20 @@
package com.bartek.esa.core.archetype;
import com.bartek.esa.core.xml.XmlHelper;
import com.bartek.esa.file.matcher.GlobMatcher;
import java.io.File;
public abstract class ResourceLayoutPlugin extends XmlPlugin {
private final GlobMatcher globMatcher;
public ResourceLayoutPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
super(globMatcher, xmlHelper);
this.globMatcher = globMatcher;
}
@Override
public boolean supports(File file) {
return globMatcher.fileMatchesGlobPattern(file, "**/res/layout*/*.xml");
}
}

View File

@@ -0,0 +1,35 @@
package com.bartek.esa.core.archetype;
import com.bartek.esa.core.xml.XmlHelper;
import com.bartek.esa.file.matcher.GlobMatcher;
import org.w3c.dom.Document;
import javax.xml.namespace.QName;
import java.io.File;
public abstract class XmlPlugin extends BasePlugin {
private final GlobMatcher globMatcher;
private final XmlHelper xmlHelper;
public XmlPlugin(GlobMatcher globMatcher, XmlHelper xmlHelper) {
this.globMatcher = globMatcher;
this.xmlHelper = xmlHelper;
}
@Override
public boolean supports(File file) {
return globMatcher.fileMatchesGlobPattern(file, "**/*.xml");
}
@Override
protected void run(File file) {
Document xml = xmlHelper.parseXml(file);
run(xml);
}
protected abstract void run(Document xml);
protected Object xPath(Document xml, String expression, QName returnType) {
return xmlHelper.xPath(xml, expression, returnType);
}
}

View File

@@ -0,0 +1,26 @@
package com.bartek.esa.core.desc.provider;
import com.bartek.esa.core.model.object.Issue;
import com.bartek.esa.error.EsaException;
import io.vavr.control.Try;
import javax.inject.Inject;
import java.util.Optional;
import java.util.Properties;
public class DescriptionProvider {
private static final String DESCRIPTION_FILE = "description.properties";
private final Properties descriptions = new Properties();
@Inject
public DescriptionProvider() {
Optional.ofNullable(DescriptionProvider.class.getClassLoader().getResourceAsStream(DESCRIPTION_FILE))
.ifPresent(p -> Try.run(() -> descriptions.load(p)).getOrElseThrow(EsaException::new));
}
public String getDescriptionForIssue(Issue issue) {
String code = issue.getIssuer().getCanonicalName() + issue.getDescriptionCode();
String description = descriptions.getProperty(code);
return description != null && !description.isEmpty() ? description : "No description provided.";
}
}

View File

@@ -0,0 +1,32 @@
package com.bartek.esa.core.di;
import com.bartek.esa.core.desc.provider.DescriptionProvider;
import com.bartek.esa.core.executor.PluginExecutor;
import com.bartek.esa.core.java.JavaSyntaxRegexProvider;
import com.bartek.esa.core.xml.XmlHelper;
import dagger.Module;
import dagger.Provides;
@Module
public class CoreModule {
@Provides
public PluginExecutor pluginExecutor(XmlHelper xmlHelper) {
return new PluginExecutor(xmlHelper);
}
@Provides
public JavaSyntaxRegexProvider javaSyntaxRegexProvider() {
return new JavaSyntaxRegexProvider();
}
@Provides
public DescriptionProvider descriptionProvider() {
return new DescriptionProvider();
}
@Provides
public XmlHelper xmlHelper() {
return new XmlHelper();
}
}

View File

@@ -0,0 +1,40 @@
package com.bartek.esa.core.executor;
import com.bartek.esa.core.archetype.Plugin;
import com.bartek.esa.core.model.object.Issue;
import com.bartek.esa.core.xml.XmlHelper;
import org.w3c.dom.Document;
import javax.inject.Inject;
import java.io.File;
import java.util.List;
import static java.util.stream.Collectors.toList;
public class PluginExecutor {
private final XmlHelper xmlHelper;
@Inject
public PluginExecutor(XmlHelper xmlHelper) {
this.xmlHelper = xmlHelper;
}
public List<Issue> executeForFiles(File manifest, List<File> files, List<Plugin> plugins) {
return files.stream()
.map(file -> executeForFile(manifest, file, plugins))
.flatMap(List::stream)
.collect(toList());
}
private List<Issue> executeForFile(File manifest, File file, List<Plugin> plugins) {
Document xmlManifest = xmlHelper.parseXml(manifest);
return plugins.stream()
.filter(plugin -> plugin.supports(file))
.map(plugin -> {
plugin.update(file, xmlManifest);
return plugin.runForIssues();
})
.flatMap(List::stream)
.collect(toList());
}
}

View File

@@ -0,0 +1,17 @@
package com.bartek.esa.core.java;
import javax.inject.Inject;
import static java.lang.String.format;
public class JavaSyntaxRegexProvider {
@Inject
public JavaSyntaxRegexProvider() {
}
public String methodInvocation(String methodName) {
return format("^%s\\s*\\($", methodName);
}
}

View File

@@ -0,0 +1,6 @@
package com.bartek.esa.core.model.enumeration;
public enum Severity {
WARNING,
ERROR
}

View File

@@ -0,0 +1,18 @@
package com.bartek.esa.core.model.object;
import com.bartek.esa.core.model.enumeration.Severity;
import lombok.Builder;
import lombok.Data;
import java.io.File;
@Data
@Builder
public class Issue {
private final Class<?> issuer;
private final Severity severity;
private final String descriptionCode;
private final File file;
private final Integer lineNumber;
private final String line;
}

View File

@@ -0,0 +1,31 @@
package com.bartek.esa.core.xml;
import com.bartek.esa.error.EsaException;
import io.vavr.control.Try;
import org.w3c.dom.Document;
import javax.inject.Inject;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPathFactory;
import java.io.File;
public class XmlHelper {
@Inject
public XmlHelper() {
}
public Document parseXml(File file) {
DocumentBuilderFactory factory = DocumentBuilderFactory.newDefaultInstance();
DocumentBuilder builder = Try.of(factory::newDocumentBuilder).getOrElseThrow(EsaException::new);
return Try.of(() -> builder.parse(file)).getOrElseThrow(EsaException::new);
}
public Object xPath(Document xml, String expression, QName returnType) {
return Try.of(() -> XPathFactory.newDefaultInstance().newXPath().evaluate(expression, xml, returnType))
.getOrElseThrow(EsaException::new);
}
}

View File

@@ -2,6 +2,7 @@ package com.bartek.esa.di;
import com.bartek.esa.EsaMain;
import com.bartek.esa.cli.di.CliModule;
import com.bartek.esa.core.di.CoreModule;
import com.bartek.esa.decompiler.di.DecompilerModule;
import com.bartek.esa.dispatcher.di.DispatcherModule;
import com.bartek.esa.file.di.FileModule;
@@ -11,7 +12,8 @@ import dagger.Component;
CliModule.class,
DispatcherModule.class,
FileModule.class,
DecompilerModule.class
DecompilerModule.class,
CoreModule.class
})
public interface DependencyInjector {
EsaMain esa();

View File

@@ -1,5 +1,7 @@
package com.bartek.esa.file.di;
import com.bartek.esa.file.matcher.GlobMatcher;
import com.bartek.esa.file.provider.FileContentProvider;
import com.bartek.esa.file.provider.FileProvider;
import dagger.Module;
import dagger.Provides;
@@ -8,7 +10,17 @@ import dagger.Provides;
public class FileModule {
@Provides
public FileProvider fileProvider() {
return new FileProvider();
public FileProvider fileProvider(GlobMatcher globMatcher) {
return new FileProvider(globMatcher);
}
@Provides
public FileContentProvider fileContentProvider() {
return new FileContentProvider();
}
@Provides
public GlobMatcher globMatcher() {
return new GlobMatcher();
}
}

View File

@@ -0,0 +1,22 @@
package com.bartek.esa.file.matcher;
import javax.inject.Inject;
import java.io.File;
import java.nio.file.FileSystems;
import java.nio.file.Path;
public class GlobMatcher {
@Inject
public GlobMatcher() {
}
public boolean fileMatchesGlobPattern(File file, String globPattern) {
return pathMatchesGlobPattern(file.toPath(), globPattern);
}
public boolean pathMatchesGlobPattern(Path path, String globPattern) {
return FileSystems.getDefault().getPathMatcher("glob:" + globPattern).matches(path);
}
}

View File

@@ -0,0 +1,12 @@
package com.bartek.esa.file.model;
import lombok.Builder;
import lombok.Data;
@Data
@Builder
public class FoundLine {
private int number;
private String line;
private int position;
}

View File

@@ -0,0 +1,63 @@
package com.bartek.esa.file.provider;
import com.bartek.esa.error.EsaException;
import com.bartek.esa.file.model.FoundLine;
import io.vavr.control.Try;
import javax.inject.Inject;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
public class FileContentProvider {
@Inject
public FileContentProvider() {
}
public List<FoundLine> findPhraseByLines(File file, String regex) {
try {
return tryToFindPhraseByLines(file, regex);
} catch (IOException e) {
throw new EsaException(e);
}
}
private List<FoundLine> tryToFindPhraseByLines(File file, String regex) throws IOException {
LineNumberReader lineNumberReader = readForLines(file);
List<FoundLine> foundLines = new ArrayList<>();
String line;
Pattern pattern = Pattern.compile(regex);
while ((line = lineNumberReader.readLine()) != null) {
if (pattern.matcher(line).find()) {
foundLines.add(FoundLine.builder()
.line(line)
.number(lineNumberReader.getLineNumber())
.build());
}
}
return foundLines;
}
private LineNumberReader readForLines(File file) {
return Try.of(() -> new LineNumberReader(new FileReader(file)))
.getOrElseThrow(EsaException::new);
}
public String readFile(File file) {
StringBuilder content = new StringBuilder();
Try.run(() -> Files.lines(file.toPath())
.map(line -> line + "\n")
.forEach(content::append));
return content.toString();
}
}

View File

@@ -1,6 +1,7 @@
package com.bartek.esa.file.provider;
import com.bartek.esa.error.EsaException;
import com.bartek.esa.file.matcher.GlobMatcher;
import io.vavr.control.Try;
import javax.inject.Inject;
@@ -12,10 +13,12 @@ import java.util.Set;
import java.util.stream.Collectors;
public class FileProvider {
private final GlobMatcher globMatcher;
@Inject
public FileProvider() {
public FileProvider(GlobMatcher globMatcher) {
this.globMatcher = globMatcher;
}
public File createTemporaryDirectory() {
@@ -24,58 +27,11 @@ public class FileProvider {
.toFile();
}
public String readFile(File file) {
StringBuilder content = new StringBuilder();
Try.run(() -> Files.lines(file.toPath())
.map(line -> line + "\n")
.forEach(content::append));
return content.toString();
}
public Set<File> findFilesRecursively(String path, String globPattern) {
return findFilesRecursivelyInSubpath(path, "", globPattern);
}
public Set<File> findFilesRecursivelyInSubpath(String path, String subpath, String globPattern) {
public Set<File> getGlobMatchedFiles(String path, String globPattern) {
return Try.of(() -> Files.walk(Paths.get(path))
.filter(p -> p.toString().contains(subpath))
.filter(p -> matchesGlobPattern(p.getFileName().toString(), globPattern))
.filter(p -> globMatcher.pathMatchesGlobPattern(p, globPattern))
.map(Path::toFile)
.collect(Collectors.toSet()))
.getOrElseThrow(EsaException::new);
}
public boolean matchesGlobPattern(String filename, String globPattern) {
return filename.matches(createRegexFromGlob(globPattern));
}
private String createRegexFromGlob(String glob) {
StringBuilder out = new StringBuilder("^");
glob
.chars()
.mapToObj(i -> (char) i)
.map(this::globCharacterToRegexString)
.forEach(out::append);
out.append('$');
return out.toString();
}
private String globCharacterToRegexString(char character) {
switch (character) {
case '*':
return ".*";
case '?':
return ".";
case '.':
return "\\.";
case '\\':
return "\\\\";
default:
return String.valueOf(character);
}
}
}