diff --git a/build.gradle b/build.gradle index c64d2a7..58a64e2 100644 --- a/build.gradle +++ b/build.gradle @@ -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 { diff --git a/dependency-versions.gradle b/dependency-versions.gradle index a9c4422..879c4c0 100644 --- a/dependency-versions.gradle +++ b/dependency-versions.gradle @@ -3,4 +3,5 @@ ext { lombokVersion = '1.18.6' commonCliVersion = '1.4' vavrVersion = '1.0.0-alpha-2' + javaParserVersion = '3.13.4' } \ No newline at end of file diff --git a/src/main/java/com/bartek/esa/core/archetype/AndroidManifestPlugin.java b/src/main/java/com/bartek/esa/core/archetype/AndroidManifestPlugin.java new file mode 100644 index 0000000..b01718e --- /dev/null +++ b/src/main/java/com/bartek/esa/core/archetype/AndroidManifestPlugin.java @@ -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"); + } +} diff --git a/src/main/java/com/bartek/esa/core/archetype/BasePlugin.java b/src/main/java/com/bartek/esa/core/archetype/BasePlugin.java new file mode 100644 index 0000000..391ac16 --- /dev/null +++ b/src/main/java/com/bartek/esa/core/archetype/BasePlugin.java @@ -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 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 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; + } +} diff --git a/src/main/java/com/bartek/esa/core/archetype/JavaPlugin.java b/src/main/java/com/bartek/esa/core/archetype/JavaPlugin.java new file mode 100644 index 0000000..f735329 --- /dev/null +++ b/src/main/java/com/bartek/esa/core/archetype/JavaPlugin.java @@ -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); +} diff --git a/src/main/java/com/bartek/esa/core/archetype/Plugin.java b/src/main/java/com/bartek/esa/core/archetype/Plugin.java new file mode 100644 index 0000000..ddbf1d4 --- /dev/null +++ b/src/main/java/com/bartek/esa/core/archetype/Plugin.java @@ -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 runForIssues(); +} diff --git a/src/main/java/com/bartek/esa/core/archetype/ResourceLayoutPlugin.java b/src/main/java/com/bartek/esa/core/archetype/ResourceLayoutPlugin.java new file mode 100644 index 0000000..aa924e7 --- /dev/null +++ b/src/main/java/com/bartek/esa/core/archetype/ResourceLayoutPlugin.java @@ -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"); + } +} diff --git a/src/main/java/com/bartek/esa/core/archetype/XmlPlugin.java b/src/main/java/com/bartek/esa/core/archetype/XmlPlugin.java new file mode 100644 index 0000000..c1bbedd --- /dev/null +++ b/src/main/java/com/bartek/esa/core/archetype/XmlPlugin.java @@ -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); + } +} diff --git a/src/main/java/com/bartek/esa/core/desc/provider/DescriptionProvider.java b/src/main/java/com/bartek/esa/core/desc/provider/DescriptionProvider.java new file mode 100644 index 0000000..b5ac646 --- /dev/null +++ b/src/main/java/com/bartek/esa/core/desc/provider/DescriptionProvider.java @@ -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."; + } +} diff --git a/src/main/java/com/bartek/esa/core/di/CoreModule.java b/src/main/java/com/bartek/esa/core/di/CoreModule.java new file mode 100644 index 0000000..59d666d --- /dev/null +++ b/src/main/java/com/bartek/esa/core/di/CoreModule.java @@ -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(); + } +} diff --git a/src/main/java/com/bartek/esa/core/executor/PluginExecutor.java b/src/main/java/com/bartek/esa/core/executor/PluginExecutor.java new file mode 100644 index 0000000..e114489 --- /dev/null +++ b/src/main/java/com/bartek/esa/core/executor/PluginExecutor.java @@ -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 executeForFiles(File manifest, List files, List plugins) { + return files.stream() + .map(file -> executeForFile(manifest, file, plugins)) + .flatMap(List::stream) + .collect(toList()); + } + + private List executeForFile(File manifest, File file, List 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()); + } +} diff --git a/src/main/java/com/bartek/esa/core/java/JavaSyntaxRegexProvider.java b/src/main/java/com/bartek/esa/core/java/JavaSyntaxRegexProvider.java new file mode 100644 index 0000000..d15c375 --- /dev/null +++ b/src/main/java/com/bartek/esa/core/java/JavaSyntaxRegexProvider.java @@ -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); + } +} diff --git a/src/main/java/com/bartek/esa/core/model/enumeration/Severity.java b/src/main/java/com/bartek/esa/core/model/enumeration/Severity.java new file mode 100644 index 0000000..78b81ea --- /dev/null +++ b/src/main/java/com/bartek/esa/core/model/enumeration/Severity.java @@ -0,0 +1,6 @@ +package com.bartek.esa.core.model.enumeration; + +public enum Severity { + WARNING, + ERROR +} diff --git a/src/main/java/com/bartek/esa/core/model/object/Issue.java b/src/main/java/com/bartek/esa/core/model/object/Issue.java new file mode 100644 index 0000000..249250f --- /dev/null +++ b/src/main/java/com/bartek/esa/core/model/object/Issue.java @@ -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; +} diff --git a/src/main/java/com/bartek/esa/core/xml/XmlHelper.java b/src/main/java/com/bartek/esa/core/xml/XmlHelper.java new file mode 100644 index 0000000..7a9ceee --- /dev/null +++ b/src/main/java/com/bartek/esa/core/xml/XmlHelper.java @@ -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); + } +} diff --git a/src/main/java/com/bartek/esa/di/DependencyInjector.java b/src/main/java/com/bartek/esa/di/DependencyInjector.java index e3c449e..b05f447 100644 --- a/src/main/java/com/bartek/esa/di/DependencyInjector.java +++ b/src/main/java/com/bartek/esa/di/DependencyInjector.java @@ -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(); diff --git a/src/main/java/com/bartek/esa/file/di/FileModule.java b/src/main/java/com/bartek/esa/file/di/FileModule.java index adcbd99..815b3d4 100644 --- a/src/main/java/com/bartek/esa/file/di/FileModule.java +++ b/src/main/java/com/bartek/esa/file/di/FileModule.java @@ -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(); } } diff --git a/src/main/java/com/bartek/esa/file/matcher/GlobMatcher.java b/src/main/java/com/bartek/esa/file/matcher/GlobMatcher.java new file mode 100644 index 0000000..58d9a1d --- /dev/null +++ b/src/main/java/com/bartek/esa/file/matcher/GlobMatcher.java @@ -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); + } +} diff --git a/src/main/java/com/bartek/esa/file/model/FoundLine.java b/src/main/java/com/bartek/esa/file/model/FoundLine.java new file mode 100644 index 0000000..05cd59a --- /dev/null +++ b/src/main/java/com/bartek/esa/file/model/FoundLine.java @@ -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; +} diff --git a/src/main/java/com/bartek/esa/file/provider/FileContentProvider.java b/src/main/java/com/bartek/esa/file/provider/FileContentProvider.java new file mode 100644 index 0000000..195d908 --- /dev/null +++ b/src/main/java/com/bartek/esa/file/provider/FileContentProvider.java @@ -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 findPhraseByLines(File file, String regex) { + try { + return tryToFindPhraseByLines(file, regex); + } catch (IOException e) { + throw new EsaException(e); + } + } + + private List tryToFindPhraseByLines(File file, String regex) throws IOException { + LineNumberReader lineNumberReader = readForLines(file); + List 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(); + } +} diff --git a/src/main/java/com/bartek/esa/file/provider/FileProvider.java b/src/main/java/com/bartek/esa/file/provider/FileProvider.java index b3cbd3e..b058d37 100644 --- a/src/main/java/com/bartek/esa/file/provider/FileProvider.java +++ b/src/main/java/com/bartek/esa/file/provider/FileProvider.java @@ -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 findFilesRecursively(String path, String globPattern) { - return findFilesRecursivelyInSubpath(path, "", globPattern); - } - - public Set findFilesRecursivelyInSubpath(String path, String subpath, String globPattern) { + public Set 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); - } - } } diff --git a/src/main/resources/description.properties b/src/main/resources/description.properties new file mode 100644 index 0000000..e69de29