diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index feb5bb0f9..8ab7aa22a 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -33,7 +33,7 @@ jobs: - name: Generate coverage with JaCoCo run: mvn -V --color always -ntp clean verify -Pci - name: Upload coverage to Codecov - uses: codecov/codecov-action@v5.1.1 + uses: codecov/codecov-action@v5.1.2 with: file: 'target/site/jacoco/jacoco.xml' disable_search: true diff --git a/SUPPORTED-FORMATS.md b/SUPPORTED-FORMATS.md index 7e99e7cfb..ac480fdd8 100644 --- a/SUPPORTED-FORMATS.md +++ b/SUPPORTED-FORMATS.md @@ -1,4 +1,4 @@ - + # Supported Report Formats The static analysis model supports the following report formats. @@ -2178,6 +2178,27 @@ analyze - iccxxxxcompiler_opts cstat2.cFor details check the IAR C- - + + + vale + + + - + + + + Vale + + + + **/vale-report.json + + + + + :bulb: Reads vale report files. Use the flag --output=JSON + + valgrind diff --git a/badges/line-coverage.svg b/badges/line-coverage.svg index 963d51b2d..724589379 100644 --- a/badges/line-coverage.svg +++ b/badges/line-coverage.svg @@ -1,5 +1,5 @@ - - Lines: 94% + + Lines: 93% @@ -13,8 +13,8 @@ \ No newline at end of file diff --git a/doc/dependency-graph.puml b/doc/dependency-graph.puml index 40470d5b4..59ef5c357 100644 --- a/doc/dependency-graph.puml +++ b/doc/dependency-graph.puml @@ -12,7 +12,7 @@ rectangle "commons-io\n\n2.18.0" as commons_io_commons_io_jar rectangle "commons-digester3\n\n3.2" as org_apache_commons_commons_digester3_jar rectangle "cglib\n\n2.2.2" as cglib_cglib_jar rectangle "commons-logging\n\n1.3.4" as commons_logging_commons_logging_jar -rectangle "commons-beanutils\n\n1.9.4" as commons_beanutils_commons_beanutils_jar +rectangle "commons-beanutils\n\n1.10.0" as commons_beanutils_commons_beanutils_jar rectangle "commons-collections\n\n3.2.2" as commons_collections_commons_collections_jar rectangle "commons-text\n\n1.13.0" as org_apache_commons_commons_text_jar rectangle "violations-lib\n\n1.157.3" as se_bjurr_violations_violations_lib_jar @@ -32,19 +32,19 @@ rectangle "gson\n\n2.11.0" as com_google_code_gson_gson_jar rectangle "Saxon-HE\n\n12.5" as net_sf_saxon_Saxon_HE_jar rectangle "xmlresolver\n\n5.2.2" as org_xmlresolver_xmlresolver_jar rectangle "xmlresolver\ndata\n5.2.2" as org_xmlresolver_xmlresolver_jar_data -rectangle "pmd-core\n\n7.8.0" as net_sourceforge_pmd_pmd_core_jar +rectangle "pmd-core\n\n7.9.0" as net_sourceforge_pmd_pmd_core_jar rectangle "slf4j-api\n\n2.0.16" as org_slf4j_slf4j_api_jar rectangle "antlr4-runtime\n\n4.9.3" as org_antlr_antlr4_runtime_jar -rectangle "checker-qual\n\n3.48.1" as org_checkerframework_checker_qual_jar +rectangle "checker-qual\n\n3.48.3" as org_checkerframework_checker_qual_jar rectangle "pcollections\n\n4.0.2" as org_pcollections_pcollections_jar rectangle "nice-xml-messages\n\n3.1" as com_github_oowekyala_ooxml_nice_xml_messages_jar -rectangle "pmd-java\n\n7.8.0" as net_sourceforge_pmd_pmd_java_jar +rectangle "pmd-java\n\n7.9.0" as net_sourceforge_pmd_pmd_java_jar rectangle "json\n\n20240303" as org_json_json_jar rectangle "json-smart\n\n2.5.1" as net_minidev_json_smart_jar rectangle "accessors-smart\n\n2.5.1" as net_minidev_accessors_smart_jar -rectangle "codingstyle\n\n5.2.0" as edu_hm_hafner_codingstyle_jar +rectangle "codingstyle\n\n5.5.0" as edu_hm_hafner_codingstyle_jar rectangle "spotbugs-annotations\n\n4.8.6" as com_github_spotbugs_spotbugs_annotations_jar -rectangle "error_prone_annotations\n\n2.35.1" as com_google_errorprone_error_prone_annotations_jar +rectangle "error_prone_annotations\n\n2.36.0" as com_google_errorprone_error_prone_annotations_jar rectangle "commons-lang3\n\n3.17.0" as org_apache_commons_commons_lang3_jar rectangle "streamex\n\n0.8.3" as one_util_streamex_jar edu_hm_hafner_analysis_model_jar -[#000000]-> org_jsoup_jsoup_jar diff --git a/pom.xml b/pom.xml index 3d5c923f6..bf008b6a3 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ edu.hm.hafner codingstyle-pom - 5.5.0 + 5.9.0 @@ -62,7 +62,7 @@ 1.37 2.18.0 3.2 - 1.9.4 + 1.10.0 1.13.0 1.4.0 2.0.16 @@ -70,7 +70,7 @@ 1.18.3 20240303 2.5.1 - 7.8.0 + 7.9.0 4.8.6 1.8 @@ -292,6 +292,13 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + @{argLine} -Dorg.apache.logging.log4j.level=OFF + + org.sonatype.plugins nexus-staging-maven-plugin @@ -352,6 +359,7 @@ .*LineRangeList .*SecureDigester + .*Benchmark.* edu.hm.hafner.analysis.parser.* edu.hm.hafner.analysis.PackageDetectors.* @@ -383,14 +391,6 @@ - - org.revapi - revapi-maven-plugin - - true - false - - diff --git a/src/main/java/edu/hm/hafner/analysis/AbstractModuleDetector.java b/src/main/java/edu/hm/hafner/analysis/AbstractModuleDetector.java index cca86816f..cfc3bf0c4 100644 --- a/src/main/java/edu/hm/hafner/analysis/AbstractModuleDetector.java +++ b/src/main/java/edu/hm/hafner/analysis/AbstractModuleDetector.java @@ -5,7 +5,7 @@ import org.apache.commons.lang3.StringUtils; -import edu.hm.hafner.analysis.ModuleDetector.FileSystem; +import edu.hm.hafner.analysis.ModuleDetectorRunner.FileSystemFacade; /** * Abstract class for all Module Detectors. @@ -15,7 +15,7 @@ abstract class AbstractModuleDetector { static final String ALL_DIRECTORIES = "**/"; static final String PLUS = ", "; - private final FileSystem factory; + private final FileSystemFacade factory; /** * Collects all projects of a specific type. @@ -32,8 +32,8 @@ abstract class AbstractModuleDetector { */ abstract String getPattern(); - AbstractModuleDetector(final FileSystem fileSystem) { - factory = fileSystem; + AbstractModuleDetector(final FileSystemFacade fileSystemFacade) { + factory = fileSystemFacade; } void addMapping(final Map mapping, final String fileName, final String suffix, @@ -43,7 +43,7 @@ void addMapping(final Map mapping, final String fileName, final } } - public FileSystem getFactory() { + public FileSystemFacade getFactory() { return factory; } } diff --git a/src/main/java/edu/hm/hafner/analysis/AbstractPackageDetector.java b/src/main/java/edu/hm/hafner/analysis/AbstractPackageDetector.java deleted file mode 100644 index e4f1ac274..000000000 --- a/src/main/java/edu/hm/hafner/analysis/AbstractPackageDetector.java +++ /dev/null @@ -1,99 +0,0 @@ -package edu.hm.hafner.analysis; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.Charset; -import java.nio.file.InvalidPathException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Stream; - -import org.apache.commons.io.input.BOMInputStream; - -import edu.hm.hafner.util.VisibleForTesting; - -import static edu.hm.hafner.analysis.PackageDetectors.*; - -/** - * Base class for package detectors. - * - * @author Ullrich Hafner - */ -abstract class AbstractPackageDetector { - private final FileSystem fileSystem; - - /** - * Creates a new instance of {@link AbstractPackageDetector}. - * - * @param fileSystem - * file system facade - */ - AbstractPackageDetector(final FileSystem fileSystem) { - this.fileSystem = fileSystem; - } - - /** - * Detects the package or namespace name of the specified file. - * - * @param fileName - * the file name of the file to scan - * @param charset - * the charset to use when reading the source files - * - * @return the detected package or namespace name - */ - public String detectPackageName(final String fileName, final Charset charset) { - if (accepts(fileName)) { - try (var stream = fileSystem.openFile(fileName)) { - return detectPackageName(stream, charset); - } - catch (IOException | InvalidPathException ignore) { - // ignore IO errors - } - } - return UNDEFINED_PACKAGE; - } - - @VisibleForTesting - String detectPackageName(final InputStream stream, final Charset charset) throws IOException { - try (var buffer = new BufferedReader(new InputStreamReader(BOMInputStream.builder().setInputStream(stream).get(), charset))) { - return detectPackageName(buffer.lines()); - } - } - - /** - * Detects the package or namespace name of the specified input stream. The stream will be closed automatically by - * the caller of this method. - * - * @param lines - * the content of the file to scan - * - * @return the detected package or namespace name - */ - String detectPackageName(final Stream lines) { - var pattern = getPattern(); - return lines.map(pattern::matcher) - .filter(Matcher::matches) - .findFirst() - .map(matcher -> matcher.group(1)) - .orElse(UNDEFINED_PACKAGE).trim(); - } - - /** - * Returns the Pattern for the Package Name in this kind of file. - * @return the Pattern. - */ - abstract Pattern getPattern(); - - /** - * Returns whether this classifier accepts the specified file for processing. - * - * @param fileName - * the file name - * - * @return {@code true} if the classifier accepts the specified file for processing. - */ - abstract boolean accepts(String fileName); -} diff --git a/src/main/java/edu/hm/hafner/analysis/AntModuleDetector.java b/src/main/java/edu/hm/hafner/analysis/AntModuleDetector.java index 24e495200..04a5440a6 100644 --- a/src/main/java/edu/hm/hafner/analysis/AntModuleDetector.java +++ b/src/main/java/edu/hm/hafner/analysis/AntModuleDetector.java @@ -8,16 +8,16 @@ import org.apache.commons.lang3.StringUtils; import org.xml.sax.SAXException; -import edu.hm.hafner.analysis.ModuleDetector.FileSystem; +import edu.hm.hafner.analysis.ModuleDetectorRunner.FileSystemFacade; /** * Detects module names by parsing the name of a source file, the ANT build.xml. */ -public class AntModuleDetector extends AbstractModuleDetector { +class AntModuleDetector extends AbstractModuleDetector { static final String ANT_PROJECT = "build.xml"; - AntModuleDetector(final FileSystem fileSystem) { - super(fileSystem); + AntModuleDetector(final FileSystemFacade fileSystemFacade) { + super(fileSystemFacade); } @Override @@ -44,7 +44,7 @@ void collectProjects(final Map mapping, final List proje */ private String parseBuildXml(final String buildXml) { try (var file = getFactory().open(buildXml)) { - var digester = new SecureDigester(ModuleDetector.class); + var digester = new SecureDigester(ModuleDetectorRunner.class); digester.push(new StringBuilder()); var xPath = "project"; diff --git a/src/main/java/edu/hm/hafner/analysis/CSharpNamespaceDetector.java b/src/main/java/edu/hm/hafner/analysis/CSharpNamespaceDetector.java deleted file mode 100644 index 702852708..000000000 --- a/src/main/java/edu/hm/hafner/analysis/CSharpNamespaceDetector.java +++ /dev/null @@ -1,34 +0,0 @@ -package edu.hm.hafner.analysis; - -import java.util.regex.Pattern; - -import edu.hm.hafner.analysis.PackageDetectors.FileSystem; -import edu.hm.hafner.util.VisibleForTesting; - -/** - * Detects the namespace of a C# workspace file. - * - * @author Ullrich Hafner - */ -class CSharpNamespaceDetector extends AbstractPackageDetector { - private static final Pattern NAMESPACE_PATTERN = Pattern.compile("^\\s*namespace\\s+([^{]*)\\s*\\{?\\s*$"); - - CSharpNamespaceDetector() { - this(new FileSystem()); - } - - @VisibleForTesting - CSharpNamespaceDetector(final FileSystem fileSystem) { - super(fileSystem); - } - - @Override - public boolean accepts(final String fileName) { - return fileName.endsWith(".cs"); - } - - @Override - Pattern getPattern() { - return NAMESPACE_PATTERN; - } -} diff --git a/src/main/java/edu/hm/hafner/analysis/GradleModuleDetector.java b/src/main/java/edu/hm/hafner/analysis/GradleModuleDetector.java index b03fdf10b..82dd37636 100644 --- a/src/main/java/edu/hm/hafner/analysis/GradleModuleDetector.java +++ b/src/main/java/edu/hm/hafner/analysis/GradleModuleDetector.java @@ -11,12 +11,12 @@ import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang3.StringUtils; -import edu.hm.hafner.analysis.ModuleDetector.FileSystem; +import edu.hm.hafner.analysis.ModuleDetectorRunner.FileSystemFacade; /** * Detects module names by parsing gradle source files. */ -public class GradleModuleDetector extends AbstractModuleDetector { +class GradleModuleDetector extends AbstractModuleDetector { static final String BUILD_GRADLE = "build.gradle"; static final String BUILD_GRADLE_KTS = "build.gradle.kts"; static final String SETTINGS_GRADLE = "settings.gradle"; @@ -29,8 +29,8 @@ public class GradleModuleDetector extends AbstractModuleDetector { private static final Pattern RE_GRADLE_SET_PROJECT_NAME = Pattern.compile("^\\s*rootProject\\.(name\\s*=|setName\\(?)\\s*['\"]([^'\"]*)['\"]\\)?"); - GradleModuleDetector(final FileSystem fileSystem) { - super(fileSystem); + GradleModuleDetector(final FileSystemFacade fileSystemFacade) { + super(fileSystemFacade); } @Override diff --git a/src/main/java/edu/hm/hafner/analysis/Issue.java b/src/main/java/edu/hm/hafner/analysis/Issue.java index f4cb3229a..36ae7bbf8 100644 --- a/src/main/java/edu/hm/hafner/analysis/Issue.java +++ b/src/main/java/edu/hm/hafner/analysis/Issue.java @@ -375,7 +375,7 @@ public static Predicate byType(final String type) { } /** - * Called after de-serialization to improve the memory usage. + * Called after deserialization to improve the memory usage. * * @return this */ diff --git a/src/main/java/edu/hm/hafner/analysis/IssueDifference.java b/src/main/java/edu/hm/hafner/analysis/IssueDifference.java index 195bd1620..0844dff55 100644 --- a/src/main/java/edu/hm/hafner/analysis/IssueDifference.java +++ b/src/main/java/edu/hm/hafner/analysis/IssueDifference.java @@ -60,8 +60,8 @@ public IssueDifference(final Report currentIssues, final String referenceId, final Report referenceIssues, final Map includes) { newIssues = currentIssues.copy(); fixedIssues = referenceIssues.copy(); - outstandingIssues = new Report(); - newIssuesInChangedCode = new Report(); + outstandingIssues = referenceIssues.copyEmptyInstance(); + newIssuesInChangedCode = currentIssues.copyEmptyInstance(); referencesByHash = new HashMap<>(); referencesByFingerprint = new HashMap<>(); diff --git a/src/main/java/edu/hm/hafner/analysis/IssueParser.java b/src/main/java/edu/hm/hafner/analysis/IssueParser.java index 1641161b1..cd4f1c7d1 100644 --- a/src/main/java/edu/hm/hafner/analysis/IssueParser.java +++ b/src/main/java/edu/hm/hafner/analysis/IssueParser.java @@ -7,6 +7,7 @@ import org.apache.commons.lang3.StringUtils; +import edu.hm.hafner.analysis.Report.IssueType; import edu.hm.hafner.util.SecureXmlParserFactory; import edu.umd.cs.findbugs.annotations.CheckForNull; @@ -18,7 +19,7 @@ @SuppressWarnings("checkstyle:JavadocVariable") public abstract class IssueParser implements Serializable { @Serial - private static final long serialVersionUID = 200992696185460268L; + private static final long serialVersionUID = 5L; // release 13.0.0 protected static final String ADDITIONAL_PROPERTIES = "additionalProperties"; protected static final String CATEGORY = "category"; @@ -35,42 +36,71 @@ public abstract class IssueParser implements Serializable { protected static final String LINE_START = "lineStart"; protected static final String MESSAGE = "message"; protected static final String MODULE_NAME = "moduleName"; - protected static final String ORIGIN = "origin"; protected static final String PACKAGE_NAME = "packageName"; protected static final String SEVERITY = "severity"; protected static final String TYPE = "type"; + private String id = Report.DEFAULT_ID; + private String name = Report.DEFAULT_ID; + private IssueType type = IssueType.WARNING; + /** - * Parses the specified file for issues. + * Parses a report (given by the reader factory) for issues. The name and ID of the report are set to the default + * values provided by the parser descriptor. * * @param readerFactory - * provides a reader to the reports + * factory to read input reports with a specific locale * - * @return the issues + * @return the report containing the found issues * @throws ParsingException - * Signals that during parsing a non-recoverable error has been occurred + * signals that during parsing a non-recoverable error has been occurred * @throws ParsingCanceledException - * Signals that the user has aborted the parsing + * signals that the user has aborted the parsing */ - public abstract Report parse(ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException; + public Report parse(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException { + var report = parseReport(readerFactory); + + report.setOrigin(id, name, type, readerFactory.getFileName()); + + return report; + } /** - * Parses the specified file for issues. Invokes the parser using {@link #parse(ReaderFactory)} and sets the file - * name of the report. + * Parses a report (given by the reader factory) for issues. * * @param readerFactory - * provides a reader to the reports + * factory to read input reports with a specific locale * - * @return the issues + * @return the report containing the found issues * @throws ParsingException - * Signals that during parsing a non-recoverable error has been occurred + * signals that during parsing a non-recoverable error has been occurred * @throws ParsingCanceledException - * Signals that the user has aborted the parsing + * signals that the user has aborted the parsing */ - public Report parseFile(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException { - var report = parse(readerFactory); - report.setOriginReportFile(readerFactory.getFileName()); - return report; + protected abstract Report parseReport(ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException; + + public final void setId(final String id) { + this.id = id; + } + + protected String getId() { + return id; + } + + public final void setName(final String name) { + this.name = name; + } + + protected String getName() { + return name; + } + + public final void setType(final IssueType type) { + this.type = type; + } + + protected IssueType getType() { + return type; } /** @@ -110,7 +140,8 @@ protected boolean isXmlFile(final ReaderFactory readerFactory) { * equal sequences of characters, ignoring case. * *

{@code null}s are handled without exceptions. Two {@code null} - * references are considered equal. The comparison is case insensitive.

+ * references are considered equal. The comparison is case-insensitive. + *

* *
      * equalsIgnoreCase(null, null)   = true
@@ -128,7 +159,7 @@ protected boolean isXmlFile(final ReaderFactory readerFactory) {
      * @return {@code true} if the CharSequences are equal (case-insensitive), or both {@code null}
      */
     public static boolean equalsIgnoreCase(@CheckForNull final String a, @CheckForNull final String b) {
-        return StringUtils.equals(normalize(a), normalize(b));
+        return StringUtils.equalsIgnoreCase(normalize(a), normalize(b));
     }
 
     private static String normalize(@CheckForNull final String input) {
diff --git a/src/main/java/edu/hm/hafner/analysis/JavaPackageDetector.java b/src/main/java/edu/hm/hafner/analysis/JavaPackageDetector.java
deleted file mode 100644
index 62681e6d7..000000000
--- a/src/main/java/edu/hm/hafner/analysis/JavaPackageDetector.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package edu.hm.hafner.analysis;
-
-import java.util.regex.Pattern;
-
-import edu.hm.hafner.analysis.PackageDetectors.FileSystem;
-import edu.hm.hafner.util.VisibleForTesting;
-
-/**
- * Detects the package name of a Java file.
- *
- * @author Ullrich Hafner
- */
-class JavaPackageDetector extends AbstractPackageDetector {
-    private static final Pattern PACKAGE_PATTERN = Pattern.compile(
-            "^\\s*package\\s*([a-z]+[.\\w]*)\\s*;.*");
-
-    JavaPackageDetector() {
-        this(new FileSystem());
-    }
-
-    @VisibleForTesting
-    JavaPackageDetector(final FileSystem fileSystem) {
-        super(fileSystem);
-    }
-
-    @Override
-    Pattern getPattern() {
-        return PACKAGE_PATTERN;
-    }
-
-    @Override
-    public boolean accepts(final String fileName) {
-        return fileName.endsWith(".java");
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/KotlinPackageDetector.java b/src/main/java/edu/hm/hafner/analysis/KotlinPackageDetector.java
deleted file mode 100644
index ea951dbb5..000000000
--- a/src/main/java/edu/hm/hafner/analysis/KotlinPackageDetector.java
+++ /dev/null
@@ -1,35 +0,0 @@
-package edu.hm.hafner.analysis;
-
-import java.util.regex.Pattern;
-
-import edu.hm.hafner.analysis.PackageDetectors.FileSystem;
-import edu.hm.hafner.util.VisibleForTesting;
-
-/**
- * Detects the package name of a Kotlin file.
- *
- * @author Bastian Kersting
- */
-class KotlinPackageDetector extends AbstractPackageDetector {
-    private static final Pattern PACKAGE_PATTERN = Pattern.compile(
-            "^\\s*package\\s*([a-z]+[.\\w]*)\\s*.*");
-
-    KotlinPackageDetector() {
-        this(new FileSystem());
-    }
-
-    @VisibleForTesting
-    KotlinPackageDetector(final FileSystem fileSystem) {
-        super(fileSystem);
-    }
-
-    @Override
-    Pattern getPattern() {
-        return PACKAGE_PATTERN;
-    }
-
-    @Override
-    boolean accepts(final String fileName) {
-        return fileName.endsWith(".kt");
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/LookaheadParser.java b/src/main/java/edu/hm/hafner/analysis/LookaheadParser.java
index ebef38c3d..2e8c67c6d 100644
--- a/src/main/java/edu/hm/hafner/analysis/LookaheadParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/LookaheadParser.java
@@ -57,7 +57,7 @@ protected LookaheadParser(final String pattern) {
     }
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
         var report = new Report();
         try (Stream lines = readerFactory.readStream()) {
             try (var lookahead = new LookaheadStream(lines, readerFactory.getFileName())) {
diff --git a/src/main/java/edu/hm/hafner/analysis/MavenModuleDetector.java b/src/main/java/edu/hm/hafner/analysis/MavenModuleDetector.java
index d87b74353..08781dcb9 100644
--- a/src/main/java/edu/hm/hafner/analysis/MavenModuleDetector.java
+++ b/src/main/java/edu/hm/hafner/analysis/MavenModuleDetector.java
@@ -8,16 +8,16 @@
 import org.apache.commons.lang3.StringUtils;
 import org.xml.sax.SAXException;
 
-import edu.hm.hafner.analysis.ModuleDetector.FileSystem;
+import edu.hm.hafner.analysis.ModuleDetectorRunner.FileSystemFacade;
 
 /**
  * Detects module names by parsing the name of a source file, the Maven pom.xml.
  */
-public class MavenModuleDetector extends AbstractModuleDetector {
+class MavenModuleDetector extends AbstractModuleDetector {
     static final String MAVEN_POM = "pom.xml";
 
-    MavenModuleDetector(final FileSystem fileSystem) {
-        super(fileSystem);
+    MavenModuleDetector(final FileSystemFacade fileSystemFacade) {
+        super(fileSystemFacade);
     }
 
     @Override
@@ -51,7 +51,7 @@ private String parsePom(final String pom) {
     @SuppressWarnings("OverlyBroadCatchBlock")
     private String parsePomAttribute(final String pom, final String tagName) {
         try (var file = getFactory().open(pom)) {
-            var digester = new SecureDigester(ModuleDetector.class);
+            var digester = new SecureDigester(ModuleDetectorRunner.class);
             digester.push(new StringBuilder());
             digester.addCallMethod("project/" + tagName, "append", 0);
 
diff --git a/src/main/java/edu/hm/hafner/analysis/ModuleDetector.java b/src/main/java/edu/hm/hafner/analysis/ModuleDetectorRunner.java
similarity index 81%
rename from src/main/java/edu/hm/hafner/analysis/ModuleDetector.java
rename to src/main/java/edu/hm/hafner/analysis/ModuleDetectorRunner.java
index cbcbc1e19..8aa32ddab 100644
--- a/src/main/java/edu/hm/hafner/analysis/ModuleDetector.java
+++ b/src/main/java/edu/hm/hafner/analysis/ModuleDetectorRunner.java
@@ -23,36 +23,36 @@
  * @author Ullrich Hafner
  * @author Christoph Laeubrich (support for OSGi-Bundles)
  */
-public class ModuleDetector {
+public class ModuleDetectorRunner {
     private static final String BACK_SLASH = "\\";
     private static final String SLASH = "/";
 
-    /** A list of all module detectors (e.g. Maven) */
+    /** A list of all module detectors (e.g., Maven). */
     private final List moduleDetectors;
     /** The factory to create input streams with. */
-    private final FileSystem factory;
+    private final FileSystemFacade fileSystemFacade;
     /** Maps file names to module names. */
     private final Map fileNameToModuleName;
     /** Sorted list of file name prefixes. */
     private final List prefixes;
 
     /**
-     * Creates a new instance of {@link ModuleDetector}.
+     * Creates a new instance of {@link ModuleDetectorRunner}.
      *
      * @param workspace
-     *         the workspace to scan for Maven pom.xml or ant build.xml files
-     * @param fileSystem
-     *         file system facade to find and load files with
+     *         the workspace to scan for module files
+     * @param fileSystemFacade
+     *         file system facade to find and load the files with
      */
-    public ModuleDetector(final Path workspace, final FileSystem fileSystem) {
-        factory = fileSystem;
+    public ModuleDetectorRunner(final Path workspace, final FileSystemFacade fileSystemFacade) {
+        this.fileSystemFacade = fileSystemFacade;
 
-        moduleDetectors = new ArrayList<>(Arrays.asList(
-                new AntModuleDetector(factory),
-                new GradleModuleDetector(factory),
-                new MavenModuleDetector(factory),
-                new OsgiModuleDetector(factory)
-        ));
+        moduleDetectors = Arrays.asList(
+                new AntModuleDetector(this.fileSystemFacade),
+                new GradleModuleDetector(this.fileSystemFacade),
+                new MavenModuleDetector(this.fileSystemFacade),
+                new OsgiModuleDetector(this.fileSystemFacade)
+        );
 
         fileNameToModuleName = createFilesToModuleMapping(workspace);
         prefixes = new ArrayList<>(fileNameToModuleName.keySet());
@@ -111,7 +111,7 @@ private List find(final Path path) {
         List absoluteFileNames = new ArrayList<>();
 
         for (AbstractModuleDetector moduleDetector : moduleDetectors) {
-            var relativeFileNames = factory.find(path, moduleDetector.getPattern());
+            var relativeFileNames = fileSystemFacade.find(path, moduleDetector.getPattern());
             for (String relativeFileName : relativeFileNames) {
                 var relativePath = normalizePath(relativeFileName);
                 if (relativePath.startsWith(SLASH)) {
@@ -133,7 +133,7 @@ private String normalizePath(final String fileName) {
     /**
      * Facade for file system operations. May be replaced by stubs in test cases.
      */
-    public interface FileSystem {
+    public interface FileSystemFacade {
         /**
          * Returns all file names that match the specified pattern.
          *
@@ -144,8 +144,7 @@ public interface FileSystem {
          *
          * @return the found file names
          */
-        @SuppressWarnings("AvoidObjectArrays") // TODO: change to list in next major release
-        String[] find(Path root, String pattern);
+        List find(Path root, String pattern);
 
         /**
          * Creates an {@link InputStream} from the specified filename.
diff --git a/src/main/java/edu/hm/hafner/analysis/ModuleResolver.java b/src/main/java/edu/hm/hafner/analysis/ModuleResolver.java
index c0e4dd99a..67d28bd1a 100644
--- a/src/main/java/edu/hm/hafner/analysis/ModuleResolver.java
+++ b/src/main/java/edu/hm/hafner/analysis/ModuleResolver.java
@@ -1,7 +1,6 @@
 package edu.hm.hafner.analysis;
 
 import java.util.List;
-import java.util.stream.Collectors;
 
 /**
  * Resolves module names by reading and mapping module definitions (build.xml, pom.xml, or Manifest.mf files).
@@ -9,18 +8,27 @@
  * @author Ullrich Hafner
  */
 public class ModuleResolver {
+    private final ModuleDetectorRunner runner;
+
+    /**
+     * Creates a new instance of {@link ModuleResolver}.
+     *
+     * @param runner the module runner to use
+     */
+    public ModuleResolver(final ModuleDetectorRunner runner) {
+        this.runner = runner;
+    }
+
     /**
-     * Resolves absolute paths of the affected files of the specified set of issues.
+     * Resolves the absolute paths of all affected files referenced by the specified report.
      *
      * @param report
-     *         the issues to resolve the paths
-     * @param detector
-     *         the module detector to use
+     *         the issues to resolve the paths for
      */
-    public void run(final Report report, final ModuleDetector detector) {
+    public void run(final Report report) {
         List issuesWithoutModule = report.stream()
                 .filter(issue -> !issue.hasModuleName())
-                .collect(Collectors.toList());
+                .toList();
 
         if (issuesWithoutModule.isEmpty()) {
             report.logInfo("-> all issues already have a valid module name");
@@ -28,7 +36,7 @@ public void run(final Report report, final ModuleDetector detector) {
             return;
         }
 
-        issuesWithoutModule.forEach(issue -> issue.setModuleName(detector.guessModuleName(issue.getAbsolutePath())));
+        issuesWithoutModule.forEach(issue -> issue.setModuleName(runner.guessModuleName(issue.getAbsolutePath())));
         report.logInfo("-> resolved module names for %d issues", issuesWithoutModule.size());
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/OsgiModuleDetector.java b/src/main/java/edu/hm/hafner/analysis/OsgiModuleDetector.java
index 56644c68a..fb7c7edc3 100644
--- a/src/main/java/edu/hm/hafner/analysis/OsgiModuleDetector.java
+++ b/src/main/java/edu/hm/hafner/analysis/OsgiModuleDetector.java
@@ -10,12 +10,12 @@
 
 import org.apache.commons.lang3.StringUtils;
 
-import edu.hm.hafner.analysis.ModuleDetector.FileSystem;
+import edu.hm.hafner.analysis.ModuleDetectorRunner.FileSystemFacade;
 
 /**
  * Detects module names by parsing the name of OSGi specific source files.
  */
-public class OsgiModuleDetector extends AbstractModuleDetector {
+class OsgiModuleDetector extends AbstractModuleDetector {
     static final String BUNDLE_PROPERTIES = "OSGI-INF/l10n/bundle.properties";
     static final String OSGI_BUNDLE = "META-INF/MANIFEST.MF";
     static final String PLUGIN_PROPERTIES = "plugin.properties";
@@ -24,8 +24,8 @@ public class OsgiModuleDetector extends AbstractModuleDetector {
     private static final String BUNDLE_VENDOR = "Bundle-Vendor";
     private static final String REPLACEMENT_CHAR = "%";
 
-    OsgiModuleDetector(final FileSystem fileSystem) {
-        super(fileSystem);
+    OsgiModuleDetector(final FileSystemFacade fileSystemFacade) {
+        super(fileSystemFacade);
     }
 
     @Override
diff --git a/src/main/java/edu/hm/hafner/analysis/PackageDetectors.java b/src/main/java/edu/hm/hafner/analysis/PackageDetectors.java
deleted file mode 100644
index a36bc26cf..000000000
--- a/src/main/java/edu/hm/hafner/analysis/PackageDetectors.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package edu.hm.hafner.analysis;
-
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
-import java.nio.file.InvalidPathException;
-import java.nio.file.Path;
-import java.util.List;
-
-import com.google.errorprone.annotations.MustBeClosed;
-
-import edu.hm.hafner.util.VisibleForTesting;
-
-/**
- * Provides convenient methods to determine the package or namespace names of a file.
- *
- * @author Ullrich Hafner
- */
-class PackageDetectors {
-    /** If no package could be assigned this value is used as package name. */
-    static final String UNDEFINED_PACKAGE = "-";
-
-    private final List detectors;
-
-    @VisibleForTesting
-    PackageDetectors(final List detectors) {
-        this.detectors = detectors;
-    }
-
-    /**
-     * Detects the package name of the specified file based on several detector strategies.
-     *
-     * @param fileName
-     *         the filename of the file to scan
-     * @param charset
-     *         the charset to use when reading the source files
-     *
-     * @return the package name or the String {@link #UNDEFINED_PACKAGE} if no package could be detected
-     */
-    public String detectPackageName(final String fileName, final Charset charset) {
-        for (AbstractPackageDetector detector : detectors) {
-            if (detector.accepts(fileName)) {
-                return detector.detectPackageName(fileName, charset);
-            }
-        }
-        return UNDEFINED_PACKAGE;
-    }
-
-    /**
-     * Facade for file system operations. May be replaced by stubs in test cases.
-     */
-    @VisibleForTesting
-    static class FileSystem {
-        @MustBeClosed
-        InputStream openFile(final String fileName) throws IOException, InvalidPathException {
-            return Files.newInputStream(Path.of(fileName));
-        }
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/PackageNameResolver.java b/src/main/java/edu/hm/hafner/analysis/PackageNameResolver.java
index bd2a4f38a..28816e2ac 100644
--- a/src/main/java/edu/hm/hafner/analysis/PackageNameResolver.java
+++ b/src/main/java/edu/hm/hafner/analysis/PackageNameResolver.java
@@ -1,41 +1,34 @@
 package edu.hm.hafner.analysis;
 
 import java.nio.charset.Charset;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.util.HashMap;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import edu.hm.hafner.util.PackageDetectorFactory;
+import edu.hm.hafner.util.PackageDetectorRunner;
 import edu.hm.hafner.util.VisibleForTesting;
 
-import static edu.hm.hafner.analysis.PackageDetectors.*;
-import static java.util.function.Function.*;
-
 /**
  * Resolves packages or namespace names for a set of issues.
  *
  * @author Ullrich Hafner
  */
 public class PackageNameResolver {
-    private final PackageDetectors packageDetectors;
+    private final PackageDetectorRunner runner;
 
     /**
      * Creates a new {@link PackageNameResolver}.
      */
     public PackageNameResolver() {
-        this(new FileSystem());
+        this(PackageDetectorFactory.createPackageDetectors());
     }
 
     @VisibleForTesting
-    PackageNameResolver(final FileSystem fileSystem) {
-        List detectors = new ArrayList<>(Arrays.asList(
-                new JavaPackageDetector(fileSystem),
-                new CSharpNamespaceDetector(fileSystem),
-                new KotlinPackageDetector(fileSystem)
-        ));
-        packageDetectors = new PackageDetectors(detectors);
+    PackageNameResolver(final PackageDetectorRunner runner) {
+        this.runner = runner;
     }
 
     /**
@@ -58,9 +51,11 @@ public void run(final Report report, final Charset charset) {
             return;
         }
 
-        Map packagesOfFiles = filesWithoutPackageName.stream()
-                .collect(Collectors.toMap(identity(),
-                        fileName -> packageDetectors.detectPackageName(fileName, charset)));
+        Map packagesOfFiles = new HashMap<>();
+        filesWithoutPackageName.stream()
+                .map(f -> extractPackageName(f, charset))
+                .flatMap(Optional::stream)
+                .forEach(e -> packagesOfFiles.put(e.getKey(), e.getValue()));
 
         try (var builder = new IssueBuilder()) {
             report.stream().forEach(issue -> {
@@ -71,4 +66,8 @@ public void run(final Report report, final Charset charset) {
         }
         report.logInfo("-> resolved package names of %d affected files", filesWithoutPackageName.size());
     }
+
+    private Optional> extractPackageName(final String fileName, final Charset charset) {
+        return runner.detectPackageName(fileName, charset).map(r -> Map.entry(fileName, r));
+    }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/Report.java b/src/main/java/edu/hm/hafner/analysis/Report.java
index ba24fc795..b819d0afd 100644
--- a/src/main/java/edu/hm/hafner/analysis/Report.java
+++ b/src/main/java/edu/hm/hafner/analysis/Report.java
@@ -40,28 +40,26 @@
 import edu.hm.hafner.util.PathUtil;
 import edu.hm.hafner.util.TreeStringBuilder;
 import edu.hm.hafner.util.VisibleForTesting;
-import edu.umd.cs.findbugs.annotations.CheckForNull;
 import edu.umd.cs.findbugs.annotations.NonNull;
 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 
 /**
- * A report contains a set of unique {@link Issue issues}: it contains no duplicate elements, i.e. it models the
+ * A report contains a set of unique {@link Issue issues}: it contains no duplicate elements, i.e., it models the
  * mathematical set abstraction. This report provides a total ordering on its elements. I.e., the issues
- * in this report are ordered by their index: the first added issue is at position 0, the second added issues is at
+ * in this report are ordered by their index: the first added issue is at position 0, the second added issue is at
  * position 1, and so on.
  *
  * 

- * Additionally, this report provides methods to find and filter issues based on different properties. In order to - * create issues use the provided {@link IssueBuilder builder} class. + * Additionally, this report provides methods to find and filter issues based on different properties. To create issues + * use the provided {@link IssueBuilder builder} class. *

* * @author Ullrich Hafner */ @SuppressWarnings({"PMD.ExcessivePublicCount", "PMD.ExcessiveClassLength", "PMD.GodClass", "PMD.CognitiveComplexity", "PMD.CyclomaticComplexity", "checkstyle:ClassFanOutComplexity"}) -// TODO: provide a readResolve method to check the instance and improve the performance (TreeString, etc.) public class Report implements Iterable, Serializable { @Serial - private static final long serialVersionUID = 4L; // release 10.0.0 + private static final long serialVersionUID = 5L; // release 13.0.0 @VisibleForTesting static final String DEFAULT_ID = "-"; @@ -69,6 +67,9 @@ public class Report implements Iterable, Serializable { private String id; private String name; private String originReportFile; + private String icon = StringUtils.EMPTY; // since 13.0.0 + private String parserId = DEFAULT_ID; // since 13.0.0 + private IssueType elementType; // since 13.0.0 private List subReports = new ArrayList<>(); // almost final @@ -77,11 +78,6 @@ public class Report implements Iterable, Serializable { private List errorMessages = new ArrayList<>(); private Map countersByKey = new HashMap<>(); - @CheckForNull @SuppressWarnings({"all", "UnusedVariable"}) - private transient Set fileNames; // Not needed anymore since 10.0.0 - @CheckForNull @SuppressWarnings({"all", "UnusedVariable"}) - private transient Map namesByOrigin; // Not needed anymore since 10.0.0 - private int duplicatesSize; /** @@ -112,15 +108,32 @@ public Report(final String id, final String name) { * @param name * a human-readable name for the report * @param originReportFile - * the specified source file * that is the origin of the issues. + * the specified source file that is the origin of the issues. */ public Report(final String id, final String name, final String originReportFile) { + this(id, name, originReportFile, IssueType.WARNING); + } + + /** + * Creates an empty {@link Report} with the specified ID and name. Link the report with the specified source file + * that is the origin of the issues. + * + * @param id + * the ID of the report + * @param name + * a human-readable name for the report + * @param originReportFile + * the specified source file that is the origin of the issues + * @param elementType + * the type of the issues in the report + */ + public Report(final String id, final String name, final String originReportFile, final IssueType elementType) { this.id = id; this.name = name; this.originReportFile = originReportFile; + this.elementType = elementType; } - /** * Creates a new {@link Report} that is an aggregation of the specified {@link Report reports}. The created report * will contain the issues of all specified reports, in the same order. The properties of the specified reports will @@ -164,7 +177,24 @@ public String getId() { } private boolean hasId() { - return !DEFAULT_ID.equals(id) && StringUtils.isNoneBlank(id); + return isDefaultId(id); + } + + public String getParserId() { + return aggregateChildProperty(Report::getParserId, parserId); + } + + /** + * Returns whether this report has a parser ID. A parser ID is considered to be set if it is not the default ID. + * + * @return {@code true} if this report has a parser ID, {@code false} otherwise + */ + public boolean hasParserId() { + return isDefaultId(getParserId()); + } + + private boolean isDefaultId(final String value) { + return !DEFAULT_ID.equals(value) && StringUtils.isNoneBlank(value); } /** @@ -174,14 +204,18 @@ private boolean hasId() { * @return the effective ID of all sub-reports */ public String getEffectiveId() { - Set ids = subReports.stream().map(Report::getEffectiveId).collect(Collectors.toSet()); - ids.add(getId()); + return aggregateChildProperty(Report::getEffectiveId, getId()); + } + + private String aggregateChildProperty(final Function idProperty, final String defaultId) { + Set ids = subReports.stream().map(idProperty).collect(Collectors.toSet()); + ids.add(defaultId); ids.remove(DEFAULT_ID); if (ids.size() == 1) { return ids.iterator().next(); } - return getId(); + return defaultId; } public String getName() { @@ -189,8 +223,8 @@ public String getName() { } /** - * Sets the origin of all issues in this report. Calling this method will associate all containing issues and - * issues in sub-reports using the specified ID and name. + * Sets the origin of all issues in this report. Calling this method will associate all containing issues and issues + * in sub-reports using the specified ID and name. * * @param originId * the ID of the report @@ -198,14 +232,53 @@ public String getName() { * a human-readable name for the report */ public void setOrigin(final String originId, final String originName) { - Ensure.that(originId).isNotBlank("Issue origin ID '%s' must be not blank (%s)", originId, toString()); - Ensure.that(originName).isNotBlank("Issue origin name '%s' must be not blank (%s)", originName, toString()); + var normalizedId = StringUtils.defaultIfBlank(originId, DEFAULT_ID); + var normalizedName = StringUtils.defaultIfBlank(originName, DEFAULT_ID); + + id = normalizedId; + name = normalizedName; + subReports.forEach(report -> report.setOrigin(normalizedId, normalizedName)); + elements.forEach(issue -> issue.setOrigin(normalizedId, normalizedName)); } + + /** + * Sets the origin of all issues in this report. Calling this method will associate all containing issues and issues + * in sub-reports using the specified ID and name. + * + * @param originId + * the ID of the report + * @param originName + * a human-readable name for the report + * @param originElementType + * the type of the issues in the report + */ + private void setOrigin(final String originId, final String originName, final IssueType originElementType) { + setOrigin(originId, originName); - id = originId; - name = originName; + elementType = originElementType; + parserId = originId; + subReports.forEach(report -> report.setOrigin(this.id, this.name, originElementType)); + // TODO check if we need the type for issues as well + elements.forEach(issue -> issue.setOrigin(this.id, this.name)); + } - subReports.forEach(report -> report.setOrigin(originId, originName)); - elements.forEach(issue -> issue.setOrigin(originId, originName)); + /** + * Sets the origin of all issues in this report. Calling this method will associate all containing issues and issues + * in sub-reports using the specified ID and name. + * + * @param originId + * the ID of the report + * @param originName + * a human-readable name for the report + * @param elementType + * the type of the issues in the report + * @param reportFile + * the report file name to add + */ + @SuppressWarnings("checkstyle:HiddenField") + public void setOrigin(final String originId, final String originName, final IssueType elementType, + final String reportFile) { + setOrigin(originId, originName, elementType); + setOriginReportFile(reportFile); } /** @@ -215,14 +288,7 @@ public void setOrigin(final String originId, final String originName) { * @return the effective ID of all sub-reports */ public String getEffectiveName() { - Set names = subReports.stream().map(Report::getEffectiveName).collect(Collectors.toSet()); - names.add(getName()); - names.remove(DEFAULT_ID); - - if (names.size() == 1) { - return names.iterator().next(); - } - return getName(); + return aggregateChildProperty(Report::getEffectiveName, getName()); } public String getOriginReportFile() { @@ -255,9 +321,50 @@ public Set getOriginReportFiles() { return files; } + /** + * Returns the type of the issues in the report. The type might be used to customize reports in the UI. + * + * @return the type of the issues in the report + */ + public IssueType getElementType() { + if (elements.isEmpty()) { + var types = subReports.stream() + .map(Report::getElementType) + .collect(Collectors.toSet()); + if (types.size() > 1) { + return IssueType.WARNING; // fallback if the element type is not unique + } + else if (types.isEmpty()) { + return elementType; + } + return types.iterator().next(); + } + return elementType; + } + + /** + * Returns the icon of the report. The icon might be used to customize reports in the UI. + * + * @return the name of te icon + */ + public String getIcon() { + return icon; + } + + /** + * Sets the icon of the report. The icon might be used to customize reports in the UI. + * + * @param icon the new icon + */ + public void setIcon(final String icon) { + if (StringUtils.isNotBlank(icon)) { + this.icon = icon; + } + } + /** * Appends the specified issue to the end of this report. Duplicates will be skipped (the number of skipped elements - * is available using the method {@link #getDuplicatesSize()}. + * is available using the method {@link #getDuplicatesSize()}). * * @param issue * the issue to append @@ -281,8 +388,8 @@ public Report add(final Issue issue) { /** * Appends all the specified issues to the end of this report, preserving the order of the array elements. - * Duplicates will be skipped (the number of skipped elements is available using the method {@link - * #getDuplicatesSize()}). + * Duplicates will be skipped (the number of skipped elements is available using the method + * {@link #getDuplicatesSize()}). * * @param issue * the first issue to append @@ -302,9 +409,9 @@ public Report addAll(final Issue issue, final Issue... additionalIssues) { } /** - * Appends all of the specified issues to the end of this report, preserving the order of the array elements. - * Duplicates will be skipped (the number of skipped elements is available using the method {@link - * #getDuplicatesSize()}. + * Appends all the specified issues to the end of this report, preserving the order of the array elements. + * Duplicates will be skipped (the number of skipped elements is available using the method + * {@link #getDuplicatesSize()}). * * @param issues * the issues to append @@ -383,11 +490,12 @@ List getSubReports() { } /** - * Called after de-serialization to improve the memory usage and to initialize fields that have been introduced - * after the first release. + * Called after deserialization to improve the memory usage and to initialize fields that have been introduced after + * the first release. * * @return this */ + @Serial @SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_OF_NONNULL_VALUE", justification = "Deserialization of instances that do not have all fields yet") protected Object readResolve() { if (countersByKey == null) { @@ -399,6 +507,11 @@ protected Object readResolve() { name = DEFAULT_ID; originReportFile = DEFAULT_ID; } + if (parserId == null) { // release 13.0.0 + parserId = DEFAULT_ID; + icon = DEFAULT_ID; + elementType = IssueType.WARNING; + } return this; } @@ -458,7 +571,7 @@ public Issue findById(final UUID issueId) { return stream().filter(issue -> issue.getId().equals(issueId)) .findAny() .orElseThrow(() -> new NoSuchElementException( - "No issue found with id %s.".formatted(issueId))); + "No issue found with id %s.".formatted(issueId))); } /** @@ -540,7 +653,8 @@ public Issue get(final int index) { } /** - * Returns the issues in this report that are part of modified code. This will include the issues of any sub-reports. + * Returns the issues in this report that are part of modified code. This will include the issues of any + * sub-reports. * * @return all issues in this report */ @@ -622,8 +736,92 @@ public int getSizeOf(final Severity severity) { @Override public String toString() { - return String.format(Locale.ENGLISH, "%s (%s): %d issues (%d duplicates)", getEffectiveName(), getEffectiveId(), - size(), getDuplicatesSize()); + var summary = String.format(Locale.ENGLISH, "%s%s%s", getNamePrefix(), getSummary(), getDuplicates()); + + return summary + getSeverityDistribution(); + } + + /** + * Returns a string representation of this report's severity distribution. + * + * @return a string representation of severity distribution + */ + public String getSeverityDistribution() { + var severityDistribution = Severity.getPredefinedValues() + .stream() + .map(this::reportSeverity) + .flatMap(Optional::stream) + .collect(Collectors.joining(", ")); + if (StringUtils.isEmpty(severityDistribution)) { + return severityDistribution; + } + return " (" + severityDistribution + ")"; + } + + /** + * Returns a string representation of this report that shows the number of issues. + * + * @return a string representation of this report + */ + public String getSummary() { + return getItemName(size()); + } + + private String getNamePrefix() { + if (isEmptyOrDefault(getName()) && isEmptyOrDefault(getId())) { + return StringUtils.EMPTY; + } + else { + return String.format(Locale.ENGLISH, "%s (%s): ", getName(), getId()); + } + } + + private boolean isEmptyOrDefault(final String value) { + return StringUtils.isEmpty(value) || DEFAULT_ID.equals(value); + } + + private Optional reportSeverity(final Severity severity) { + var size = getSizeOf(severity); + if (size > 0) { + return Optional.of( + String.format(Locale.ENGLISH, "%s: %d", StringUtils.lowerCase(severity.getName()), size)); + } + return Optional.empty(); + } + + private String getItemName(final int size) { + var items = getItemCount(size); + if (size == 0) { + return String.format("No %s", items); + } + return String.format(Locale.ENGLISH, "%d %s", size, items); + } + + // Open as API? + private String getItemCount(final int count) { + if (count == 1) { + return switch (getElementType()) { + case WARNING -> "warning"; + case BUG -> "bug"; + case DUPLICATION -> "duplication"; + case VULNERABILITY -> "vulnerability"; + }; + } + else { + return switch (getElementType()) { + case WARNING -> "warnings"; + case BUG -> "bugs"; + case DUPLICATION -> "duplications"; + case VULNERABILITY -> "vulnerabilities"; + }; + } + } + + private String getDuplicates() { + if (duplicatesSize > 0) { + return String.format(Locale.ENGLISH, " (%d duplicates)", duplicatesSize); + } + return StringUtils.EMPTY; } /** @@ -803,40 +1001,40 @@ public boolean hasSeverities() { /** * Returns the different values for a given property for all issues. * - * @param propertiesMapper - * the properties mapper that selects the property + * @param propertyMapper + * the property mapper that selects the property * @param * type of the property * * @return the set of different values * @see #getFiles() */ - public Set getProperties(final Function propertiesMapper) { - return stream().map(propertiesMapper).collect(Collectors.toSet()); + public Set getProperties(final Function propertyMapper) { + return stream().map(propertyMapper).collect(Collectors.toSet()); } /** * Returns the number of occurrences for every existing value of a given property for all issues. * - * @param propertiesMapper - * the properties mapper that selects the property to evaluate + * @param propertyMapper + * the property mapper that selects the property to evaluate * @param * type of the property * * @return a mapping of: property value to the number of issues for that value * @see #getProperties(Function) */ - public Map getPropertyCount(final Function propertiesMapper) { + public Map getPropertyCount(final Function propertyMapper) { return stream().collect( - Collectors.groupingBy(propertiesMapper, Collectors.reducing(0, issue -> 1, Integer::sum))); + Collectors.groupingBy(propertyMapper, Collectors.reducing(0, issue -> 1, Integer::sum))); } /** - * Groups issues by a specified property. Returns the results as a mapping of property values to a new set of {@link - * Report} for this value. + * Groups issues by a specified property. Returns the results as a mapping of property values to a new set of + * {@link Report} for this value. * * @param propertyName - * the property to that selects the property to evaluate + * the property to that selects the property to evaluate * * @return a mapping of: property value to the number of issues for that value * @see #getProperties(Function) @@ -873,12 +1071,15 @@ private void copyIssuesAndProperties(final Report source, final Report destinati private void copyProperties(final Report source, final Report destination) { destination.id = source.getId(); destination.name = source.getName(); + destination.icon = source.getIcon(); + destination.elementType = source.getElementType(); + destination.parserId = source.getParserId(); destination.originReportFile = source.getOriginReportFile(); destination.duplicatesSize += source.duplicatesSize; // not recursively destination.infoMessages.addAll(source.infoMessages); destination.errorMessages.addAll(source.errorMessages); destination.countersByKey = Stream.concat( - destination.countersByKey.entrySet().stream(), source.countersByKey.entrySet().stream()) + destination.countersByKey.entrySet().stream(), source.countersByKey.entrySet().stream()) .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, Integer::sum)); } @@ -981,8 +1182,8 @@ public List getErrorMessages() { private List mergeMessages(final List thisMessages, final Function> sumMessages) { return Stream.concat( - subReports.stream().map(sumMessages).flatMap(Collection::stream), - thisMessages.stream()) + subReports.stream().map(sumMessages).flatMap(Collection::stream), + thisMessages.stream()) .collect(Collectors.toList()); } @@ -1008,6 +1209,9 @@ public boolean equals(final Object o) { return duplicatesSize == issues.duplicatesSize && Objects.equals(id, issues.id) && Objects.equals(name, issues.name) + && Objects.equals(icon, issues.icon) + && Objects.equals(elementType, issues.elementType) + && Objects.equals(parserId, issues.parserId) && Objects.equals(originReportFile, issues.originReportFile) && Objects.equals(subReports, issues.subReports) && Objects.equals(elements, issues.elements) @@ -1019,10 +1223,11 @@ public boolean equals(final Object o) { @Override @Generated public int hashCode() { - return Objects.hash(id, name, originReportFile, subReports, elements, infoMessages, errorMessages, - countersByKey, duplicatesSize); + return Objects.hash(id, name, icon, elementType, parserId, originReportFile, subReports, elements, + infoMessages, errorMessages, countersByKey, duplicatesSize); } + @Serial private void writeObject(final ObjectOutputStream output) throws IOException { output.writeInt(elements.size()); writeIssues(output); @@ -1035,6 +1240,10 @@ private void writeObject(final ObjectOutputStream output) throws IOException { output.writeUTF(id); output.writeUTF(name); + output.writeUTF(icon); + output.writeUTF(parserId); + output.writeObject(elementType); + output.writeUTF(originReportFile); output.writeInt(subReports.size()); for (Report subReport : subReports) { @@ -1075,6 +1284,7 @@ private void writeLongString(final ObjectOutputStream output, final String value @SuppressWarnings("unchecked") @SuppressFBWarnings(value = "MC_OVERRIDABLE_METHOD_CALL_IN_READ_OBJECT", justification = "False positive, the overridden method is in already initialized objects") + @Serial private void readObject(final ObjectInputStream input) throws IOException, ClassNotFoundException { elements = new LinkedHashSet<>(); readIssues(input, input.readInt()); @@ -1087,6 +1297,11 @@ private void readObject(final ObjectInputStream input) throws IOException, Class id = input.readUTF(); name = input.readUTF(); + + icon = input.readUTF(); + parserId = input.readUTF(); + elementType = (IssueType) input.readObject(); + originReportFile = input.readUTF(); subReports = new ArrayList<>(); @@ -1663,4 +1878,18 @@ private void addMessageFilter(final Collection patterns, final FilterTyp } // } + + /** + * Returns the type of the issues. The type is used to customize reports in the UI. + */ + public enum IssueType { + /** A parser that scans the output of a build tool to find warnings. */ + WARNING, + /** A parser that scans the output of a build tool to find bugs. */ + BUG, + /** A parser that scans the output of a build tool to find vulnerabilities. */ + VULNERABILITY, + /** A parser that scans the output of a build tool to find vulnerabilities. */ + DUPLICATION + } } diff --git a/src/main/java/edu/hm/hafner/analysis/Severity.java b/src/main/java/edu/hm/hafner/analysis/Severity.java index 6e4ab1589..615e6fe02 100644 --- a/src/main/java/edu/hm/hafner/analysis/Severity.java +++ b/src/main/java/edu/hm/hafner/analysis/Severity.java @@ -4,6 +4,8 @@ import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; import java.util.Set; @@ -18,9 +20,9 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** - * Severity of an issue. The predefined set of severities consists of an error and 3 warnings with priorities high, - * normal, or low. Additional severities can be created if this set of severities is not sufficient. Note that new - * instances are not cached by this class so that there might exist several severity instances with the same name. + * Severity of an issue. The predefined set of severities consists of an error and three warnings with priorities high, + * normal, or low. Additional severities can be created if this set of severities is not enough. Note that this class + * does not cache new instances so that there might exist several severity instances with the same name. * * @author Ullrich Hafner */ @@ -38,7 +40,9 @@ public class Severity implements Serializable { /** A warning with priority low. Mapping of warning priorities is determined by the corresponding tool. */ public static final Severity WARNING_LOW = new Severity("LOW"); - private static final Set ALL_SEVERITIES = Set.of(ERROR, WARNING_HIGH, WARNING_NORMAL, WARNING_LOW); + private static final Set ALL_SEVERITIES + = Collections.unmodifiableSet(new LinkedHashSet<>( + List.of(ERROR, WARNING_HIGH, WARNING_NORMAL, WARNING_LOW))); /** * Creates a new {@link Severity} with the specified name. If the name is the same as the name of one of the @@ -66,8 +70,8 @@ public static Severity valueOf(final String name) { } /** - * Converts a String severity to one of the predefined severities. If the provided String does not match then the default - * severity will be returned. + * Converts a String severity to one of the predefined severities. If the provided String does not match, + * then the default severity will be returned. * * @param severity * priority as a String @@ -87,7 +91,7 @@ public static Severity valueOf(@CheckForNull final String severity, final Severi /** * Converts a String severity to one of the predefined severities. If the provided String does not match (even - * partly) then the default severity will be returned. + * partly), then the default severity will be returned. * * @param severity * the severity string @@ -98,7 +102,7 @@ public static Severity guessFromString(@CheckForNull final String severity) { if (StringUtils.containsAnyIgnoreCase(severity, "error", "severe", "critical", "fatal")) { return ERROR; } - if (StringUtils.containsAnyIgnoreCase(severity, "info", "note", "low")) { + if (StringUtils.containsAnyIgnoreCase(severity, "info", "note", "low", "suggestion")) { return WARNING_LOW; } if (StringUtils.containsAnyIgnoreCase(severity, "warning", "medium")) { diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/AbstractDryParser.java b/src/main/java/edu/hm/hafner/analysis/parser/AbstractDryParser.java similarity index 95% rename from src/main/java/edu/hm/hafner/analysis/parser/dry/AbstractDryParser.java rename to src/main/java/edu/hm/hafner/analysis/parser/AbstractDryParser.java index 28dc58211..59bb1492d 100644 --- a/src/main/java/edu/hm/hafner/analysis/parser/dry/AbstractDryParser.java +++ b/src/main/java/edu/hm/hafner/analysis/parser/AbstractDryParser.java @@ -1,4 +1,4 @@ -package edu.hm.hafner.analysis.parser.dry; +package edu.hm.hafner.analysis.parser; import java.io.IOException; import java.io.Serial; @@ -67,7 +67,7 @@ protected Severity getPriority(final int lines) { } @Override - public Report parse(final ReaderFactory readerFactory) throws ParsingCanceledException, ParsingException { + public Report parseReport(final ReaderFactory readerFactory) throws ParsingCanceledException, ParsingException { var digester = new SecureDigester(AbstractDryParser.class); configureParser(digester); diff --git a/src/main/java/edu/hm/hafner/analysis/parser/AjcParser.java b/src/main/java/edu/hm/hafner/analysis/parser/AjcParser.java index 732ddf25f..4a6404397 100644 --- a/src/main/java/edu/hm/hafner/analysis/parser/AjcParser.java +++ b/src/main/java/edu/hm/hafner/analysis/parser/AjcParser.java @@ -28,7 +28,7 @@ public class AjcParser extends IssueParser { static final String ADVICE = "Advice"; @Override - public Report parse(final ReaderFactory reader) throws ParsingException { + public Report parseReport(final ReaderFactory reader) throws ParsingException { try (Stream lines = reader.readStream()) { return parse(lines); } diff --git a/src/main/java/edu/hm/hafner/analysis/parser/AnsibleLintParser.java b/src/main/java/edu/hm/hafner/analysis/parser/AnsibleLintParser.java index 734855e80..e8bf65d6a 100644 --- a/src/main/java/edu/hm/hafner/analysis/parser/AnsibleLintParser.java +++ b/src/main/java/edu/hm/hafner/analysis/parser/AnsibleLintParser.java @@ -43,7 +43,7 @@ protected Optional createIssue(final Matcher matcher, final LookaheadStre final String cat; /* Ansible-lint has changed the style of parseable output. This requires - * to distinguish between rule names in square brackets and rule names + * distinguishing between rule names in square brackets and rule names * containing square brackets. */ if (matcher.group("cat") != null) { cat = matcher.group("cat"); diff --git a/src/main/java/edu/hm/hafner/analysis/parser/CargoCheckParser.java b/src/main/java/edu/hm/hafner/analysis/parser/CargoCheckParser.java index ea3c69ca7..c18f362d4 100644 --- a/src/main/java/edu/hm/hafner/analysis/parser/CargoCheckParser.java +++ b/src/main/java/edu/hm/hafner/analysis/parser/CargoCheckParser.java @@ -69,7 +69,7 @@ public class CargoCheckParser extends IssueParser { private static final String MESSAGE_SPAN_COLUMN_END = "column_end"; @Override - public Report parse(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException { + public Report parseReport(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException { var report = new Report(); try (Stream lines = readerFactory.readStream(); var issueBuilder = new IssueBuilder()) { diff --git a/src/main/java/edu/hm/hafner/analysis/parser/ccm/CcmParser.java b/src/main/java/edu/hm/hafner/analysis/parser/CcmParser.java similarity index 52% rename from src/main/java/edu/hm/hafner/analysis/parser/ccm/CcmParser.java rename to src/main/java/edu/hm/hafner/analysis/parser/CcmParser.java index 6d7112d9a..6ea50f2e1 100644 --- a/src/main/java/edu/hm/hafner/analysis/parser/ccm/CcmParser.java +++ b/src/main/java/edu/hm/hafner/analysis/parser/CcmParser.java @@ -1,7 +1,9 @@ -package edu.hm.hafner.analysis.parser.ccm; +package edu.hm.hafner.analysis.parser; import java.io.IOException; import java.io.Serial; +import java.util.ArrayList; +import java.util.List; import java.util.Locale; import org.apache.commons.lang3.StringUtils; @@ -14,6 +16,8 @@ import edu.hm.hafner.analysis.Report; import edu.hm.hafner.analysis.SecureDigester; import edu.hm.hafner.analysis.Severity; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; /** * A parser for CCM XML files. @@ -25,7 +29,7 @@ public class CcmParser extends IssueParser { private static final long serialVersionUID = -5172155190810975806L; @Override - public Report parse(final ReaderFactory readerFactory) throws ParsingException { + public Report parseReport(final ReaderFactory readerFactory) throws ParsingException { var report = parseCcmXmlFile(readerFactory); return convert(report); @@ -112,4 +116,130 @@ private boolean isMetricModeratePriority(final Metric metric) { return StringUtils.contains(metricClassification, "moderate") || "B".equals(metricClassification); } + + /** + * Entity used by {@link CcmParser} to represent the root node of CCM results file. + * + * @author Bruno P. Kinoshita - http://www.kinoshita.eti.br + * @since 1.0 + */ + @SuppressWarnings("all") + @SuppressFBWarnings("EI") + public static class Ccm { + /** + * List of metrics present in the XML file. + */ + private List metrics = new ArrayList<>(); + + public List getMetrics() { + return metrics; + } + + public void setMetrics(List metrics) { + this.metrics = metrics; + } + + public void addMetric(Metric metric) { + this.metrics.add(metric); + } + } + + /** + * Entity representing the Metric from CCM.exe output. + * + *

+ * It has the {@link #complexity}, {@link #unit}, {@link #classification} and {@link #file} fields. + *

+ * + * @author Bruno P. Kinoshita - http://www.kinoshita.eti.br + * @since 1.0 + */ + @SuppressWarnings("all") + public static class Metric { + /** + * Total CC of the method. + */ + private int complexity; + + /** + * String containing Class_Name::Method_Name + */ + @CheckForNull + private String unit; + + /** + * CCM outputs a String with a classification such as "complex, high risk", "untestable, very high risk", etc. As + * there is no documentation on which values are used to determine a method's CC classification CCM Plugin only + * outputs this value. But does not use the information as a constraint in any place. + */ + @CheckForNull + private String classification; + + /** + * The file name (e.g.:\ascx\request\open\form.ascx.cs). + */ + @CheckForNull + private String file; + + /** + * The start line number of the measurement + */ + private int startLineNumber; + + /** + * The end line number of the measurement + */ + private int endLineNumber; + + public int getComplexity() { + return complexity; + } + + public void setComplexity(int complexity) { + this.complexity = complexity; + } + + @CheckForNull + public String getUnit() { + return unit; + } + + public void setUnit(String unit) { + this.unit = unit; + } + + @CheckForNull + public String getClassification() { + return classification; + } + + public void setClassification(String classification) { + this.classification = classification; + } + + @CheckForNull + public String getFile() { + return file; + } + + public void setFile(String file) { + this.file = file; + } + + public int getStartLineNumber() { + return startLineNumber; + } + + public void setStartLineNumber(int startLineNumber) { + this.startLineNumber = startLineNumber; + } + + public int getEndLineNumber() { + return endLineNumber; + } + + public void setEndLineNumber(int endLineNumber) { + this.endLineNumber = endLineNumber; + } + } } diff --git a/src/main/java/edu/hm/hafner/analysis/parser/CheckStyleParser.java b/src/main/java/edu/hm/hafner/analysis/parser/CheckStyleParser.java new file mode 100644 index 000000000..4ebeaabaf --- /dev/null +++ b/src/main/java/edu/hm/hafner/analysis/parser/CheckStyleParser.java @@ -0,0 +1,452 @@ +package edu.hm.hafner.analysis.parser; + +import java.io.IOException; +import java.io.Serial; +import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.TransformerException; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.apache.commons.beanutils.MethodUtils; +import org.apache.commons.digester3.NodeCreateRule; +import org.apache.commons.lang3.StringUtils; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.xml.sax.SAXException; + +import edu.hm.hafner.analysis.IssueBuilder; +import edu.hm.hafner.analysis.IssueParser; +import edu.hm.hafner.analysis.ParsingException; +import edu.hm.hafner.analysis.ReaderFactory; +import edu.hm.hafner.analysis.Report; +import edu.hm.hafner.analysis.SecureDigester; +import edu.hm.hafner.util.SecureXmlParserFactory; +import edu.umd.cs.findbugs.annotations.CheckForNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; + +/** + * A parser for Checkstyle XML files. + * + * @author Ullrich Hafner + */ +public class CheckStyleParser extends IssueParser { + @Serial + private static final long serialVersionUID = -3187275729854832128L; + + @Override + public Report parseReport(final ReaderFactory readerFactory) throws ParsingException { + var digester = new SecureDigester(CheckStyleParser.class); + + var rootXPath = "checkstyle"; + digester.addObjectCreate(rootXPath, CheckStyle.class); + digester.addSetProperties(rootXPath); + + var fileXPath = "checkstyle/file"; + digester.addObjectCreate(fileXPath, File.class); + digester.addSetProperties(fileXPath); + digester.addSetNext(fileXPath, "addFile", File.class.getName()); + + var bugXPath = "checkstyle/file/error"; + digester.addObjectCreate(bugXPath, Error.class); + digester.addSetProperties(bugXPath); + digester.addSetNext(bugXPath, "addError", Error.class.getName()); + + try (var reader = readerFactory.create()) { + CheckStyle checkStyle = digester.parse(reader); + if (checkStyle == null) { + throw new ParsingException("Input stream is not a Checkstyle file."); + } + + return convert(checkStyle); + } + catch (IOException | SAXException exception) { + throw new ParsingException(exception); + } + } + + /** + * Converts the internal structure to the annotations API. + * + * @param collection + * the internal maven module + * + * @return a maven module of the annotations API + */ + private Report convert(final CheckStyle collection) { + try (var issueBuilder = new IssueBuilder()) { + var report = new Report(); + + for (File file : collection.getFiles()) { + if (isValidWarning(file)) { + for (Error error : file.getErrors()) { + issueBuilder.guessSeverity(error.getSeverity()); + var source = error.getSource(); + issueBuilder.setType(getType(source)); + issueBuilder.setCategory(getCategory(source)); + issueBuilder.setMessage(error.getMessage()); + issueBuilder.setLineStart(error.getLine()); + issueBuilder.setFileName(file.getName()); + issueBuilder.setColumnStart(error.getColumn()); + report.add(issueBuilder.buildAndClean()); + } + } + } + return report; + } + } + + @CheckForNull + private String getCategory(@CheckForNull final String source) { + return StringUtils.capitalize(getType(StringUtils.substringBeforeLast(source, "."))); + } + + @CheckForNull + private String getType(@CheckForNull final String source) { + if (StringUtils.contains(source, '.')) { + return StringUtils.substringAfterLast(source, "."); + } + return source; + } + + /** + * Returns {@code true} if this warning is valid or {@code false} if the warning can't be processed by the + * checkstyle plug-in. + * + * @param file + * the file to check + * + * @return {@code true} if this warning is valid + */ + private boolean isValidWarning(final File file) { + return !StringUtils.endsWith(file.getName(), "package.html"); + } + + /** + * Java Bean class for a file of the Checkstyle format. + * + * @author Ullrich Hafner + */ + public static class File { + /** Name of the file. */ + @CheckForNull + private String name; + /** All errors of this file. */ + private final List errors = new ArrayList<>(); + + /** + * Adds a new violation to this file. + * + * @param violation + * the new violation + */ + public void addError(final Error violation) { + errors.add(violation); + } + + /** + * Returns all violations of this file. The returned collection is + * read-only. + * + * @return all violations in this file + */ + public Collection getErrors() { + return Collections.unmodifiableCollection(errors); + } + + /** + * Returns the name of this file. + * + * @return the name of this file + */ + @CheckForNull + public String getName() { + return name; + } + + /** + * Sets the name of this file to the specified value. + * + * @param name the value to set + */ + public void setName(@CheckForNull final String name) { + this.name = name; + } + } + + /** + * Java Bean class for a violation of the Checkstyle format. + * + * @author Ullrich Hafner + */ + @SuppressWarnings({"all", "JavaLangClash"}) + public static class Error { + @CheckForNull + private String source; + @CheckForNull + private String severity; + @CheckForNull + private String message; + private int line; + private int column; + + public int getColumn() { + return column; + } + + public void setColumn(final int column) { + this.column = column; + } + + @CheckForNull + public String getSource() { + return source; + } + + public void setSource(final String source) { + this.source = source; + } + + @CheckForNull + public String getSeverity() { + return severity; + } + + public void setSeverity(final String severity) { + this.severity = severity; + } + + @CheckForNull + public String getMessage() { + return message; + } + + public void setMessage(final String message) { + this.message = message; + } + + public int getLine() { + return line; + } + + public void setLine(final int line) { + this.line = line; + } + } + + /** + * Java Bean class for a errors collection of the Checkstyle format. + * + * @author Ullrich Hafner + */ + public static class CheckStyle { + /** All files of this violations collection. */ + private final List files = new ArrayList<>(); + + /** + * Adds a new file to this bug collection. + * + * @param file the file to add + */ + public void addFile(final File file) { + files.add(file); + } + + /** + * Returns all files of this violations collection. The returned collection is + * read-only. + * + * @return all files of this bug collection + */ + public Collection getFiles() { + return Collections.unmodifiableCollection(files); + } + } + + /** + * Java Bean class representing a Checkstyle rule. + * + * @author Ullrich Hafner + */ + @SuppressWarnings("PMD.DataClass") + public static class Rule { + /** Description to indicate that the rules stored in this plug-in don't match with the generators version. */ + static final String UNDEFINED_DESCRIPTION = StringUtils.EMPTY; + /** The name of the subsection that defines a description in the docbook files. */ + private static final String DESCRIPTION_SUBSECTION_NAME = "Description"; + + @CheckForNull + private String name; + @CheckForNull + private String description; + + /** + * Instantiates a new rule. + */ + public Rule() { + // nothing to do + } + + /** + * Instantiates a new rule. + * + * @param name + * the name of the rule + */ + public Rule(@CheckForNull final String name) { + this.name = name; + description = UNDEFINED_DESCRIPTION; + } + + /** + * Returns the name of this rule. + * + * @return the name + */ + public String getName() { + return StringUtils.defaultString(name); + } + + /** + * Sets the name of this rule. + * + * @param name + * the name + */ + public void setName(@CheckForNull final String name) { + this.name = name; + } + + /** + * Returns the description of this rule. + * + * @return the description + */ + public String getDescription() { + return StringUtils.defaultString(description); + } + + /** + * Sets the description of this rule. The description is only set if the topic is a description. + * + * @param topic + * the topic that might contain the description + */ + @SuppressFBWarnings("IMPROPER_UNICODE") + public void setDescription(final Topic topic) { + if (DESCRIPTION_SUBSECTION_NAME.equalsIgnoreCase(topic.getName())) { + description = topic.getValue(); + } + } + } + + /** + * Java Bean class representing a DocBook subsection. + * + * @author Ullrich Hafner + */ + @SuppressWarnings("PMD.DataClass") + public static class Topic { + @CheckForNull + private String name; + @CheckForNull + private String value; + + /** + * Returns the name of this topic. + * + * @return the name + */ + public String getName() { + return StringUtils.defaultString(name); + } + + /** + * Sets the name of this topic. + * + * @param name + * the name + */ + public void setName(@CheckForNull final String name) { + this.name = name; + } + + /** + * Returns the value of this topic. + * + * @return the value + */ + public String getValue() { + return StringUtils.defaultString(value); + } + + /** + * Sets the value of this topic. + * + * @param value + * the value + */ + public void setValue(@CheckForNull final String value) { + this.value = value; + } + } + + /** + * Digester rule to parse the actual content of a DocBook subsection node. Does not interpret XML elements that are + * children of a subsection. + * + * @author Ullrich Hafner + */ + public static class TopicRule extends NodeCreateRule { + /** + * Instantiates a new topic rule. + * + * @throws ParserConfigurationException + * the parser configuration exception + */ + TopicRule() throws ParserConfigurationException { + super(Node.ELEMENT_NODE); + } + + @Override + public void end(final String namespace, final String name) + throws TransformerException, InvocationTargetException, NoSuchMethodException, IllegalAccessException { + Element subsection = getDigester().pop(); + var description = extractNodeContent(subsection); + + MethodUtils.invokeExactMethod(getDigester().peek(), "setValue", description); + } + + /** + * Extracts the node content. Basically returns every character in the subsection element. + * + * @param subsection + * the subsection of a rule + * + * @return the node content + * @throws TransformerException + * in case of an error + */ + @SuppressFBWarnings("SECURITY") + private String extractNodeContent(final Element subsection) throws TransformerException { + var content = new StringWriter(); + + var transformer = new SecureXmlParserFactory().createTransformer(); + transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); + transformer.transform(new DOMSource(subsection), new StreamResult(content)); + var text = content.toString(); + var prefixRemoved = StringUtils.substringAfter(text, ">"); + var suffixRemoved = StringUtils.substringBeforeLast(prefixRemoved, "<"); + + var endSourceRemoved = StringUtils.replace(suffixRemoved, "", "
"); + + return StringUtils.replace(endSourceRemoved, "", "
");
+        }
+    }
+}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/CheckStyleRules.java b/src/main/java/edu/hm/hafner/analysis/parser/CheckStyleRules.java
similarity index 82%
rename from src/main/java/edu/hm/hafner/analysis/parser/checkstyle/CheckStyleRules.java
rename to src/main/java/edu/hm/hafner/analysis/parser/CheckStyleRules.java
index f833fff4e..a9d2bb98a 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/CheckStyleRules.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/CheckStyleRules.java
@@ -1,4 +1,4 @@
-package edu.hm.hafner.analysis.parser.checkstyle;
+package edu.hm.hafner.analysis.parser;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -23,7 +23,7 @@
  * @author Ullrich Hafner
  */
 public class CheckStyleRules {
-    private final Map rulesByName = new HashMap<>();
+    private final Map rulesByName = new HashMap<>();
 
     /**
      * Loads the available rules into a map.
@@ -33,12 +33,12 @@ public CheckStyleRules() {
                 "imports", "javadoc", "metrics", "misc", "modifier", "naming", "regexp",
                 "reporting", "sizes", "whitespace"};
         for (String ruleFile : ruleFiles) {
-            try (var inputStream = CheckStyleRules.class.getResourceAsStream("config_" + ruleFile + ".xml")) {
+            try (var inputStream = CheckStyleRules.class.getResourceAsStream("checkstyle/config_" + ruleFile + ".xml")) {
                 var digester = createDigester();
-                List rules = new ArrayList<>();
+                List rules = new ArrayList<>();
                 digester.push(rules);
                 digester.parse(inputStream);
-                for (Rule rule : rules) {
+                for (CheckStyleParser.Rule rule : rules) {
                     if (StringUtils.isNotBlank(rule.getDescription())) {
                         rulesByName.put(rule.getName(), rule);
                     }
@@ -66,15 +66,15 @@ private Digester createDigester() throws ParserConfigurationException {
         var digester = new SecureDigester(CheckStyleRules.class);
 
         var section = "*/section";
-        digester.addObjectCreate(section, Rule.class);
+        digester.addObjectCreate(section, CheckStyleParser.Rule.class);
         digester.addSetProperties(section);
         digester.addSetNext(section, "add");
 
         var subSection = "*/section/subsection";
-        digester.addObjectCreate(subSection, Topic.class);
+        digester.addObjectCreate(subSection, CheckStyleParser.Topic.class);
         digester.addSetProperties(subSection);
         digester.addSetNext(subSection, "setDescription");
-        digester.addRule(subSection, new TopicRule());
+        digester.addRule(subSection, new CheckStyleParser.TopicRule());
         return digester;
     }
 
@@ -83,7 +83,7 @@ private Digester createDigester() throws ParserConfigurationException {
      *
      * @return all Checkstyle rules
      */
-    public Collection getRules() {
+    public Collection getRules() {
         return Collections.unmodifiableCollection(rulesByName.values());
     }
 
@@ -95,13 +95,13 @@ public Collection getRules() {
      *
      * @return the Checkstyle rule with the specified name.
      */
-    public Rule getRule(final String name) {
+    public CheckStyleParser.Rule getRule(final String name) {
         var rule = rulesByName.get(name);
         if (rule == null) {
             rule = rulesByName.get(StringUtils.removeEnd(name, "Check"));
         }
         if (rule == null) {
-            return new Rule(name);
+            return new CheckStyleParser.Rule(name);
         }
         return rule;
     }
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/ClangAnalyzerPlistParser.java b/src/main/java/edu/hm/hafner/analysis/parser/ClangAnalyzerPlistParser.java
index 8dd64bbc4..78c0be321 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/ClangAnalyzerPlistParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/ClangAnalyzerPlistParser.java
@@ -38,7 +38,7 @@ public boolean accepts(final ReaderFactory readerFactory) {
     }
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException {
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException {
         try (var issueBuilder = new IssueBuilder()) {
             var doc = readerFactory.readDocument();
 
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/CpdParser.java b/src/main/java/edu/hm/hafner/analysis/parser/CpdParser.java
new file mode 100644
index 000000000..d4c2f5dac
--- /dev/null
+++ b/src/main/java/edu/hm/hafner/analysis/parser/CpdParser.java
@@ -0,0 +1,227 @@
+package edu.hm.hafner.analysis.parser;
+
+import java.io.Serial;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.digester3.Digester;
+
+import edu.hm.hafner.analysis.DuplicationGroup;
+import edu.hm.hafner.analysis.IssueBuilder;
+import edu.hm.hafner.analysis.Report;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+
+/**
+ * A parser for PMD's CPD XML files.
+ *
+ * @author Ullrich Hafner
+ */
+public class CpdParser extends AbstractDryParser {
+    /** Unique ID of this class. */
+    @Serial
+    private static final long serialVersionUID = 6507147028628714706L;
+
+    /**
+     * Creates a new instance of {@link CpdParser}.
+     *
+     * @param highThreshold
+     *         minimum number of duplicate lines for high priority warnings
+     * @param normalThreshold
+     *         minimum number of duplicate lines for normal priority warnings
+     */
+    public CpdParser(final int highThreshold, final int normalThreshold) {
+        super(highThreshold, normalThreshold);
+    }
+
+    /**
+     * Creates a new instance of {@link CpdParser}. The {@code highThreshold} is set to 50, the {@code normalThreshold}
+     * is set to 25.
+     */
+    public CpdParser() {
+        super(50, 25);
+    }
+
+    @Override
+    protected void configureParser(final Digester digester) {
+        var duplicationXPath = "*/pmd-cpd/duplication";
+        digester.addObjectCreate(duplicationXPath, Duplication.class);
+        digester.addSetProperties(duplicationXPath);
+        digester.addCallMethod(duplicationXPath + "/codefragment", "setCodeFragment", 0);
+        digester.addSetNext(duplicationXPath, "add");
+
+        var fileXPath = duplicationXPath + "/file";
+        digester.addObjectCreate(fileXPath, SourceFile.class);
+        digester.addSetProperties(fileXPath);
+        digester.addSetNext(fileXPath, "addFile", SourceFile.class.getName());
+    }
+
+    @Override
+    protected Report convertDuplicationsToIssues(final List duplications, final IssueBuilder issueBuilder) {
+        var report = new Report();
+
+        for (Duplication duplication : duplications) {
+            var group = new DuplicationGroup(duplication.getCodeFragment());
+            for (SourceFile file : duplication.getFiles()) {
+                issueBuilder.setSeverity(getPriority(duplication.getLines()))
+                        .setLineStart(file.getLine())
+                        .setLineEnd(file.getLine() + duplication.getLines() - 1)
+                        .setFileName(file.getPath())
+                        .setType("CPD")
+                        .setAdditionalProperties(group);
+                var issue = issueBuilder.build();
+                group.add(issue);
+                report.add(issue);
+            }
+        }
+        return report;
+    }
+
+    /**
+     * Java Bean class for a file of the PMD CPD format.
+     *
+     * @author Ullrich Hafner
+     */
+    @SuppressWarnings("PMD.DataClass")
+    public static class SourceFile {
+        /** Starting line number in file. */
+        private int line;
+        /** Path of the file. */
+        @CheckForNull
+        private String path;
+
+        /**
+         * Returns the path of this file.
+         *
+         * @return the path of this file
+         */
+        @CheckForNull
+        public String getPath() {
+            return path;
+        }
+
+        /**
+         * Sets the path of this file to the specified value.
+         *
+         * @param path
+         *         the value to set
+         */
+        public void setPath(@CheckForNull final String path) {
+            this.path = path;
+        }
+
+        /**
+         * Returns the line of the duplication.
+         *
+         * @return the line of the duplication
+         */
+        public int getLine() {
+            return line;
+        }
+
+        /**
+         * Sets the line of the duplication to the specified value.
+         *
+         * @param line
+         *         the value to set
+         */
+        public void setLine(final int line) {
+            this.line = line;
+        }
+    }
+
+    /**
+     * Java Bean class for a CPD duplication.
+     *
+     * @author Ullrich Hafner
+     */
+    @SuppressWarnings("PMD.DataClass")
+    public static class Duplication {
+        /** Number of duplicate lines. */
+        private int lines;
+        /** Number of duplicate tokens. */
+        private int tokens;
+        /** The duplicated code fragment. */
+        @CheckForNull
+        private String codeFragment;
+
+        /** All files of this duplication. */
+        private final List files = new ArrayList<>();
+
+        /**
+         * Adds a new file to this duplication.
+         *
+         * @param file
+         *            the new file
+         */
+        public void addFile(final SourceFile file) {
+            files.add(file);
+        }
+
+        /**
+         * Returns all files of the duplication. The returned collection is
+         * read-only.
+         *
+         * @return all files
+         */
+        public Collection getFiles() {
+            return Collections.unmodifiableCollection(files);
+        }
+
+        /**
+         * Returns the number of duplicate lines.
+         *
+         * @return the lines
+         */
+        public int getLines() {
+            return lines;
+        }
+
+        /**
+         * Sets the number of duplicate lines to the specified value.
+         *
+         * @param lines the value to set
+         */
+        public void setLines(final int lines) {
+            this.lines = lines;
+        }
+
+        /**
+         * Returns the number of duplicate tokens.
+         *
+         * @return the tokens
+         */
+        public int getTokens() {
+            return tokens;
+        }
+
+        /**
+         * Sets the number of duplicate tokens to the specified value.
+         *
+         * @param tokens the value to set
+         */
+        public void setTokens(final int tokens) {
+            this.tokens = tokens;
+        }
+
+        /**
+         * Returns the duplicate code fragment.
+         *
+         * @return the duplicate code fragment
+         */
+        @CheckForNull
+        public String getCodeFragment() {
+            return codeFragment;
+        }
+
+        /**
+         * Sets the duplicate code fragment to the specified value.
+         *
+         * @param codeFragment the value to set
+         */
+        public void setCodeFragment(@CheckForNull final String codeFragment) {
+            this.codeFragment = codeFragment;
+        }
+    }
+}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/DupFinderParser.java b/src/main/java/edu/hm/hafner/analysis/parser/DupFinderParser.java
new file mode 100644
index 000000000..6708be112
--- /dev/null
+++ b/src/main/java/edu/hm/hafner/analysis/parser/DupFinderParser.java
@@ -0,0 +1,292 @@
+package edu.hm.hafner.analysis.parser;
+
+import java.io.Serial;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.digester3.Digester;
+
+import edu.hm.hafner.analysis.DuplicationGroup;
+import edu.hm.hafner.analysis.IssueBuilder;
+import edu.hm.hafner.analysis.Report;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * A parser for Reshaper Dupfinder XML files.
+ *
+ * @author Rafal Jasica
+ */
+public class DupFinderParser extends AbstractDryParser {
+    /** Unique ID of this class. */
+    @Serial
+    private static final long serialVersionUID = 1357147358617711901L;
+
+    /**
+     * Creates a new instance of {@link DupFinderParser}.
+     *
+     * @param highThreshold
+     *         minimum number of duplicate lines for high priority warnings
+     * @param normalThreshold
+     *         minimum number of duplicate lines for normal priority warnings
+     */
+    public DupFinderParser(final int highThreshold, final int normalThreshold) {
+        super(highThreshold, normalThreshold);
+    }
+
+    /**
+     * Creates a new instance of {@link DupFinderParser}. The {@code highThreshold} is set to 50, the {@code normalThreshold}
+     * is set to 25.
+     */
+    public DupFinderParser() {
+        super(50, 25);
+    }
+
+    @Override
+    protected void configureParser(final Digester digester) {
+        var duplicationXPath = "*/DuplicatesReport/Duplicates/Duplicate";
+        digester.addObjectCreate(duplicationXPath, Duplicate.class);
+        digester.addSetProperties(duplicationXPath, "Cost", "cost");
+        digester.addSetNext(duplicationXPath, "add");
+
+        var fragmentXPath = duplicationXPath + "/Fragment";
+        digester.addObjectCreate(fragmentXPath, Fragment.class);
+        digester.addBeanPropertySetter(fragmentXPath + "/FileName", "fileName");
+        digester.addBeanPropertySetter(fragmentXPath + "/Text", "text");
+        digester.addSetNext(fragmentXPath, "addFragment", Fragment.class.getName());
+
+        var lineRangeXPath = fragmentXPath + "/LineRange";
+        digester.addObjectCreate(lineRangeXPath, Range.class);
+        digester.addSetProperties(lineRangeXPath, "Start", "start");
+        digester.addSetProperties(lineRangeXPath, "End", "end");
+        digester.addSetNext(lineRangeXPath, "setLineRange", Range.class.getName());
+
+        var offsetRangeXPath = fragmentXPath + "/OffsetRange";
+        digester.addObjectCreate(offsetRangeXPath, Range.class);
+        digester.addSetProperties(offsetRangeXPath, "Start", "start");
+        digester.addSetProperties(offsetRangeXPath, "End", "end");
+        digester.addSetNext(offsetRangeXPath, "setOffsetRange", Range.class.getName());
+    }
+
+    @Override
+    protected Report convertDuplicationsToIssues(final List duplications, final IssueBuilder issueBuilder) {
+        var report = new Report();
+
+        for (Duplicate duplication : duplications) {
+            var group = new DuplicationGroup();
+            for (Fragment fragment : duplication.getFragments()) {
+                group.setCodeFragment(fragment.getText());
+                var lineRange = fragment.getLineRange();
+                int count = lineRange.getEnd() - lineRange.getStart() + 1;
+                issueBuilder.setSeverity(getPriority(count))
+                        .setLineStart(lineRange.getStart())
+                        .setLineEnd(lineRange.getEnd())
+                        .setFileName(fragment.getFileName())
+                        .setType("DupFinder")
+                        .setAdditionalProperties(group);
+                var issue = issueBuilder.build();
+                group.add(issue);
+                report.add(issue);
+            }
+        }
+
+        return report;
+    }
+
+    /**
+     * Java Bean class for a Reshaper DupFinder duplicate.
+     *
+     * @author Rafal Jasica
+     */
+    public static class Duplicate {
+        /** The duplicated cost. */
+        private int cost;
+
+        /** All files of this duplication. */
+        private final List fragments = new ArrayList<>();
+
+        /**
+         * Returns the duplicate cost.
+         *
+         * @return the duplicate cost
+         */
+        public int getCost() {
+            return cost;
+        }
+
+        /**
+         * Sets the duplicate cost to the specified value.
+         *
+         * @param cost the value to set
+         */
+        public void setCost(final int cost) {
+            this.cost = cost;
+        }
+
+        /**
+         * Adds a new file to this duplication.
+         *
+         * @param file
+         *            the new file
+         */
+        public void addFragment(final Fragment file) {
+            fragments.add(file);
+        }
+
+        /**
+         * Returns all files of the duplication. The returned collection is
+         * read-only.
+         *
+         * @return all files
+         */
+        public Collection getFragments() {
+            return Collections.unmodifiableCollection(fragments);
+        }
+    }
+
+    /**
+     * Java Bean class for a Reshaper DupFinder fragment.
+     *
+     * @author Rafal Jasica
+     */
+    @SuppressWarnings("PMD.DataClass")
+    @SuppressFBWarnings("EI")
+    public static class Fragment {
+        @CheckForNull
+        private String fileName;
+        @CheckForNull
+        private String text;
+        @CheckForNull
+        private Range lineRange;
+        @CheckForNull
+        private Range offsetRange;
+
+        /**
+         * Returns the file name.
+         *
+         * @return the path of this file
+         */
+        @CheckForNull
+        public String getFileName() {
+            return fileName;
+        }
+
+        /**
+         * Sets the file name to the specified value.
+         *
+         * @param fileName the value to set
+         */
+        @SuppressFBWarnings("NM")
+        public void setFileName(@CheckForNull final String fileName) {
+            this.fileName = fileName;
+        }
+
+        /**
+         * Returns the text.
+         *
+         * @return the text
+         */
+        @CheckForNull
+        public String getText() {
+            return text;
+        }
+
+        /**
+         * Sets the text to the specified value.
+         *
+         * @param text the value to set
+         */
+        public void setText(@CheckForNull final String text) {
+            this.text = text;
+        }
+
+        /**
+         * Returns the line range.
+         *
+         * @return the line range
+         */
+        public Range getLineRange() {
+            if (lineRange == null) {
+                return new Range();
+            }
+            return lineRange;
+        }
+
+        /**
+         * Sets the line range to the specified value.
+         *
+         * @param lineRange the value to set
+         */
+        public void setLineRange(@CheckForNull final Range lineRange) {
+            this.lineRange = lineRange;
+        }
+
+        /**
+         * Returns the offset range.
+         *
+         * @return the offset range
+         */
+        @CheckForNull
+        public Range getOffsetRange() {
+            return offsetRange;
+        }
+
+        /**
+         * Sets the offset range to the specified value.
+         *
+         * @param offsetRange the value to set
+         */
+        public void setOffsetRange(@CheckForNull final Range offsetRange) {
+            this.offsetRange = offsetRange;
+        }
+    }
+
+    /**
+     * Java Bean class for a Reshaper DupFinder range.
+     *
+     * @author Rafal Jasica
+     */
+    @SuppressWarnings("PMD.DataClass")
+    public static class Range {
+        private int start;
+        private int end;
+
+        /**
+         * Returns the start.
+         *
+         * @return the start
+         */
+        public int getStart() {
+            return start;
+        }
+
+        /**
+         * Sets the start to the specified value.
+         *
+         * @param start the value to set
+         */
+        public void setStart(final int start) {
+            this.start = start;
+        }
+
+        /**
+         * Returns the line range start.
+         *
+         * @return the line range start
+         */
+        public int getEnd() {
+            return end;
+        }
+
+        /**
+         * Sets the end to the specified value.
+         *
+         * @param end the value to set
+         */
+        public void setEnd(final int end) {
+            this.end = end;
+        }
+    }
+}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/EclipseXMLParser.java b/src/main/java/edu/hm/hafner/analysis/parser/EclipseXmlParser.java
similarity index 97%
rename from src/main/java/edu/hm/hafner/analysis/parser/EclipseXMLParser.java
rename to src/main/java/edu/hm/hafner/analysis/parser/EclipseXmlParser.java
index e54b6f255..f0ace0246 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/EclipseXMLParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/EclipseXmlParser.java
@@ -22,7 +22,7 @@
  *
  * @author Jason Faust
  */
-public class EclipseXMLParser extends IssueParser {
+public class EclipseXmlParser extends IssueParser {
     static final String PREVIEW_RELATED = "Preview Related";
     static final String COMPLIANCE = "Compliance";
     static final String MODULE = "Module";
@@ -50,7 +50,7 @@ public boolean accepts(final ReaderFactory readerFactory) {
     }
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException {
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException {
         try (var issueBuilder = new IssueBuilder()) {
             var doc = readerFactory.readDocument();
 
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/EmbeddedEngineerParser.java b/src/main/java/edu/hm/hafner/analysis/parser/EmbeddedEngineerParser.java
index e8572c267..d643ddcef 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/EmbeddedEngineerParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/EmbeddedEngineerParser.java
@@ -35,7 +35,7 @@ public class EmbeddedEngineerParser extends IssueParser {
             "^\\[([^\\]]*)\\]\\s(?Error|Warn)\\s-\\s(?[^:]*)" + "(:\\s|\\s\\()(?.+)");
 
     @Override
-    public Report parse(final ReaderFactory reader) throws ParsingException {
+    public Report parseReport(final ReaderFactory reader) throws ParsingException {
         try (Stream lines = reader.readStream()) {
             try (var lookahead = new LookaheadStream(lines, reader.getFileName())) {
                 return parse(lookahead);
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/findbugs/FindBugsMessages.java b/src/main/java/edu/hm/hafner/analysis/parser/FindBugsMessages.java
similarity index 98%
rename from src/main/java/edu/hm/hafner/analysis/parser/findbugs/FindBugsMessages.java
rename to src/main/java/edu/hm/hafner/analysis/parser/FindBugsMessages.java
index 80f7f9f5f..25d78534d 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/findbugs/FindBugsMessages.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/FindBugsMessages.java
@@ -1,4 +1,4 @@
-package edu.hm.hafner.analysis.parser.findbugs;
+package edu.hm.hafner.analysis.parser;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -45,7 +45,7 @@ public FindBugsMessages() {
 
     private void loadMessages(final String fileName, final Map messagesCache,
             final Map shortMessagesCache) throws IOException, SAXException {
-        try (var file = FindBugsMessages.class.getResourceAsStream(fileName)) {
+        try (var file = FindBugsMessages.class.getResourceAsStream("findbugs/" + fileName)) {
             List patterns = parse(file);
             for (Pattern pattern : patterns) {
                 messagesCache.put(pattern.getType(), pattern.getDescription());
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/findbugs/FindBugsParser.java b/src/main/java/edu/hm/hafner/analysis/parser/FindBugsParser.java
similarity index 98%
rename from src/main/java/edu/hm/hafner/analysis/parser/findbugs/FindBugsParser.java
rename to src/main/java/edu/hm/hafner/analysis/parser/FindBugsParser.java
index 297caa089..53365a736 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/findbugs/FindBugsParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/FindBugsParser.java
@@ -1,4 +1,4 @@
-package edu.hm.hafner.analysis.parser.findbugs; // NOPMD
+package edu.hm.hafner.analysis.parser; // NOPMD
 
 import java.io.IOException;
 import java.io.Reader;
@@ -30,7 +30,7 @@
 import edu.umd.cs.findbugs.annotations.CheckForNull;
 import edu.umd.cs.findbugs.ba.SourceFinder;
 
-import static edu.hm.hafner.analysis.parser.findbugs.FindBugsParser.PriorityProperty.*;
+import static edu.hm.hafner.analysis.parser.FindBugsParser.PriorityProperty.*;
 
 /**
  * A parser for the native FindBugs XML files.
@@ -78,7 +78,7 @@ public FindBugsParser(final PriorityProperty priorityProperty) {
     }
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException {
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException {
         try (var builder = new IssueBuilder()) {
             List sources = new ArrayList<>();
             var moduleRoot = StringUtils.substringBefore(readerFactory.getFileName(), "/target/");
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/fxcop/FxCopParser.java b/src/main/java/edu/hm/hafner/analysis/parser/FxCopParser.java
similarity index 63%
rename from src/main/java/edu/hm/hafner/analysis/parser/fxcop/FxCopParser.java
rename to src/main/java/edu/hm/hafner/analysis/parser/FxCopParser.java
index 49055f4ee..273cb0fe9 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/fxcop/FxCopParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/FxCopParser.java
@@ -1,7 +1,11 @@
-package edu.hm.hafner.analysis.parser.fxcop;
+package edu.hm.hafner.analysis.parser;
 
 import java.io.Serial;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
 
+import org.apache.commons.lang3.StringUtils;
 import org.w3c.dom.Element;
 
 import edu.hm.hafner.analysis.IssueBuilder;
@@ -11,6 +15,7 @@
 import edu.hm.hafner.analysis.ReaderFactory;
 import edu.hm.hafner.analysis.Report;
 import edu.hm.hafner.analysis.util.XmlElementUtil;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
 
 /**
  * Parses a fxcop xml report file.
@@ -22,7 +27,7 @@ public class FxCopParser extends IssueParser {
     private static final int CAPACITY = 1024;
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
         return new XmlParser().parse(readerFactory);
     }
 
@@ -201,4 +206,159 @@ private String getString(final Element element, final String name) {
             }
         }
     }
+
+    /**
+     * Internal model for a FxCop rule.
+     *
+     * @author Erik Ramfelt
+     */
+    @SuppressWarnings({"PMD", "all", "CheckStyle"})
+    public static class FxCopRule {
+        private final String typeName;
+        private final String category;
+        private final String checkId;
+        @CheckForNull
+        private String name;
+        @CheckForNull
+        private String url;
+        @CheckForNull
+        private String description;
+
+        public FxCopRule(final String typeName, final String category, final String checkId) {
+            this.typeName = typeName;
+            this.category = category;
+            this.checkId = checkId;
+        }
+
+        @CheckForNull
+        public String getName() {
+            return name;
+        }
+
+        public void setName(final String name) {
+            this.name = name;
+        }
+
+        @CheckForNull
+        public String getUrl() {
+            return url;
+        }
+
+        public void setUrl(final String url) {
+            this.url = url;
+        }
+
+        @CheckForNull
+        public String getDescription() {
+            return description;
+        }
+
+        public void setDescription(final String description) {
+            this.description = description;
+        }
+
+        public String getTypeName() {
+            return typeName;
+        }
+
+        public String getCategory() {
+            return category;
+        }
+
+        public String getCheckId() {
+            return checkId;
+        }
+    }
+
+    /**
+     * Internal set containing rules for FxCop.
+     *
+     * @author Erik Ramfelt
+     */
+    @SuppressWarnings({"PMD", "all", "CheckStyle"})
+    public static class FxCopRuleSet {
+        private final Map rules = new HashMap<>();
+
+        /***
+         * Parse the element and insert the rule into the rule set.
+         * @param element the element
+         */
+        public void addRule(final Element element) {
+            var rule = new FxCopRule(element.getAttribute("TypeName"), element.getAttribute("Category"), element
+                    .getAttribute("CheckId"));
+            rule.setUrl(getNamedTagText(element, "Url"));
+            rule.setDescription(getNamedTagText(element, "Description"));
+            rule.setName(getNamedTagText(element, "Name"));
+
+            rules.put(getRuleKey(rule.getCategory(), rule.getCheckId()), rule);
+        }
+
+        /**
+         * Returns the text value of the named child element if it exists
+         *
+         * @param element
+         *         the element to check look for child elements
+         * @param tagName
+         *         the name of the child element
+         *
+         * @return the text value; or "" if no element was found
+         */
+        private String getNamedTagText(final Element element, final String tagName) {
+            Optional foundElement = XmlElementUtil.getFirstChildElementByName(element, tagName);
+            if (foundElement.isPresent()) {
+                return foundElement.get().getTextContent();
+            }
+            else {
+                return StringUtils.EMPTY;
+            }
+        }
+
+        /**
+         * Returns if the rule set contains a rule for the specified category and id
+         *
+         * @param category
+         *         the rule category
+         * @param checkId
+         *         the rule id
+         *
+         * @return {@code true}  if the rule set contains a rule for the specified category and id, {@code false} otherwise
+         */
+        public boolean contains(final String category, final String checkId) {
+            return rules.containsKey(getRuleKey(category, checkId));
+        }
+
+        /**
+         * Returns the specified rule if it exists
+         *
+         * @param category
+         *         the rule category
+         * @param checkId
+         *         the id of the rule
+         *
+         * @return the rule; null otherwise
+         */
+        @CheckForNull
+        public FxCopRule getRule(final String category, final String checkId) {
+            var key = getRuleKey(category, checkId);
+            FxCopRule rule = null;
+            if (rules.containsKey(key)) {
+                rule = rules.get(key);
+            }
+            return rule;
+        }
+
+        /**
+         * Returns the key for the map
+         *
+         * @param category
+         *         category of the rule
+         * @param checkId
+         *         id of the rule
+         *
+         * @return category + "#" + checkid
+         */
+        private String getRuleKey(final String category, final String checkId) {
+            return category + "#" + checkId;
+        }
+    }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/gendarme/GendarmeParser.java b/src/main/java/edu/hm/hafner/analysis/parser/GendarmeParser.java
similarity index 72%
rename from src/main/java/edu/hm/hafner/analysis/parser/gendarme/GendarmeParser.java
rename to src/main/java/edu/hm/hafner/analysis/parser/GendarmeParser.java
index 50761662a..243607a6f 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/gendarme/GendarmeParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/GendarmeParser.java
@@ -1,4 +1,4 @@
-package edu.hm.hafner.analysis.parser.gendarme;
+package edu.hm.hafner.analysis.parser;
 
 import java.io.Serial;
 import java.net.MalformedURLException;
@@ -18,6 +18,7 @@
 import edu.hm.hafner.analysis.Report;
 import edu.hm.hafner.analysis.Severity;
 import edu.hm.hafner.analysis.util.XmlElementUtil;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
 
 import static edu.hm.hafner.analysis.util.IntegerParser.*;
 
@@ -36,7 +37,7 @@ public class GendarmeParser extends IssueParser {
     private static final String ASSEMBLY = "Assembly";
 
     @Override
-    public Report parse(final ReaderFactory factory) throws ParsingException {
+    public Report parseReport(final ReaderFactory factory) throws ParsingException {
         var document = factory.readDocument();
 
         var mainNode = document.getElementsByTagName("gendarme-output");
@@ -81,19 +82,16 @@ private Report parseViolations(final List ruleElements, final Map Severity.WARNING_LOW;
+            case "High" -> Severity.WARNING_HIGH;
+            default -> Severity.WARNING_NORMAL;
+        };
     }
 
     private String extractFileNameMatch(final GendarmeRule rule, final String source, final int group) {
         var fileName = StringUtils.EMPTY;
-        if (rule.getType() == GendarmeRuleType.Method) {
+        if (rule.getType() == GendarmeRuleType.METHOD) {
             var matcher = FILE_PATTERN.matcher(source);
             if (matcher.matches()) {
                 fileName = matcher.group(group);
@@ -113,13 +111,13 @@ private Map parseRules(final List ruleElements) {
             var typeString = ruleElement.getAttribute(TYPE);
             switch (typeString) {
                 case TYPE:
-                    rule.setType(GendarmeRuleType.Type);
+                    rule.setType(GendarmeRuleType.TYPE);
                     break;
                 case METHOD:
-                    rule.setType(GendarmeRuleType.Method);
+                    rule.setType(GendarmeRuleType.METHOD);
                     break;
                 case ASSEMBLY:
-                    rule.setType(GendarmeRuleType.Assembly);
+                    rule.setType(GendarmeRuleType.ASSEMBLY);
                     break;
                 default:
                     // ignore the type
@@ -137,4 +135,56 @@ private Map parseRules(final List ruleElements) {
 
         return rules;
     }
+
+    @SuppressWarnings("all")
+    static class GendarmeRule {
+        @CheckForNull
+        private String name;
+        @CheckForNull
+        private String typeName;
+        @CheckForNull
+        private GendarmeRuleType type;
+        @CheckForNull
+        private URL url;
+
+        @CheckForNull
+        public String getTypeName() {
+            return typeName;
+        }
+
+        public void setTypeName(final String typeName) {
+            this.typeName = typeName;
+        }
+
+        @CheckForNull
+        public String getName() {
+            return name;
+        }
+
+        public void setName(final String name) {
+            this.name = name;
+        }
+
+        @CheckForNull
+        public GendarmeRuleType getType() {
+            return type;
+        }
+
+        public void setType(final GendarmeRuleType type) {
+            this.type = type;
+        }
+
+        @CheckForNull
+        public URL getUrl() {
+            return url;
+        }
+
+        public void setUrl(@CheckForNull final URL url) {
+            this.url = url;
+        }
+    }
+
+    enum GendarmeRuleType {
+        METHOD, TYPE, ASSEMBLY
+    }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/GhsMultiParser.java b/src/main/java/edu/hm/hafner/analysis/parser/GhsMultiParser.java
index 5728bdcff..f1796da14 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/GhsMultiParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/GhsMultiParser.java
@@ -22,7 +22,7 @@ public class GhsMultiParser extends LookaheadParser {
     private static final long serialVersionUID = 8149238560432255036L;
 
     /**
-     * Regex Pattern to match start of Warning / Error. Groups are used to identify FileName, StartLine, Type, Category,
+     * Regex Pattern to match start of Warning / Error. Groups are used to identify FileName, StartLine, IssueType, Category,
      * Start of message.
      */
     private static final String GHS_MULTI_WARNING_PATTERN = "\"(?.*)\"\\,"
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/IdeaInspectionParser.java b/src/main/java/edu/hm/hafner/analysis/parser/IdeaInspectionParser.java
index 4b8353bad..79baf5bb2 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/IdeaInspectionParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/IdeaInspectionParser.java
@@ -29,7 +29,7 @@ public class IdeaInspectionParser extends IssueParser {
     private static final String ERROR = "ERROR";
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException {
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException {
         var document = readerFactory.readDocument();
 
         var rootElement = (Element) document.getElementsByTagName("problems").item(0);
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/JcReportParser.java b/src/main/java/edu/hm/hafner/analysis/parser/JcReportParser.java
new file mode 100644
index 000000000..26704c6ad
--- /dev/null
+++ b/src/main/java/edu/hm/hafner/analysis/parser/JcReportParser.java
@@ -0,0 +1,482 @@
+package edu.hm.hafner.analysis.parser;
+
+import java.io.IOException;
+import java.io.Serial;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.xml.sax.SAXException;
+
+import edu.hm.hafner.analysis.IssueBuilder;
+import edu.hm.hafner.analysis.IssueParser;
+import edu.hm.hafner.analysis.ParsingException;
+import edu.hm.hafner.analysis.ReaderFactory;
+import edu.hm.hafner.analysis.Report;
+import edu.hm.hafner.analysis.SecureDigester;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * JcReportParser-Class. This class parses from the jcReport.xml and creates warnings from its content.
+ *
+ * @author Johann Vierthaler, johann.vierthaler@web.de
+ */
+public class JcReportParser extends IssueParser {
+    @Serial
+    private static final long serialVersionUID = -1302787609831475403L;
+
+    @Override
+    public Report parseReport(final ReaderFactory reader) {
+        try (var issueBuilder = new IssueBuilder()) {
+            var report = createReport(reader);
+            var warnings = new Report();
+            for (int i = 0; i < report.getFiles().size(); i++) {
+                var file = report.getFiles().get(i);
+
+                for (int j = 0; j < file.getItems().size(); j++) {
+                    var item = file.getItems().get(j);
+                    issueBuilder.setFileName(file.getName())
+                            .setLineStart(item.getLine())
+                            .setColumnStart(item.getColumn())
+                            .setColumnEnd(item.getEndcolumn())
+                            .setCategory(item.getFindingtype())
+                            .setPackageName(file.getPackageName())
+                            .setMessage(item.getMessage())
+                            .guessSeverity(item.getSeverity());
+
+                    warnings.add(issueBuilder.buildAndClean());
+                }
+            }
+            return warnings;
+        }
+    }
+
+    /**
+     * Creates a Report-Object out of the content within the JcReport.xml.
+     *
+     * @param readerFactory
+     *         the Reader-object that is the source to build the Report-Object.
+     *
+     * @return the finished Report-Object that creates the Warnings.
+     * @throws ParsingException
+     *         due to digester.parse(new InputSource(source))
+     */
+    JcReport createReport(final ReaderFactory readerFactory)
+            throws ParsingException {
+        var digester = new SecureDigester(JcReportParser.class);
+
+        var report = "report";
+        digester.addObjectCreate(report, JcReport.class);
+        digester.addSetProperties(report);
+
+        var file = "report/file";
+        digester.addObjectCreate(file, File.class);
+        digester.addSetProperties(file, "package", "packageName");
+        digester.addSetProperties(file, "src-dir", "srcdir");
+        digester.addSetProperties(file);
+        digester.addSetNext(file, "addFile", File.class.getName());
+
+        var item = "report/file/item";
+        digester.addObjectCreate(item, Item.class);
+        digester.addSetProperties(item);
+        digester.addSetProperties(item, "finding-type", "findingtype");
+        digester.addSetProperties(item, "end-line", "endline");
+        digester.addSetProperties(item, "end-column", "endcolumn");
+        digester.addSetNext(item, "addItem", Item.class.getName());
+
+        try (var reader = readerFactory.create()) {
+            return digester.parse(reader);
+        }
+        catch (IOException | SAXException e) {
+            throw new ParsingException(e);
+        }
+    }
+
+    /**
+     * File-Class. Stores field to create a warning. It represents the File-Tags within the report.xml. The
+     * Java-Bean-Conformity was chosen due to the digesters style of assigning.
+     *
+     * @author Johann Vierthaler, johann.vierthaler@web.de
+     */
+    @SuppressWarnings("PMD.DataClass")
+    public static class File {
+        @CheckForNull
+        private String name;
+        @CheckForNull
+        private String packageName;
+        @CheckForNull
+        private String srcdir;
+        private final List items = new ArrayList<>();
+
+        /**
+         * These properties are not used to create Warnings. It was decided to keep them available when Jenkins is modified
+         * and needs access to these fields;
+         */
+        @CheckForNull
+        private String level;
+        @CheckForNull
+        private String loc;
+        @CheckForNull
+        private String classname;
+
+        /**
+         * Getter for the Item-Collection.
+         *
+         * @return unmodifiable collection of Item-Objects
+         */
+        public List getItems() {
+            return Collections.unmodifiableList(items);
+        }
+
+        /**
+         * Adds an Item-Object to the collection items.
+         *
+         * @param item add this item.
+         */
+        public void addItem(final Item item) {
+            items.add(item);
+        }
+
+
+        /**
+         * Getter for className-Field.
+         *
+         * @return String className.
+         */
+        @CheckForNull
+        public String getClassname() {
+            return classname;
+        }
+
+        /**
+         * Setter for className-Field.
+         *
+         * @param classname lassNamesetter
+         */
+        public void setClassname(@CheckForNull final String classname) {
+            this.classname = classname;
+        }
+
+        /**
+         * Getter for level-Field.
+         *
+         * @return level
+         */
+        @CheckForNull
+        public String getLevel() {
+            return level;
+        }
+
+        /**
+         * Setter for level-Field.
+         *
+         * @param level set level
+         */
+        public void setLevel(@CheckForNull final String level) {
+            this.level = level;
+        }
+
+
+        /**
+         * Getter for loc-Field.
+         *
+         * @return loc loc
+         */
+        @CheckForNull
+        public String getLoc() {
+            return loc;
+        }
+
+        /**
+         * Setter for loc-Field.
+         *
+         * @param loc locsetter
+         */
+        public void setLoc(@CheckForNull final String loc) {
+            this.loc = loc;
+        }
+
+
+        /**
+         * Getter for name-Field.
+         *
+         * @return name name
+         */
+        @CheckForNull
+        public String getName() {
+            return name;
+        }
+
+        /**
+         * Setter for Name-Field.
+         *
+         * @param name name
+         */
+        public void setName(@CheckForNull final String name) {
+            this.name = name;
+        }
+
+
+        /**
+         * Getter for packageName-Field.
+         *
+         * @return packageName packageName.
+         */
+        @CheckForNull
+        public String getPackageName() {
+            return packageName;
+        }
+
+        /**
+         * Setter for packageName-Field.
+         *
+         * @param packageName packageName Setter
+         */
+        public void setPackageName(@CheckForNull final String packageName) {
+            this.packageName = packageName;
+        }
+
+        /**
+         * Getter for srcdir-Field.
+         *
+         * @return srcdir srcdir.
+         */
+        @CheckForNull
+        public String getSrcdir() {
+            return srcdir;
+        }
+
+        /**
+         * Setter for srcdir-Field.
+         *
+         * @param srcdir srcdir
+         */
+        public void setSrcdir(@CheckForNull final String srcdir) {
+            this.srcdir = srcdir;
+        }
+    }
+
+    /**
+     * This the Item-Class The Java-Bean-Conformity was chosen due to the digesters style of assigning. It represents the
+     * Item-Tags within the report.xml. Items have properties, that are mandatory to create a warning.
+     *
+     * @author Johann Vierthaler, johann.vierthaler@web.de
+     */
+    @SuppressWarnings("PMD.DataClass")
+    public static class Item {
+        @CheckForNull
+        private String column;
+        @CheckForNull
+        private String findingtype;
+        @CheckForNull
+        private String line;
+        @CheckForNull
+        private String message;
+        @CheckForNull
+        private String origin;
+        @CheckForNull
+        private String severity;
+        @CheckForNull
+        private String endcolumn;
+
+        /**
+         * Although this property is not used. It was decided to keep it available when Jenkins is modified and needs access
+         * to this field;
+         */
+        @CheckForNull
+        private String endline;
+
+        /**
+         * Getter for column-Field.
+         *
+         * @return column string
+         */
+        @CheckForNull
+        public String getColumn() {
+            return column;
+        }
+
+        /**
+         * Setter for Column-Field.
+         *
+         * @param column setter
+         */
+        public void setColumn(@CheckForNull final String column) {
+            this.column = column;
+        }
+
+        /**
+         * Getter for findingtype-Field.
+         *
+         * @return findingtype getter
+         */
+        @CheckForNull
+        public String getFindingtype() {
+            return findingtype;
+        }
+
+        /**
+         * Setter for findingtype-Field.
+         *
+         * @param findingtype setter
+         */
+        public void setFindingtype(@CheckForNull final String findingtype) {
+            this.findingtype = findingtype;
+        }
+
+        /**
+         * Getter for line-Field.
+         *
+         * @return line getter
+         */
+        @CheckForNull
+        public String getLine() {
+            return line;
+        }
+
+        /**
+         * Setter for line-Field.
+         *
+         * @param line setter
+         */
+        public void setLine(@CheckForNull final String line) {
+            this.line = line;
+        }
+
+        /**
+         * Getter for message-Field.
+         *
+         * @return message getter
+         */
+        @CheckForNull
+        public String getMessage() {
+            return message;
+        }
+
+        /**
+         * Setter for message-Field.
+         *
+         * @param message setter
+         */
+        public void setMessage(@CheckForNull final String message) {
+            this.message = message;
+        }
+
+        /**
+         * Getter for origin-Field.
+         *
+         * @return origin getter
+         */
+        @CheckForNull
+        public String getOrigin() {
+            return origin;
+        }
+
+        /**
+         * Setter for origin-Field.
+         *
+         * @param origin setter
+         */
+        public void setOrigin(@CheckForNull final String origin) {
+            this.origin = origin;
+        }
+
+        /**
+         * Getter for severity-Field.
+         *
+         * @return severity getter
+         */
+        @CheckForNull
+        public String getSeverity() {
+            return severity;
+        }
+
+        /**
+         * Setter for severtiy-Field.
+         *
+         * @param severity setter
+         */
+        public void setSeverity(@CheckForNull final String severity) {
+            this.severity = severity;
+        }
+
+
+        /**
+         * Getter for endline-Field.
+         *
+         * @return endline getter
+         */
+        @CheckForNull
+        public String getEndline() {
+            return endline;
+        }
+
+        /**
+         * Setter for endline-Field.
+         *
+         * @param endline setter
+         */
+        public void setEndline(@CheckForNull final String endline) {
+            this.endline = endline;
+        }
+
+        /**
+         * Getter for endcolumn-Field.
+         *
+         * @return endcolumn getter
+         */
+        @CheckForNull
+        public String getEndcolumn() {
+            return endcolumn;
+        }
+
+        /**
+         * Setter for endcolumn-Field.
+         *
+         * @param endcolumn setter
+         */
+        public void setEndcolumn(@CheckForNull final String endcolumn) {
+            this.endcolumn = endcolumn;
+        }
+    }
+
+    /**
+     * This is the Report-Class. It is mandatory to create Warnings. It represents the outer-most node within the
+     * report.xml.
+     *
+     * @author Johann Vierthaler, johann.vierthaler@web.de
+     */
+    @SuppressWarnings("PMD.DataClass")
+    @SuppressFBWarnings("EI")
+    public static class JcReport {
+        private List files = new ArrayList<>();
+
+        /**
+         * Returns an unmodifiable Collection.
+         *
+         * @return files getter
+         */
+        public List getFiles() {
+            return Collections.unmodifiableList(files);
+        }
+
+        /**
+         * Setter for the List files.
+         *
+         * @param files a list of files.
+         */
+        public void setFiles(final List files) {
+            this.files = files;
+        }
+
+        /**
+         * Adds a new File to the Collection.
+         *
+         * @param file setter
+         */
+        public void addFile(final File file) {
+            files.add(file);
+        }
+    }
+}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/JsonBaseParser.java b/src/main/java/edu/hm/hafner/analysis/parser/JsonBaseParser.java
index d2b063669..6cd823e4d 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/JsonBaseParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/JsonBaseParser.java
@@ -91,9 +91,6 @@ Optional convertToIssue(final JSONObject jsonIssue, final IssueBuilder bu
         if (jsonIssue.has(MODULE_NAME)) {
             builder.setModuleName(jsonIssue.getString(MODULE_NAME));
         }
-        if (jsonIssue.has(ORIGIN)) {
-            builder.setOrigin(jsonIssue.getString(ORIGIN));
-        }
         if (jsonIssue.has(PACKAGE_NAME)) {
             builder.setPackageName(jsonIssue.getString(PACKAGE_NAME));
         }
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/JsonIssueParser.java b/src/main/java/edu/hm/hafner/analysis/parser/JsonIssueParser.java
index 9dd504230..49e63f0d9 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/JsonIssueParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/JsonIssueParser.java
@@ -24,7 +24,7 @@ public abstract class JsonIssueParser extends IssueParser {
     private static final long serialVersionUID = -4062256623915009878L;
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException {
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException {
         var report = new Report();
         try (var reader = readerFactory.create(); var issueBuilder = new IssueBuilder()) {
             var parsedValue = new JSONTokener(reader).nextValue();
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/JsonLogParser.java b/src/main/java/edu/hm/hafner/analysis/parser/JsonLogParser.java
index dc6df85f0..c82ef12f6 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/JsonLogParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/JsonLogParser.java
@@ -28,7 +28,7 @@ public boolean accepts(final ReaderFactory readerFactory) {
     }
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException {
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException {
         try (Stream lines = readerFactory.readStream()) {
             var report = new Report();
             lines.map(String::trim)
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/JsonParser.java b/src/main/java/edu/hm/hafner/analysis/parser/JsonParser.java
index e617f91a8..4c828996c 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/JsonParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/JsonParser.java
@@ -32,7 +32,7 @@ public boolean accepts(final ReaderFactory readerFactory) {
     }
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException {
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException {
         try (var reader = readerFactory.create(); var builder = new IssueBuilder()) {
             var jsonReport = (JSONObject) new JSONTokener(reader).nextValue();
 
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/LintParser.java b/src/main/java/edu/hm/hafner/analysis/parser/LintParser.java
index 044440574..43a5ba352 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/LintParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/LintParser.java
@@ -25,16 +25,16 @@ public class LintParser extends IssueParser {
     private static final String FILE = "file";
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException {
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException {
         var report = new Report();
-        readerFactory.parse(new JSLintXmlSaxParser(report));
+        readerFactory.parse(new JsLintXmlSaxParser(report));
         return report;
     }
 
     /**
      * Handles parsing.
      */
-    static class JSLintXmlSaxParser extends DefaultHandler {
+    static class JsLintXmlSaxParser extends DefaultHandler {
         private static final String ISSUE = "issue";
         private static final String ERROR = "error";
         private final Report report;
@@ -46,7 +46,7 @@ static class JSLintXmlSaxParser extends DefaultHandler {
         static final String CATEGORY_FORMATTING = "Formatting";
         private final IssueBuilder issueBuilder;
 
-        JSLintXmlSaxParser(final Report report) {
+        JsLintXmlSaxParser(final Report report) {
             super();
 
             this.report = report;
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/OELintAdvParser.java b/src/main/java/edu/hm/hafner/analysis/parser/OeLintAdvParser.java
similarity index 88%
rename from src/main/java/edu/hm/hafner/analysis/parser/OELintAdvParser.java
rename to src/main/java/edu/hm/hafner/analysis/parser/OeLintAdvParser.java
index d53bc33f3..2aad6d30a 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/OELintAdvParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/OeLintAdvParser.java
@@ -14,14 +14,14 @@
 /**
  * A parser for oelint-adv.
  */
-public class OELintAdvParser extends LookaheadParser {
+public class OeLintAdvParser extends LookaheadParser {
     @Serial
     private static final long serialVersionUID = 1L;
 
     /**
-     * Creates a new instance of {@link OELintAdvParser}.
+     * Creates a new instance of {@link OeLintAdvParser}.
      */
-    public OELintAdvParser() {
+    public OeLintAdvParser() {
         super("^(?.+?):(?[0-9]+?):(?.+?):(?.+?):(?.+?)$");
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/OTDockerLintParser.java b/src/main/java/edu/hm/hafner/analysis/parser/OtDockerLintParser.java
similarity index 88%
rename from src/main/java/edu/hm/hafner/analysis/parser/OTDockerLintParser.java
rename to src/main/java/edu/hm/hafner/analysis/parser/OtDockerLintParser.java
index c204467ef..c3de9b15f 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/OTDockerLintParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/OtDockerLintParser.java
@@ -6,7 +6,6 @@
 import org.json.JSONObject;
 
 import edu.hm.hafner.analysis.IssueBuilder;
-import edu.hm.hafner.analysis.ReaderFactory;
 import edu.hm.hafner.analysis.Report;
 import edu.hm.hafner.analysis.Severity;
 
@@ -16,15 +15,10 @@
  * @author Abhishek Dubey
  * @see ot-docker-linter
  */
-public class OTDockerLintParser extends JsonIssueParser {
+public class OtDockerLintParser extends JsonIssueParser {
     @Serial
     private static final long serialVersionUID = 42L;
 
-    @Override
-    public boolean accepts(final ReaderFactory readerFactory) {
-        return readerFactory.getFileName().endsWith(".json");
-    }
-
     @Override
     protected void parseJsonArray(final Report report, final JSONArray jsonReport, final IssueBuilder issueBuilder) {
         for (Object entry : jsonReport) {
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/pmd/PmdMessages.java b/src/main/java/edu/hm/hafner/analysis/parser/PmdMessages.java
similarity index 98%
rename from src/main/java/edu/hm/hafner/analysis/parser/pmd/PmdMessages.java
rename to src/main/java/edu/hm/hafner/analysis/parser/PmdMessages.java
index d6a1a3afa..a2b518724 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/pmd/PmdMessages.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/PmdMessages.java
@@ -1,4 +1,4 @@
-package edu.hm.hafner.analysis.parser.pmd;
+package edu.hm.hafner.analysis.parser;
 
 import java.util.HashMap;
 import java.util.List;
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/PmdParser.java b/src/main/java/edu/hm/hafner/analysis/parser/PmdParser.java
new file mode 100644
index 000000000..be4c58ec1
--- /dev/null
+++ b/src/main/java/edu/hm/hafner/analysis/parser/PmdParser.java
@@ -0,0 +1,417 @@
+package edu.hm.hafner.analysis.parser;
+
+import java.io.IOException;
+import java.io.Serial;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.lang3.StringUtils;
+import org.xml.sax.SAXException;
+
+import edu.hm.hafner.analysis.IssueBuilder;
+import edu.hm.hafner.analysis.IssueParser;
+import edu.hm.hafner.analysis.ParsingException;
+import edu.hm.hafner.analysis.ReaderFactory;
+import edu.hm.hafner.analysis.Report;
+import edu.hm.hafner.analysis.SecureDigester;
+import edu.hm.hafner.analysis.Severity;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * A parser for PMD XML files.
+ *
+ * @author Ullrich Hafner
+ */
+public class PmdParser extends IssueParser {
+    @Serial
+    private static final long serialVersionUID = 6507147028628714706L;
+
+    /** PMD priorities smaller than this value are mapped to {@link Severity#WARNING_HIGH}. */
+    private static final int PMD_PRIORITY_MAPPED_TO_HIGH_PRIORITY = 3;
+    /** PMD priorities greater than this value are mapped to {@link Severity#WARNING_LOW}. */
+    private static final int PMD_PRIORITY_MAPPED_TO_LOW_PRIORITY = 4;
+
+    @Override
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException {
+        var issues = parseIssues(readerFactory);
+        parseErrors(readerFactory).stream().forEach(issues::add);
+        return issues;
+    }
+
+    private Report parseIssues(final ReaderFactory readerFactory) {
+        var digester = new SecureDigester(PmdParser.class);
+
+        var rootXPath = "pmd";
+        digester.addObjectCreate(rootXPath, Pmd.class);
+        digester.addSetProperties(rootXPath);
+
+        var fileXPath = "pmd/file";
+        digester.addObjectCreate(fileXPath, File.class);
+        digester.addSetProperties(fileXPath);
+        digester.addSetNext(fileXPath, "addFile", File.class.getName());
+
+        var bugXPath = "pmd/file/violation";
+        digester.addObjectCreate(bugXPath, Violation.class);
+        digester.addSetProperties(bugXPath);
+        digester.addCallMethod(bugXPath, "setMessage", 0);
+        digester.addSetNext(bugXPath, "addViolation", Violation.class.getName());
+
+        try (var reader = readerFactory.create()) {
+            Pmd pmd = digester.parse(reader);
+            if (pmd == null) {
+                throw new ParsingException("Input stream is not a PMD file.");
+            }
+
+            return convertIssues(pmd);
+        }
+        catch (IOException | SAXException exception) {
+            throw new ParsingException(exception);
+        }
+    }
+
+    private Report parseErrors(final ReaderFactory readerFactory) {
+        var digester = new SecureDigester(PmdParser.class);
+
+        var rootXPath = "pmd";
+        digester.addObjectCreate(rootXPath, Pmd.class);
+        digester.addSetProperties(rootXPath);
+
+        var errorXPath = "pmd/error";
+        digester.addObjectCreate(errorXPath, PmdError.class);
+        digester.addSetProperties(errorXPath);
+        digester.addSetNext(errorXPath, "addError", PmdError.class.getName());
+        digester.addCallMethod(errorXPath, "setDescription", 0);
+
+        try (var reader = readerFactory.create()) {
+            Pmd pmd = digester.parse(reader);
+            if (pmd == null) {
+                throw new ParsingException("Input stream is not a PMD file.");
+            }
+
+            return convertErrors(pmd);
+        }
+        catch (IOException | SAXException exception) {
+            throw new ParsingException(exception);
+        }
+    }
+
+    private Report convertIssues(final Pmd pmdIssues) {
+        try (var issueBuilder = new IssueBuilder()) {
+            var report = new Report();
+            for (File file : pmdIssues.getFiles()) {
+                for (Violation warning : file.getViolations()) {
+                    issueBuilder.setSeverity(mapPriority(warning))
+                            .setMessage(createMessage(warning))
+                            .setCategory(warning.getRuleset())
+                            .setType(warning.getRule())
+                            .setLineStart(warning.getBeginline())
+                            .setLineEnd(warning.getEndline())
+                            .setPackageName(warning.getPackage())
+                            .setFileName(file.getName())
+                            .setColumnStart(warning.getBegincolumn())
+                            .setColumnEnd(warning.getEndcolumn());
+                    report.add(issueBuilder.buildAndClean());
+                }
+            }
+            return report;
+        }
+    }
+
+    private Report convertErrors(final Pmd pmdIssues) {
+        try (var issueBuilder = new IssueBuilder()) {
+            var report = new Report();
+            for (PmdError error : pmdIssues.getErrors()) {
+                issueBuilder.setSeverity(Severity.ERROR)
+                        .setMessage(error.getMsg())
+                        .setDescription(error.getDescription())
+                        .setFileName(error.getFilename());
+                report.add(issueBuilder.buildAndClean());
+            }
+            return report;
+        }
+    }
+
+    private Severity mapPriority(final Violation warning) {
+        if (warning.getPriority() < PMD_PRIORITY_MAPPED_TO_HIGH_PRIORITY) {
+            return Severity.WARNING_HIGH;
+        }
+        else if (warning.getPriority() > PMD_PRIORITY_MAPPED_TO_LOW_PRIORITY) {
+            return Severity.WARNING_LOW;
+        }
+        return Severity.WARNING_NORMAL;
+    }
+
+    private String createMessage(final Violation warning) {
+        var original = warning.getMessage();
+        if (original == null) {
+            return StringUtils.EMPTY;
+        }
+        if (StringUtils.endsWith(original, ".")) {
+            return original;
+        }
+        else {
+            return original + ".";
+        }
+    }
+
+    /**
+     * Java Bean class for a violation of the PMD format.
+     *
+     * @author Ullrich Hafner
+     */
+    @SuppressWarnings("all")
+    public static class Violation {
+        /** IssueType of warning. */
+        @CheckForNull
+        private String rule;
+        /** Category of warning. */
+        @CheckForNull
+        private String ruleset;
+
+        @CheckForNull
+        private String externalInfoUrl;
+        @CheckForNull
+        private String javaPackage;
+        private int priority;
+        @CheckForNull
+        private String message;
+        private int beginline;
+        private int endline;
+        private int begincolumn;
+        private int endcolumn;
+
+        // CHECKSTYLE:OFF
+        @CheckForNull
+        public String getRule() {
+            return rule;
+        }
+
+        public void setRule(final String rule) {
+            this.rule = rule;
+        }
+
+        @CheckForNull
+        public String getRuleset() {
+            return ruleset;
+        }
+
+        public void setRuleset(final String ruleset) {
+            this.ruleset = ruleset;
+        }
+
+        @CheckForNull
+        public String getExternalInfoUrl() {
+            return externalInfoUrl;
+        }
+
+        public void setExternalInfoUrl(final String externalInfoUrl) {
+            this.externalInfoUrl = externalInfoUrl;
+        }
+
+        @CheckForNull
+        public String getPackage() {
+            return javaPackage;
+        }
+
+        public void setPackage(final String packageName) {
+            javaPackage = packageName;
+        }
+
+        public int getPriority() {
+            return priority;
+        }
+
+        public void setPriority(final int priority) {
+            this.priority = priority;
+        }
+
+        @CheckForNull
+        public String getMessage() {
+            return message;
+        }
+
+        public void setMessage(final String message) {
+            this.message = message;
+        }
+
+        public int getBeginline() {
+            return beginline;
+        }
+
+        public void setBeginline(final int beginline) {
+            this.beginline = beginline;
+        }
+
+        public int getEndline() {
+            return endline;
+        }
+
+        public void setEndline(final int endline) {
+            this.endline = endline;
+        }
+
+        public int getEndcolumn() {
+            return endcolumn;
+        }
+
+        public void setEndcolumn(final int endcolumn) {
+            this.endcolumn = endcolumn;
+        }
+
+        public int getBegincolumn() {
+            return begincolumn;
+        }
+
+        public void setBegincolumn(final int begincolumn) {
+            this.begincolumn = begincolumn;
+        }
+    }
+
+    /**
+     * Java Bean class for warnings of the PMD format.
+     *
+     * @author Ullrich Hafner
+     */
+    public static class Pmd {
+        private final List files = new ArrayList<>();
+        private final List errors = new ArrayList<>();
+
+        /**
+         * Adds a new file.
+         *
+         * @param file
+         *         the file to add
+         */
+        public void addFile(final File file) {
+            files.add(file);
+        }
+
+        /**
+         * Adds a new error.
+         *
+         * @param error
+         *         the error to add
+         */
+        public void addError(final PmdError error) {
+            errors.add(error);
+        }
+
+        /**
+         * Returns all files. The returned collection is read-only.
+         *
+         * @return all files
+         */
+        public Collection getFiles() {
+            return Collections.unmodifiableCollection(files);
+        }
+
+        /**
+         * Returns all errors. The returned collection is read-only.
+         *
+         * @return all errors
+         */
+        public Collection getErrors() {
+            return Collections.unmodifiableCollection(errors);
+        }
+    }
+
+    /**
+     * Java Bean class for an error of the PMD format.
+     *
+     * @author Ullrich Hafner
+     */
+    @SuppressWarnings({"InstanceVariableMayNotBeInitialized", "PMD.DataClass"})
+    public static class PmdError {
+        @CheckForNull
+        private String filename;
+        @CheckForNull
+        private String msg;
+        @CheckForNull
+        private String description;
+
+        @CheckForNull
+        @SuppressFBWarnings("NM")
+        public String getFilename() {
+            return filename;
+        }
+
+        @SuppressFBWarnings("NM")
+        public void setFilename(@CheckForNull final String filename) {
+            this.filename = filename;
+        }
+
+        @CheckForNull
+        public String getMsg() {
+            return msg;
+        }
+
+        public void setMsg(@CheckForNull final String msg) {
+            this.msg = msg;
+        }
+
+        @CheckForNull
+        public String getDescription() {
+            return description;
+        }
+
+        public void setDescription(@CheckForNull final String description) {
+            this.description = description;
+        }
+    }
+
+    /**
+     * Java Bean class for a file of the PMD format.
+     *
+     * @author Ullrich Hafner
+     */
+    @SuppressWarnings("InstanceVariableMayNotBeInitialized")
+    public static class File {
+        /** Name of the file. */
+        @CheckForNull
+        private String name;
+        /** All violations of this file. */
+        private final List violations = new ArrayList<>();
+
+        /**
+         * Adds a new violation to this file.
+         *
+         * @param violation
+         *            the new violation
+         */
+        public void addViolation(final Violation violation) {
+            violations.add(violation);
+        }
+
+        /**
+         * Returns all violations of this file. The returned collection is
+         * read-only.
+         *
+         * @return all violations in this file
+         */
+        public Collection getViolations() {
+            return Collections.unmodifiableCollection(violations);
+        }
+
+        /**
+         * Returns the name of this file.
+         *
+         * @return the name of this file
+         */
+        @CheckForNull
+        public String getName() {
+            return name;
+        }
+
+        /**
+         * Sets the name of this file to the specified value.
+         *
+         * @param name the value to set
+         */
+        public void setName(@CheckForNull final String name) {
+            this.name = name;
+        }
+    }
+}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/PolyspaceParser.java b/src/main/java/edu/hm/hafner/analysis/parser/PolyspaceParser.java
index 1f356b9e7..3078d191d 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/PolyspaceParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/PolyspaceParser.java
@@ -33,7 +33,7 @@ public class PolyspaceParser extends IssueParser {
      */
 
     @Override
-    public Report parse(final ReaderFactory reader) throws ParsingException {
+    public Report parseReport(final ReaderFactory reader) throws ParsingException {
         try (Stream lines = reader.readStream()) {
             return parse(lines);
         }
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/PvsStudioParser.java b/src/main/java/edu/hm/hafner/analysis/parser/PvsStudioParser.java
new file mode 100644
index 000000000..49f228465
--- /dev/null
+++ b/src/main/java/edu/hm/hafner/analysis/parser/PvsStudioParser.java
@@ -0,0 +1,456 @@
+package edu.hm.hafner.analysis.parser;
+
+import java.io.Serial;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import java.util.Optional;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import java.util.stream.Stream;
+
+import org.apache.commons.lang3.StringUtils;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import edu.hm.hafner.analysis.IssueBuilder;
+import edu.hm.hafner.analysis.IssueParser;
+import edu.hm.hafner.analysis.ParsingCanceledException;
+import edu.hm.hafner.analysis.ParsingException;
+import edu.hm.hafner.analysis.ReaderFactory;
+import edu.hm.hafner.analysis.Report;
+import edu.hm.hafner.analysis.Severity;
+import edu.hm.hafner.analysis.parser.PvsStudioParser.PlogMessagesReader.PlogMessage;
+import edu.hm.hafner.analysis.util.IntegerParser;
+
+/**
+ * A parser for the PVS-Studio static analyzer.
+ *
+ * @author PVS-Studio Team
+ */
+public class PvsStudioParser extends IssueParser {
+    @Serial
+    private static final long serialVersionUID = -7777775729854832128L;
+    private static final String SEVERITY_HIGH = "1";
+    private static final String SEVERITY_NORMAL = "2";
+    private static final String SEVERITY_LOW = "3";
+
+    private static Severity getSeverity(final String level) {
+        if (SEVERITY_HIGH.equals(level)) {
+            return Severity.WARNING_HIGH;
+        }
+        else if (SEVERITY_NORMAL.equals(level)) {
+            return Severity.WARNING_NORMAL;
+        }
+        else if (SEVERITY_LOW.equals(level)) {
+            return Severity.WARNING_LOW;
+        }
+        else {
+            return Severity.ERROR;
+        }
+    }
+
+    @Override
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
+        try (var issueBuilder = new IssueBuilder()) {
+            var report = new Report();
+            var parser = new PlogMessagesReader();
+
+            for (PlogMessage plogMessage : parser.getMessagesFromReport(readerFactory)) {
+                issueBuilder.setFileName(plogMessage.getFilePath());
+
+                issueBuilder.setSeverity(getSeverity(plogMessage.getLevel()));
+                issueBuilder.setMessage(plogMessage.toString());
+                issueBuilder.setCategory(plogMessage.getType());
+
+                issueBuilder.setType(AnalyzerType.fromErrorCode(plogMessage.getType()).getMessage());
+
+                issueBuilder.setLineStart(plogMessage.getLine());
+
+                report.add(issueBuilder.buildAndClean());
+            }
+
+            return report;
+        }
+    }
+
+    /**
+     * A parser for PVS-Studio Plog/XML files.
+     *
+     * @author PVS-Studio Team
+     */
+    static class PlogMessagesReader {
+        private int failWarningsCount;
+        private int falseAlarmCount;
+
+        /**
+         * Getting list messages from the report.
+         *
+         * @param readerFactory
+         *         factory containing report file reader
+         *
+         * @return list plog messages
+         */
+        List getMessagesFromReport(final ReaderFactory readerFactory) {
+            var plogDoc = readerFactory.readDocument();
+            plogDoc.getDocumentElement().normalize();
+
+            var nList = plogDoc.getElementsByTagName("PVS-Studio_Analysis_Log");
+
+            List plogMessages = new ArrayList<>();
+            for (int nodeCount = 0; nodeCount < nList.getLength(); nodeCount++) {
+                var nNode = nList.item(nodeCount);
+
+                if (nNode.getNodeType() == Node.ELEMENT_NODE) {
+                    processNode(plogMessages, nNode);
+                }
+            }
+
+            if ((plogMessages.size() + falseAlarmCount) == 0 && failWarningsCount > 0) {
+                Logger.getLogger(PvsStudioParser.class.getName()).log(Level.SEVERE, "No messages were parsed!");
+            }
+
+            return plogMessages;
+        }
+
+        private void processNode(final List plogMessages, final Node node) {
+            var eElement = (Element) node;
+
+            var nodeFalseAlarm = eElement.getElementsByTagName("FalseAlarm");
+            if (skipMessage(nodeFalseAlarm)) {
+                ++falseAlarmCount;
+                return;
+            }
+
+            var nodeFile = eElement.getElementsByTagName("File");
+
+            var msg = new PlogMessage();
+
+            if (nodeNotNull(nodeFile)) {
+                msg.file = nodeFile.item(0).getTextContent().trim();
+            }
+
+            if (msg.file.isEmpty()) {
+                ++failWarningsCount;
+                return;
+            }
+
+            var nodeErrorCode = eElement.getElementsByTagName("ErrorCode");
+
+            if (nodeNotNull(nodeErrorCode)) {
+                msg.errorCode = nodeErrorCode.item(0).getTextContent().trim();
+            }
+
+            if (!errorCodeIsValid(msg.errorCode)) {
+                ++failWarningsCount;
+                return;
+            }
+
+            msg.message = ""
+                    + msg.errorCode + " "
+                    + eElement.getElementsByTagName("Message").item(0).getTextContent();
+
+            msg.level = eElement.getElementsByTagName("Level").item(0).getTextContent();
+
+            msg.lineNumber = IntegerParser.parseInt(eElement.getElementsByTagName("Line").item(0).getTextContent());
+            if (msg.lineNumber <= 0) {
+                ++failWarningsCount;
+                return;
+            }
+
+            plogMessages.add(msg);
+        }
+
+        private boolean skipMessage(final NodeList elements) {
+            return elements != null && elements.item(0) != null
+                    && equalsIgnoreCase(elements.item(0).getTextContent(), "true");
+        }
+
+        private boolean nodeNotNull(final NodeList elements) {
+            return elements != null && elements.item(0) != null && elements.item(0).getTextContent() != null;
+        }
+
+        private boolean errorCodeIsValid(final String errorCode) {
+            return !(errorCode.isEmpty() || errorCode.charAt(0) != 'V');
+        }
+
+        static class PlogMessage {
+            private String file = StringUtils.EMPTY;
+            private int lineNumber;
+            private String errorCode = StringUtils.EMPTY;
+            private String message = StringUtils.EMPTY;
+            private String level = StringUtils.EMPTY;
+
+            public String getHash() {
+                return errorCode + message + file + lineNumber;
+            }
+
+            @Override
+            public String toString() {
+                return message;
+            }
+
+            String getFilePath() {
+                return file;
+            }
+
+            public int getLine() {
+                return lineNumber;
+            }
+
+            public String getType() {
+                return errorCode;
+            }
+
+            public String getLevel() {
+                return level;
+            }
+        }
+    }
+
+    /**
+     * The AnalyzerType for PVS-Studio static analyzer.
+     *
+     * @author PVS-Studio Team
+     */
+    static final class AnalyzerType {
+        /**
+         * Diagnosis of 64-bit errors (Viva64, C++).
+         * ...
+         */
+        private static final int VIVA64_CCPP_ERRORCODE_BEGIN = 100;
+        /**
+         * Diagnosis of 64-bit errors (Viva64, C++).
+         * ...
+         */
+        private static final int VIVA64_CCPP_ERRORCODE_END = 499;
+        /**
+         * General Analysis (C++).
+         * ...
+         */
+        private static final int GENERAL_CCPP_LOW_ERRORCODE_BEGIN = 500;
+        /**
+         * General Analysis (C++).
+         * ...
+         */
+        private static final int GENERAL_CCPP_LOW_ERRORCODE_END = 799;
+        /**
+         * Diagnosis of micro-optimizations (C++).
+         * ...
+         */
+        private static final int OPTIMIZATION_CCPP_ERRORCODE_BEGIN = 800;
+        /**
+         * Diagnosis of micro-optimizations (C++).
+         * ...
+         */
+        private static final int OPTIMIZATION_CCPP_ERRORCODE_END = 999;
+        /**
+         * General Analysis (C++).
+         * ...
+         */
+        private static final int GENERAL_CCPP_HIGH_ERRORCODE_BEGIN = 1000;
+        /**
+         * General Analysis (C++).
+         * ...
+         */
+        private static final int GENERAL_CCPP_HIGH_ERRORCODE_END = 1999;
+        /**
+         * Customers Specific Requests (C++).
+         * ...
+         */
+        private static final int CUSTOMERSPECIFIC_CCPP_ERRORCODE_BEGIN = 2000;
+        /**
+         * Customers Specific Requests (C++).
+         * ...
+         */
+        private static final int CUSTOMERSPECIFIC_CCPP_ERRORCODE_END = 2499;
+        /**
+         * MISRA errors.
+         * ...
+         */
+        private static final int MISRA_CCPP_ERRORCODE_BEGIN = 2500;
+        /**
+         * MISRA errors.
+         * ...
+         */
+        private static final int MISRA_CCPP_ERRORCODE_END = 2999;
+        /**
+         * General Analysis (C#).
+         * ...
+         */
+        private static final int GENERAL_CS_ERRORCODE_BEGIN = 3000;
+        /**
+         * General Analysis (C#).
+         * ...
+         */
+        private static final int GENERAL_CS_ERRORCODE_END = 3499;
+        /**
+         * General Analysis (Java).
+         * ...
+         */
+        private static final int GENERAL_JAVA_ERRORCODE_BEGIN = 6000;
+        /**
+         * General Analysis (Java).
+         * ...
+         */
+        private static final int GENERAL_JAVA_ERRORCODE_END = 6999;
+
+        static final String VIVA_64_MESSAGE = "64-bit";
+        static final String GENERAL_MESSAGE = "General Analysis";
+        static final String OPTIMIZATION_MESSAGE = "Micro-optimization";
+        static final String CUSTOMER_SPECIFIC_MESSAGE = "Specific Requests";
+        static final String MISRA_MESSAGE = "MISRA";
+        static final String UNKNOWN_MESSAGE = "Unknown";
+
+        private AnalyzerType() {
+            // prevents instantiation
+        }
+
+        private static final AnalysisType[] ANALYSIS_TYPES = {new Viva64(), new General(),
+                new Optimization(), new CustomerSpecific(), new Misra()};
+
+        static AnalysisType fromErrorCode(final String errorCodeStr) {
+            if (equalsIgnoreCase(errorCodeStr, "External")) {
+                return new General();
+            }
+
+            // errorCodeStr format is Vnnn.
+            int errorCode = IntegerParser.parseInt(errorCodeStr.substring(1));
+
+            return Arrays.stream(ANALYSIS_TYPES)
+                    .map(type -> type.create(errorCode))
+                    .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
+                    .findFirst()
+                    .orElse(new Unknown());
+        }
+
+        /**
+         * Viva64 AnalysisType.
+         */
+        static final class Viva64 implements AnalysisType {
+            @Override
+            public String getMessage() {
+                return VIVA_64_MESSAGE;
+            }
+
+            @Override
+            public Optional create(final int errorCode) {
+                if (errorCode >= VIVA64_CCPP_ERRORCODE_BEGIN && errorCode <= VIVA64_CCPP_ERRORCODE_END) {
+                    return Optional.of(new Viva64());
+                }
+
+                return Optional.empty();
+            }
+        }
+
+        /**
+         * GENERAL AnalysisType.
+         */
+        private static final class General implements AnalysisType {
+            @Override
+            public String getMessage() {
+                return GENERAL_MESSAGE;
+            }
+
+            @Override
+            public Optional create(final int errorCode) {
+                if (errorCode >= GENERAL_CCPP_LOW_ERRORCODE_BEGIN && errorCode <= GENERAL_CCPP_LOW_ERRORCODE_END) {
+                    return Optional.of(new General());
+                }
+                else if (errorCode >= GENERAL_CCPP_HIGH_ERRORCODE_BEGIN && errorCode <= GENERAL_CCPP_HIGH_ERRORCODE_END) {
+                    return Optional.of(new General());
+                }
+                else if (errorCode >= GENERAL_CS_ERRORCODE_BEGIN && errorCode <= GENERAL_CS_ERRORCODE_END) {
+                    return Optional.of(new General());
+                }
+                else if (errorCode >= GENERAL_JAVA_ERRORCODE_BEGIN && errorCode <= GENERAL_JAVA_ERRORCODE_END) {
+                    return Optional.of(new General());
+                }
+                else {
+                    return Optional.empty();
+                }
+            }
+        }
+
+        /**
+         * OPTIMIZATION AnalysisType.
+         */
+        private static final class Optimization implements AnalysisType {
+            @Override
+            public String getMessage() {
+                return OPTIMIZATION_MESSAGE;
+            }
+
+            @Override
+            public Optional create(final int errorCode) {
+                if (errorCode >= OPTIMIZATION_CCPP_ERRORCODE_BEGIN && errorCode <= OPTIMIZATION_CCPP_ERRORCODE_END) {
+                    return Optional.of(new Optimization());
+                }
+
+                return Optional.empty();
+            }
+        }
+
+        /**
+         * CustomerSpecific AnalysisType.
+         */
+        private static final class CustomerSpecific implements AnalysisType {
+            @Override
+            public String getMessage() {
+                return CUSTOMER_SPECIFIC_MESSAGE;
+            }
+
+            @Override
+            public Optional create(final int errorCode) {
+                if (errorCode >= CUSTOMERSPECIFIC_CCPP_ERRORCODE_BEGIN && errorCode <= CUSTOMERSPECIFIC_CCPP_ERRORCODE_END) {
+                    return Optional.of(new CustomerSpecific());
+                }
+
+                return Optional.empty();
+            }
+        }
+
+        /**
+         * MISRA AnalysisType.
+         */
+        private static final class Misra implements AnalysisType {
+            @Override
+            public String getMessage() {
+                return MISRA_MESSAGE;
+            }
+
+            @Override
+            public Optional create(final int errorCode) {
+                if (errorCode >= MISRA_CCPP_ERRORCODE_BEGIN && errorCode <= MISRA_CCPP_ERRORCODE_END) {
+                    return Optional.of(new Misra());
+                }
+
+                return Optional.empty();
+            }
+        }
+
+        /**
+         * Unknown AnalysisType.
+         */
+        private static final class Unknown implements AnalysisType {
+            @Override
+            public String getMessage() {
+                return UNKNOWN_MESSAGE;
+            }
+
+            @Override
+            public Optional create(final int errorCode) {
+                return Optional.empty();
+            }
+        }
+
+        interface AnalysisType {
+            String getMessage();
+
+            Optional create(int errorCodeStr);
+        }
+    }
+}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/pylint/PyLintDescriptions.java b/src/main/java/edu/hm/hafner/analysis/parser/PyLintDescriptions.java
similarity index 97%
rename from src/main/java/edu/hm/hafner/analysis/parser/pylint/PyLintDescriptions.java
rename to src/main/java/edu/hm/hafner/analysis/parser/PyLintDescriptions.java
index 94112abeb..93a21022b 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/pylint/PyLintDescriptions.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/PyLintDescriptions.java
@@ -1,4 +1,4 @@
-package edu.hm.hafner.analysis.parser.pylint;
+package edu.hm.hafner.analysis.parser;
 
 import java.io.IOException;
 import java.util.HashMap;
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/QtTranslationParser.java b/src/main/java/edu/hm/hafner/analysis/parser/QtTranslationParser.java
index b3e4da8e4..1ffba75e2 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/QtTranslationParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/QtTranslationParser.java
@@ -45,7 +45,7 @@ public class QtTranslationParser extends IssueParser {
                     + "so that \"lupdate\" can find it.";
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException {
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException {
         var report = new Report();
         readerFactory.parse(new QtTranslationSaxParser(report, readerFactory.getFileName()));
         return report;
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/RfLintParser.java b/src/main/java/edu/hm/hafner/analysis/parser/RfLintParser.java
index 3bcaf786f..9f1571b7c 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/RfLintParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/RfLintParser.java
@@ -159,7 +159,7 @@ public static RfLintRuleName fromName(final String name) {
     private static final Pattern FILE_PATTERN = Pattern.compile("\\+\\s(?.*)");
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) {
+    public Report parseReport(final ReaderFactory readerFactory) {
         try (Stream lines = readerFactory.readStream(); var builder = new IssueBuilder()) {
             var warnings = new Report();
             lines.forEach(line -> parseLine(builder, warnings, line));
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/SimianParser.java b/src/main/java/edu/hm/hafner/analysis/parser/SimianParser.java
new file mode 100644
index 000000000..e6255cd61
--- /dev/null
+++ b/src/main/java/edu/hm/hafner/analysis/parser/SimianParser.java
@@ -0,0 +1,201 @@
+package edu.hm.hafner.analysis.parser;
+
+import java.io.Serial;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.digester3.Digester;
+
+import edu.hm.hafner.analysis.DuplicationGroup;
+import edu.hm.hafner.analysis.IssueBuilder;
+import edu.hm.hafner.analysis.Report;
+import edu.umd.cs.findbugs.annotations.CheckForNull;
+
+/**
+ * A parser for Simian XML files.
+ *
+ * @author Ullrich Hafner
+ */
+public class SimianParser extends AbstractDryParser {
+    /** Unique ID of this class. */
+    @Serial
+    private static final long serialVersionUID = 6507147028628714706L;
+
+    /**
+     * Creates a new instance of {@link SimianParser}.
+     *
+     * @param highThreshold
+     *         minimum number of duplicate lines for high priority warnings
+     * @param normalThreshold
+     *         minimum number of duplicate lines for normal priority warnings
+     */
+    public SimianParser(final int highThreshold, final int normalThreshold) {
+        super(highThreshold, normalThreshold);
+    }
+
+    /**
+     * Creates a new instance of {@link SimianParser}. The {@code highThreshold} is set to 50, the {@code normalThreshold}
+     * is set to 25.
+     */
+    public SimianParser() {
+        super(50, 25);
+    }
+
+    @Override
+    protected void configureParser(final Digester digester) {
+        var duplicationXPath = "*/simian/check/set";
+        digester.addObjectCreate(duplicationXPath, Set.class);
+        digester.addSetProperties(duplicationXPath);
+        digester.addSetNext(duplicationXPath, "add");
+
+        var fileXPath = duplicationXPath + "/block";
+        digester.addObjectCreate(fileXPath, Block.class);
+        digester.addSetProperties(fileXPath);
+        digester.addSetNext(fileXPath, "addBlock", Block.class.getName());
+    }
+
+    @Override
+    protected Report convertDuplicationsToIssues(final List duplications, final IssueBuilder issueBuilder) {
+        var report = new Report();
+
+        for (Set duplication : duplications) {
+            var group = new DuplicationGroup();
+            for (Block file : duplication.getBlocks()) {
+                issueBuilder.setSeverity(getPriority(duplication.getLineCount()))
+                        .setLineStart(file.getStartLineNumber())
+                        .setLineEnd(file.getEndLineNumber())
+                        .setFileName(file.getSourceFile())
+                        .setAdditionalProperties(group)
+                        .setType("Simian");
+                var issue = issueBuilder.build();
+                group.add(issue);
+                report.add(issue);
+            }
+        }
+        return report;
+    }
+
+    /**
+     * Java Bean class for a Simian duplication set.
+     *
+     * @author Ullrich Hafner
+     */
+    @SuppressWarnings("PMD.DataClass")
+    public static class Set {
+        private int lineCount;
+        private final List blocks = new ArrayList<>();
+
+        /**
+         * Adds a new block to this duplication set.
+         *
+         * @param block
+         *            the new block
+         */
+        public void addBlock(final Block block) {
+            blocks.add(block);
+        }
+
+        /**
+         * Returns all blocks of this duplication set. The returned collection is
+         * read-only.
+         *
+         * @return all files
+         */
+        public Collection getBlocks() {
+            return Collections.unmodifiableCollection(blocks);
+        }
+
+        /**
+         * Returns the number of duplicated lines.
+         *
+         * @return the lineCount
+         */
+        public int getLineCount() {
+            return lineCount;
+        }
+
+        /**
+         * Sets the number of duplicated lines to the specified value.
+         *
+         * @param value the value to set
+         */
+        public void setLineCount(final int value) {
+            lineCount = value;
+        }
+    }
+
+    /**
+     * Java Bean class for a duplicated block of a Simian duplication warning.
+     *
+     * @author Ullrich Hafner
+     */
+    @SuppressWarnings("PMD.DataClass")
+    public static class Block {
+        @CheckForNull
+        private String sourceFile;
+        private int startLineNumber;
+        private int endLineNumber;
+
+        /**
+         * Returns the file name.
+         *
+         * @return the file name
+         */
+        @CheckForNull
+        public String getSourceFile() {
+            return sourceFile;
+        }
+
+        /**
+         * Sets the file name to the specified value.
+         *
+         * @param sourceFile
+         *            the value to set
+         */
+        public void setSourceFile(@CheckForNull final String sourceFile) {
+            this.sourceFile = sourceFile;
+        }
+
+        /**
+         * Returns the line number of the start of the duplication.
+         *
+         * @return the line number of the start of the duplication.
+         */
+        public int getStartLineNumber() {
+            return startLineNumber;
+        }
+
+        /**
+         * Sets the line number of the start of the duplication to the specified
+         * value.
+         *
+         * @param startLineNumber
+         *            the value to set
+         */
+        public void setStartLineNumber(final int startLineNumber) {
+            this.startLineNumber = startLineNumber;
+        }
+
+        /**
+         * Returns the line number of the end of the duplication.
+         *
+         * @return the line number of the end of the duplication.
+         */
+        public int getEndLineNumber() {
+            return endLineNumber;
+        }
+
+        /**
+         * Sets the line number of the end of the duplication to the specified
+         * value.
+         *
+         * @param endLineNumber
+         *            the value to set
+         */
+        public void setEndLineNumber(final int endLineNumber) {
+            this.endLineNumber = endLineNumber;
+        }
+    }
+}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/SimulinkCheckParser.java b/src/main/java/edu/hm/hafner/analysis/parser/SimulinkCheckParser.java
index 0877730ec..502e8538f 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/SimulinkCheckParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/SimulinkCheckParser.java
@@ -31,11 +31,12 @@ public class SimulinkCheckParser extends IssueParser {
     private static final String INCOMPLETE = "div.IncompleteCheck";
     private static final String EMPTY_BASE_URI = "";
     private static final String REPORT_CONTENT = "div.ReportContent";
+    private static final String MODEL_NAME_SELECTOR = "b:contains(Model Advisor Report - ) > font";
     private static final Pattern TEXT_PATTERN = Pattern.compile("^(SW[0-9]*-[0-9]*)(\\W*)(.*)");
     private static final String SW_PREFIX = "SW";
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException {
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException {
         try (var issueBuilder = new IssueBuilder();
                 var reader = readerFactory.create();
                 var targetStream = ReaderInputStream.builder().setReader(reader).setCharset(readerFactory.getCharset()).get()) {
@@ -48,7 +49,8 @@ public Report parse(final ReaderFactory readerFactory) throws ParsingException {
             }
             var report = new Report();
 
-            var system = systemElement.id();
+            var modelNameElement = systemElement.selectFirst(MODEL_NAME_SELECTOR);
+            var system = (modelNameElement == null) ? systemElement.id() : modelNameElement.text();
             parseIssues(report, document, issueBuilder, system, WARNING);
             parseIssues(report, document, issueBuilder, system, FAILED);
             parseIssues(report, document, issueBuilder, system, NOT_RUN);
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/StyleCopParser.java b/src/main/java/edu/hm/hafner/analysis/parser/StyleCopParser.java
index 9e12bef1e..b6f2fa16a 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/StyleCopParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/StyleCopParser.java
@@ -25,7 +25,7 @@ public class StyleCopParser extends IssueParser {
     private static final long serialVersionUID = 7846052338159003458L;
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException {
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException {
         var document = readerFactory.readDocument();
 
         // Pre v4.3 uses SourceAnalysisViolations as the parent node name
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/TaglistParser.java b/src/main/java/edu/hm/hafner/analysis/parser/TaglistParser.java
index 1138879ef..1ab949963 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/TaglistParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/TaglistParser.java
@@ -28,7 +28,7 @@ public class TaglistParser extends IssueParser {
     private static final long serialVersionUID = 1L;
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException {
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingException {
         try (var issueBuilder = new IssueBuilder()) {
             var xPathFactory = XPathFactory.newInstance();
             var xPath = xPathFactory.newXPath();
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/TrivyParser.java b/src/main/java/edu/hm/hafner/analysis/parser/TrivyParser.java
index 5c5e3cd25..eee209dc2 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/TrivyParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/TrivyParser.java
@@ -23,12 +23,13 @@
  * @author Thomas Fürer - tfuerer.javanet@gmail.com
  */
 public class TrivyParser extends JsonIssueParser {
+    @Serial
+    private static final long serialVersionUID = 1L;
+
     private static final String VALUE_NOT_SET = "-";
     private static final String TRIVY_VULNERABILITY_LEVEL_TAG_HIGH = "high";
     private static final String TRIVY_VULNERABILITY_LEVEL_TAG_MEDIUM = "medium";
     private static final String TRIVY_VULNERABILITY_LEVEL_TAG_LOW = "low";
-    @Serial
-    private static final long serialVersionUID = 1L;
 
     /**
      * Used with schema version 2 starting with trivy 0.20.0.
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/ValeParser.java b/src/main/java/edu/hm/hafner/analysis/parser/ValeParser.java
new file mode 100644
index 000000000..f222c019e
--- /dev/null
+++ b/src/main/java/edu/hm/hafner/analysis/parser/ValeParser.java
@@ -0,0 +1,57 @@
+package edu.hm.hafner.analysis.parser;
+
+import java.io.Serial;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import edu.hm.hafner.analysis.Issue;
+import edu.hm.hafner.analysis.IssueBuilder;
+import edu.hm.hafner.analysis.Report;
+import edu.hm.hafner.analysis.Severity;
+
+/**
+ * Parser for vale reports.
+ */
+public class ValeParser extends JsonIssueParser {
+    // Constants for JSON keys
+    private static final String CHECK = "Check";
+    private static final String LINE_KEY = "Line";
+    private static final String LINK_KEY = "Link";
+    private static final String MESSAGE_KEY = "Message";
+    private static final String SPAN_KEY = "Span";
+    private static final String SEVERITY_KEY = "Severity";
+
+    @Serial
+    private static final long serialVersionUID = -4034450901865555017L;
+
+    @Override
+    protected void parseJsonObject(final Report report, final JSONObject jsonReport, final IssueBuilder issueBuilder) {
+        JSONArray fileNames = jsonReport.names();
+        for (Object o : fileNames) {
+            if (o instanceof String f) {
+                JSONArray jsonArray = jsonReport.getJSONArray(f);
+                for (Object data : jsonArray) {
+                    if (data instanceof JSONObject dataObject) {
+                        report.add(createIssue(issueBuilder, f, dataObject));
+                    }
+                }
+            }
+        }
+    }
+
+    private Issue createIssue(final IssueBuilder issueBuilder, final String fileName, final JSONObject data) {
+        JSONArray span = data.getJSONArray(SPAN_KEY);
+        int line = data.getInt(LINE_KEY);
+        return issueBuilder.setFileName(fileName)
+                .setDescription(data.getString(CHECK))
+                .setMessage(data.getString(MESSAGE_KEY))
+                .setSeverity(Severity.guessFromString(data.getString(SEVERITY_KEY)))
+                .setReference(data.getString(LINK_KEY))
+                .setLineStart(line)
+                .setLineEnd(line)
+                .setColumnStart(span.getInt(0))
+                .setColumnEnd(span.getInt(1))
+                .buildAndClean();
+    }
+}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/XlcCompilerParser.java b/src/main/java/edu/hm/hafner/analysis/parser/XlcCompilerParser.java
index 0695f3fd2..e84f91bb2 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/XlcCompilerParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/XlcCompilerParser.java
@@ -39,18 +39,12 @@ public XlcCompilerParser() {
     }
 
     private Severity toPriority(final String severity) {
-        switch (severity.charAt(0)) {
-            case 'U':
-            case 'S':
-            case 'E':
-                return Severity.WARNING_HIGH;
-            case 'W':
-                return Severity.WARNING_NORMAL;
-            case 'I':
-                return Severity.WARNING_LOW;
-            default:
-                return Severity.WARNING_HIGH;
-        }
+        return switch (severity.charAt(0)) {
+            case 'U', 'S', 'E' -> Severity.WARNING_HIGH;
+            case 'W' -> Severity.WARNING_NORMAL;
+            case 'I' -> Severity.WARNING_LOW;
+            default -> Severity.WARNING_HIGH;
+        };
     }
 
     @Override
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/XmlParser.java b/src/main/java/edu/hm/hafner/analysis/parser/XmlParser.java
index 43294d2b5..4c7de65dc 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/XmlParser.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/XmlParser.java
@@ -71,7 +71,7 @@ public boolean accepts(final ReaderFactory readerFactory) {
     }
 
     @Override @SuppressFBWarnings("XPATH_INJECTION")
-    public Report parse(final ReaderFactory readerFactory) {
+    public Report parseReport(final ReaderFactory readerFactory) {
         try (var issueBuilder = new IssueBuilder()) {
             var doc = readerFactory.readDocument();
             var xPathFactory = XPathFactory.newInstance();
@@ -95,7 +95,6 @@ public Report parse(final ReaderFactory readerFactory) {
                         .setDescription(path.evaluate(DESCRIPTION, issue))
                         .setPackageName(path.evaluate(PACKAGE_NAME, issue))
                         .setModuleName(path.evaluate(MODULE_NAME, issue))
-                        .setOrigin(path.evaluate(ORIGIN, issue))
                         .setFingerprint(path.evaluate(FINGERPRINT, issue))
                         .setAdditionalProperties(path.evaluate(ADDITIONAL_PROPERTIES, issue));
 
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/ccm/Ccm.java b/src/main/java/edu/hm/hafner/analysis/parser/ccm/Ccm.java
deleted file mode 100644
index 044b8c372..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/ccm/Ccm.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package edu.hm.hafner.analysis.parser.ccm;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * Entity used by {@link CcmParser} to represent the root node of CCM results file.
- *
- * @author Bruno P. Kinoshita - http://www.kinoshita.eti.br
- * @since 1.0
- */
-@SuppressWarnings("all")
-@SuppressFBWarnings("EI")
-public class Ccm {
-    /**
-     * List of metrics present in the XML file.
-     */
-    private List metrics = new ArrayList<>();
-
-    public List getMetrics() {
-        return metrics;
-    }
-
-    public void setMetrics(List metrics) {
-        this.metrics = metrics;
-    }
-
-    public void addMetric(Metric metric) {
-        this.metrics.add(metric);
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/ccm/Metric.java b/src/main/java/edu/hm/hafner/analysis/parser/ccm/Metric.java
deleted file mode 100644
index e63a52d77..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/ccm/Metric.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package edu.hm.hafner.analysis.parser.ccm;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-
-/**
- * Entity representing the Metric from CCM.exe output.
- *
- * 

- * It has the {@link #complexity}, {@link #unit}, {@link #classification} and {@link #file} fields. - *

- * - * @author Bruno P. Kinoshita - http://www.kinoshita.eti.br - * @since 1.0 - */ -@SuppressWarnings("all") -public class Metric { - /** - * Total CC of the method. - */ - private int complexity; - - /** - * String containing Class_Name::Method_Name - */ - @CheckForNull - private String unit; - - /** - * CCM outputs a String with a classification such as "complex, high risk", "untestable, very high risk", etc. As - * there is no documentation on which values are used to determine a method's CC classification CCM Plugin only - * outputs this value. But does not use the information as a constraint in any place. - */ - @CheckForNull - private String classification; - - /** - * The file name (e.g.:\ascx\request\open\form.ascx.cs). - */ - @CheckForNull - private String file; - - /** - * The start line number of the measurement - */ - private int startLineNumber; - - /** - * The end line number of the measurement - */ - private int endLineNumber; - - public int getComplexity() { - return complexity; - } - - public void setComplexity(int complexity) { - this.complexity = complexity; - } - - @CheckForNull - public String getUnit() { - return unit; - } - - public void setUnit(String unit) { - this.unit = unit; - } - - @CheckForNull - public String getClassification() { - return classification; - } - - public void setClassification(String classification) { - this.classification = classification; - } - - @CheckForNull - public String getFile() { - return file; - } - - public void setFile(String file) { - this.file = file; - } - - public int getStartLineNumber() { - return startLineNumber; - } - - public void setStartLineNumber(int startLineNumber) { - this.startLineNumber = startLineNumber; - } - - public int getEndLineNumber() { - return endLineNumber; - } - - public void setEndLineNumber(int endLineNumber) { - this.endLineNumber = endLineNumber; - } - -} diff --git a/src/main/java/edu/hm/hafner/analysis/parser/ccm/package-info.java b/src/main/java/edu/hm/hafner/analysis/parser/ccm/package-info.java deleted file mode 100644 index 494700173..000000000 --- a/src/main/java/edu/hm/hafner/analysis/parser/ccm/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -/** - * CCM parser. - */ -@DefaultAnnotation(NonNull.class) -package edu.hm.hafner.analysis.parser.ccm; - -import edu.umd.cs.findbugs.annotations.DefaultAnnotation; -import edu.umd.cs.findbugs.annotations.NonNull; diff --git a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/CheckStyle.java b/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/CheckStyle.java deleted file mode 100644 index adb91cce6..000000000 --- a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/CheckStyle.java +++ /dev/null @@ -1,35 +0,0 @@ -package edu.hm.hafner.analysis.parser.checkstyle; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -/** - * Java Bean class for a errors collection of the Checkstyle format. - * - * @author Ullrich Hafner - */ -public class CheckStyle { - /** All files of this violations collection. */ - private final List files = new ArrayList<>(); - - /** - * Adds a new file to this bug collection. - * - * @param file the file to add - */ - public void addFile(final File file) { - files.add(file); - } - - /** - * Returns all files of this violations collection. The returned collection is - * read-only. - * - * @return all files of this bug collection - */ - public Collection getFiles() { - return Collections.unmodifiableCollection(files); - } -} diff --git a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/CheckStyleParser.java b/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/CheckStyleParser.java deleted file mode 100644 index 1685c2f6f..000000000 --- a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/CheckStyleParser.java +++ /dev/null @@ -1,113 +0,0 @@ -package edu.hm.hafner.analysis.parser.checkstyle; - -import java.io.IOException; -import java.io.Serial; - -import org.apache.commons.lang3.StringUtils; -import org.xml.sax.SAXException; - -import edu.hm.hafner.analysis.IssueBuilder; -import edu.hm.hafner.analysis.IssueParser; -import edu.hm.hafner.analysis.ParsingException; -import edu.hm.hafner.analysis.ReaderFactory; -import edu.hm.hafner.analysis.Report; -import edu.hm.hafner.analysis.SecureDigester; -import edu.umd.cs.findbugs.annotations.CheckForNull; - -/** - * A parser for Checkstyle XML files. - * - * @author Ullrich Hafner - */ -public class CheckStyleParser extends IssueParser { - @Serial - private static final long serialVersionUID = -3187275729854832128L; - - @Override - public Report parse(final ReaderFactory readerFactory) throws ParsingException { - var digester = new SecureDigester(CheckStyleParser.class); - - var rootXPath = "checkstyle"; - digester.addObjectCreate(rootXPath, CheckStyle.class); - digester.addSetProperties(rootXPath); - - var fileXPath = "checkstyle/file"; - digester.addObjectCreate(fileXPath, File.class); - digester.addSetProperties(fileXPath); - digester.addSetNext(fileXPath, "addFile", File.class.getName()); - - var bugXPath = "checkstyle/file/error"; - digester.addObjectCreate(bugXPath, Error.class); - digester.addSetProperties(bugXPath); - digester.addSetNext(bugXPath, "addError", Error.class.getName()); - - try (var reader = readerFactory.create()) { - CheckStyle checkStyle = digester.parse(reader); - if (checkStyle == null) { - throw new ParsingException("Input stream is not a Checkstyle file."); - } - - return convert(checkStyle); - } - catch (IOException | SAXException exception) { - throw new ParsingException(exception); - } - } - - /** - * Converts the internal structure to the annotations API. - * - * @param collection - * the internal maven module - * - * @return a maven module of the annotations API - */ - private Report convert(final CheckStyle collection) { - try (var issueBuilder = new IssueBuilder()) { - var report = new Report(); - - for (File file : collection.getFiles()) { - if (isValidWarning(file)) { - for (Error error : file.getErrors()) { - issueBuilder.guessSeverity(error.getSeverity()); - var source = error.getSource(); - issueBuilder.setType(getType(source)); - issueBuilder.setCategory(getCategory(source)); - issueBuilder.setMessage(error.getMessage()); - issueBuilder.setLineStart(error.getLine()); - issueBuilder.setFileName(file.getName()); - issueBuilder.setColumnStart(error.getColumn()); - report.add(issueBuilder.buildAndClean()); - } - } - } - return report; - } - } - - @CheckForNull - private String getCategory(@CheckForNull final String source) { - return StringUtils.capitalize(getType(StringUtils.substringBeforeLast(source, "."))); - } - - @CheckForNull - private String getType(@CheckForNull final String source) { - if (StringUtils.contains(source, '.')) { - return StringUtils.substringAfterLast(source, "."); - } - return source; - } - - /** - * Returns {@code true} if this warning is valid or {@code false} if the warning can't be processed by the - * checkstyle plug-in. - * - * @param file - * the file to check - * - * @return {@code true} if this warning is valid - */ - private boolean isValidWarning(final File file) { - return !StringUtils.endsWith(file.getName(), "package.html"); - } -} diff --git a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/Error.java b/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/Error.java deleted file mode 100644 index 2914fd09b..000000000 --- a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/Error.java +++ /dev/null @@ -1,63 +0,0 @@ -package edu.hm.hafner.analysis.parser.checkstyle; - -import edu.umd.cs.findbugs.annotations.CheckForNull; - -/** - * Java Bean class for a violation of the Checkstyle format. - * - * @author Ullrich Hafner - */ -@SuppressWarnings({"all", "JavaLangClash"}) -public class Error { - @CheckForNull - private String source; - @CheckForNull - private String severity; - @CheckForNull - private String message; - private int line; - private int column; - - public int getColumn() { - return column; - } - - public void setColumn(final int column) { - this.column = column; - } - - @CheckForNull - public String getSource() { - return source; - } - - public void setSource(final String source) { - this.source = source; - } - - @CheckForNull - public String getSeverity() { - return severity; - } - - public void setSeverity(final String severity) { - this.severity = severity; - } - - @CheckForNull - public String getMessage() { - return message; - } - - public void setMessage(final String message) { - this.message = message; - } - - public int getLine() { - return line; - } - - public void setLine(final int line) { - this.line = line; - } -} diff --git a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/File.java b/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/File.java deleted file mode 100644 index fcf412457..000000000 --- a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/File.java +++ /dev/null @@ -1,60 +0,0 @@ -package edu.hm.hafner.analysis.parser.checkstyle; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; - -import edu.umd.cs.findbugs.annotations.CheckForNull; - -/** - * Java Bean class for a file of the Checkstyle format. - * - * @author Ullrich Hafner - */ -public class File { - /** Name of the file. */ - @CheckForNull - private String name; - /** All errors of this file. */ - private final List errors = new ArrayList<>(); - - /** - * Adds a new violation to this file. - * - * @param violation - * the new violation - */ - public void addError(final Error violation) { - errors.add(violation); - } - - /** - * Returns all violations of this file. The returned collection is - * read-only. - * - * @return all violations in this file - */ - public Collection getErrors() { - return Collections.unmodifiableCollection(errors); - } - - /** - * Returns the name of this file. - * - * @return the name of this file - */ - @CheckForNull - public String getName() { - return name; - } - - /** - * Sets the name of this file to the specified value. - * - * @param name the value to set - */ - public void setName(@CheckForNull final String name) { - this.name = name; - } -} diff --git a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/Rule.java b/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/Rule.java deleted file mode 100644 index 6a795479d..000000000 --- a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/Rule.java +++ /dev/null @@ -1,83 +0,0 @@ -package edu.hm.hafner.analysis.parser.checkstyle; - -import org.apache.commons.lang3.StringUtils; - -import edu.umd.cs.findbugs.annotations.CheckForNull; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -/** - * Java Bean class representing a Checkstyle rule. - * - * @author Ullrich Hafner - */ -@SuppressWarnings("PMD.DataClass") -public class Rule { - /** Description to indicate that the rules stored in this plug-in don't match with the generators version. */ - static final String UNDEFINED_DESCRIPTION = StringUtils.EMPTY; - /** The name of the subsection that defines a description in the docbook files. */ - private static final String DESCRIPTION_SUBSECTION_NAME = "Description"; - - @CheckForNull - private String name; - @CheckForNull - private String description; - - /** - * Instantiates a new rule. - */ - public Rule() { - // nothing to do - } - - /** - * Instantiates a new rule. - * - * @param name - * the name of the rule - */ - public Rule(@CheckForNull final String name) { - this.name = name; - description = UNDEFINED_DESCRIPTION; - } - - /** - * Returns the name of this rule. - * - * @return the name - */ - public String getName() { - return StringUtils.defaultString(name); - } - - /** - * Sets the name of this rule. - * - * @param name - * the name - */ - public void setName(@CheckForNull final String name) { - this.name = name; - } - - /** - * Returns the description of this rule. - * - * @return the description - */ - public String getDescription() { - return StringUtils.defaultString(description); - } - - /** - * Sets the description of this rule. The description is only set if the topic is a description. - * - * @param topic - * the topic that might contain the description - */ - @SuppressFBWarnings("IMPROPER_UNICODE") - public void setDescription(final Topic topic) { - if (DESCRIPTION_SUBSECTION_NAME.equalsIgnoreCase(topic.getName())) { - description = topic.getValue(); - } - } -} diff --git a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/Topic.java b/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/Topic.java deleted file mode 100644 index becb9a1a5..000000000 --- a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/Topic.java +++ /dev/null @@ -1,56 +0,0 @@ -package edu.hm.hafner.analysis.parser.checkstyle; - -import org.apache.commons.lang3.StringUtils; - -import edu.umd.cs.findbugs.annotations.CheckForNull; - -/** - * Java Bean class representing a DocBook subsection. - * - * @author Ullrich Hafner - */ -@SuppressWarnings("PMD.DataClass") -public class Topic { - @CheckForNull - private String name; - @CheckForNull - private String value; - - /** - * Returns the name of this topic. - * - * @return the name - */ - public String getName() { - return StringUtils.defaultString(name); - } - - /** - * Sets the name of this topic. - * - * @param name - * the name - */ - public void setName(@CheckForNull final String name) { - this.name = name; - } - - /** - * Returns the value of this topic. - * - * @return the value - */ - public String getValue() { - return StringUtils.defaultString(value); - } - - /** - * Sets the value of this topic. - * - * @param value - * the value - */ - public void setValue(@CheckForNull final String value) { - this.value = value; - } -} diff --git a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/TopicRule.java b/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/TopicRule.java deleted file mode 100644 index f035bd1d2..000000000 --- a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/TopicRule.java +++ /dev/null @@ -1,71 +0,0 @@ -package edu.hm.hafner.analysis.parser.checkstyle; - -import java.io.StringWriter; -import java.lang.reflect.InvocationTargetException; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.TransformerException; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; - -import org.apache.commons.beanutils.MethodUtils; -import org.apache.commons.digester3.NodeCreateRule; -import org.apache.commons.lang3.StringUtils; -import org.w3c.dom.Element; -import org.w3c.dom.Node; - -import edu.hm.hafner.util.SecureXmlParserFactory; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - -/** - * Digester rule to parse the actual content of a DocBook subsection node. Does not interpret XML elements that are - * children of a subsection. - * - * @author Ullrich Hafner - */ -public class TopicRule extends NodeCreateRule { - /** - * Instantiates a new topic rule. - * - * @throws ParserConfigurationException - * the parser configuration exception - */ - TopicRule() throws ParserConfigurationException { - super(Node.ELEMENT_NODE); - } - - @Override - public void end(final String namespace, final String name) - throws TransformerException, InvocationTargetException, NoSuchMethodException, IllegalAccessException { - Element subsection = getDigester().pop(); - var description = extractNodeContent(subsection); - - MethodUtils.invokeExactMethod(getDigester().peek(), "setValue", description); - } - - /** - * Extracts the node content. Basically returns every character in the subsection element. - * - * @param subsection - * the subsection of a rule - * - * @return the node content - * @throws TransformerException - * in case of an error - */ - @SuppressFBWarnings("SECURITY") - private String extractNodeContent(final Element subsection) throws TransformerException { - var content = new StringWriter(); - - var transformer = new SecureXmlParserFactory().createTransformer(); - transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); - transformer.transform(new DOMSource(subsection), new StreamResult(content)); - var text = content.toString(); - var prefixRemoved = StringUtils.substringAfter(text, ">"); - var suffixRemoved = StringUtils.substringBeforeLast(prefixRemoved, "<"); - - var endSourceRemoved = StringUtils.replace(suffixRemoved, "", "
"); - - return StringUtils.replace(endSourceRemoved, "", "
");
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/package-info.java b/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/package-info.java
deleted file mode 100644
index f6b0ff84c..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/checkstyle/package-info.java
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * CheckStyle parser.
- */
-@DefaultAnnotation(NonNull.class)
-package edu.hm.hafner.analysis.parser.checkstyle;
-
-import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
-import edu.umd.cs.findbugs.annotations.NonNull;
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/cpd/CpdParser.java b/src/main/java/edu/hm/hafner/analysis/parser/dry/cpd/CpdParser.java
deleted file mode 100644
index 6b86a7c06..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/dry/cpd/CpdParser.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package edu.hm.hafner.analysis.parser.dry.cpd;
-
-import java.io.Serial;
-import java.util.List;
-
-import org.apache.commons.digester3.Digester;
-
-import edu.hm.hafner.analysis.DuplicationGroup;
-import edu.hm.hafner.analysis.IssueBuilder;
-import edu.hm.hafner.analysis.Report;
-import edu.hm.hafner.analysis.parser.dry.AbstractDryParser;
-
-/**
- * A parser for PMD's CPD XML files.
- *
- * @author Ullrich Hafner
- */
-public class CpdParser extends AbstractDryParser {
-    /** Unique ID of this class. */
-    @Serial
-    private static final long serialVersionUID = 6507147028628714706L;
-
-    /**
-     * Creates a new instance of {@link CpdParser}.
-     *
-     * @param highThreshold
-     *         minimum number of duplicate lines for high priority warnings
-     * @param normalThreshold
-     *         minimum number of duplicate lines for normal priority warnings
-     */
-    public CpdParser(final int highThreshold, final int normalThreshold) {
-        super(highThreshold, normalThreshold);
-    }
-
-    /**
-     * Creates a new instance of {@link CpdParser}. The {@code highThreshold} is set to 50, the {@code normalThreshold}
-     * is set to 25.
-     */
-    public CpdParser() {
-        super(50, 25);
-    }
-
-    @Override
-    protected void configureParser(final Digester digester) {
-        var duplicationXPath = "*/pmd-cpd/duplication";
-        digester.addObjectCreate(duplicationXPath, Duplication.class);
-        digester.addSetProperties(duplicationXPath);
-        digester.addCallMethod(duplicationXPath + "/codefragment", "setCodeFragment", 0);
-        digester.addSetNext(duplicationXPath, "add");
-
-        var fileXPath = duplicationXPath + "/file";
-        digester.addObjectCreate(fileXPath, SourceFile.class);
-        digester.addSetProperties(fileXPath);
-        digester.addSetNext(fileXPath, "addFile", SourceFile.class.getName());
-    }
-
-    @Override
-    protected Report convertDuplicationsToIssues(final List duplications, final IssueBuilder issueBuilder) {
-        var report = new Report();
-
-        for (Duplication duplication : duplications) {
-            var group = new DuplicationGroup(duplication.getCodeFragment());
-            for (SourceFile file : duplication.getFiles()) {
-                issueBuilder.setSeverity(getPriority(duplication.getLines()))
-                        .setLineStart(file.getLine())
-                        .setLineEnd(file.getLine() + duplication.getLines() - 1)
-                        .setFileName(file.getPath())
-                        .setType("CPD")
-                        .setAdditionalProperties(group);
-                var issue = issueBuilder.build();
-                group.add(issue);
-                report.add(issue);
-            }
-        }
-        return report;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/cpd/Duplication.java b/src/main/java/edu/hm/hafner/analysis/parser/dry/cpd/Duplication.java
deleted file mode 100644
index a189481ac..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/dry/cpd/Duplication.java
+++ /dev/null
@@ -1,102 +0,0 @@
-package edu.hm.hafner.analysis.parser.dry.cpd;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-
-/**
- * Java Bean class for a CPD duplication.
- *
- * @author Ullrich Hafner
- */
-@SuppressWarnings("PMD.DataClass")
-public class Duplication {
-    /** Number of duplicate lines. */
-    private int lines;
-    /** Number of duplicate tokens. */
-    private int tokens;
-    /** The duplicated code fragment. */
-    @CheckForNull
-    private String codeFragment;
-
-    /** All files of this duplication. */
-    private final List files = new ArrayList<>();
-
-    /**
-     * Adds a new file to this duplication.
-     *
-     * @param file
-     *            the new file
-     */
-    public void addFile(final SourceFile file) {
-        files.add(file);
-    }
-
-    /**
-     * Returns all files of the duplication. The returned collection is
-     * read-only.
-     *
-     * @return all files
-     */
-    public Collection getFiles() {
-        return Collections.unmodifiableCollection(files);
-    }
-
-    /**
-     * Returns the number of duplicate lines.
-     *
-     * @return the lines
-     */
-    public int getLines() {
-        return lines;
-    }
-
-    /**
-     * Sets the number of duplicate lines to the specified value.
-     *
-     * @param lines the value to set
-     */
-    public void setLines(final int lines) {
-        this.lines = lines;
-    }
-
-    /**
-     * Returns the number of duplicate tokens.
-     *
-     * @return the tokens
-     */
-    public int getTokens() {
-        return tokens;
-    }
-
-    /**
-     * Sets the number of duplicate tokens to the specified value.
-     *
-     * @param tokens the value to set
-     */
-    public void setTokens(final int tokens) {
-        this.tokens = tokens;
-    }
-
-    /**
-     * Returns the duplicate code fragment.
-     *
-     * @return the duplicate code fragment
-     */
-    @CheckForNull
-    public String getCodeFragment() {
-        return codeFragment;
-    }
-
-    /**
-     * Sets the duplicate code fragment to the specified value.
-     *
-     * @param codeFragment the value to set
-     */
-    public void setCodeFragment(@CheckForNull final String codeFragment) {
-        this.codeFragment = codeFragment;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/cpd/SourceFile.java b/src/main/java/edu/hm/hafner/analysis/parser/dry/cpd/SourceFile.java
deleted file mode 100644
index c603bf73a..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/dry/cpd/SourceFile.java
+++ /dev/null
@@ -1,56 +0,0 @@
-package edu.hm.hafner.analysis.parser.dry.cpd;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-
-/**
- * Java Bean class for a file of the PMD CPD format.
- *
- * @author Ullrich Hafner
- */
-@SuppressWarnings("PMD.DataClass")
-public class SourceFile {
-    /** Starting line number in file. */
-    private int line;
-    /** Path of the file. */
-    @CheckForNull
-    private String path;
-
-    /**
-     * Returns the path of this file.
-     *
-     * @return the path of this file
-     */
-    @CheckForNull
-    public String getPath() {
-        return path;
-    }
-
-    /**
-     * Sets the path of this file to the specified value.
-     *
-     * @param path
-     *         the value to set
-     */
-    public void setPath(@CheckForNull final String path) {
-        this.path = path;
-    }
-
-    /**
-     * Returns the line of the duplication.
-     *
-     * @return the line of the duplication
-     */
-    public int getLine() {
-        return line;
-    }
-
-    /**
-     * Sets the line of the duplication to the specified value.
-     *
-     * @param line
-     *         the value to set
-     */
-    public void setLine(final int line) {
-        this.line = line;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/cpd/package-info.java b/src/main/java/edu/hm/hafner/analysis/parser/dry/cpd/package-info.java
deleted file mode 100644
index f79920747..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/dry/cpd/package-info.java
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * PMD:CPD parser.
- */
-@DefaultAnnotation(NonNull.class)
-package edu.hm.hafner.analysis.parser.dry.cpd;
-
-import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
-import edu.umd.cs.findbugs.annotations.NonNull;
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/DupFinderParser.java b/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/DupFinderParser.java
deleted file mode 100644
index b4efa5a1d..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/DupFinderParser.java
+++ /dev/null
@@ -1,93 +0,0 @@
-package edu.hm.hafner.analysis.parser.dry.dupfinder;
-
-import java.io.Serial;
-import java.util.List;
-
-import org.apache.commons.digester3.Digester;
-
-import edu.hm.hafner.analysis.DuplicationGroup;
-import edu.hm.hafner.analysis.IssueBuilder;
-import edu.hm.hafner.analysis.Report;
-import edu.hm.hafner.analysis.parser.dry.AbstractDryParser;
-
-/**
- * A parser for Reshaper Dupfinder XML files.
- *
- * @author Rafal Jasica
- */
-public class DupFinderParser extends AbstractDryParser {
-    /** Unique ID of this class. */
-    @Serial
-    private static final long serialVersionUID = 1357147358617711901L;
-
-    /**
-     * Creates a new instance of {@link DupFinderParser}.
-     *
-     * @param highThreshold
-     *         minimum number of duplicate lines for high priority warnings
-     * @param normalThreshold
-     *         minimum number of duplicate lines for normal priority warnings
-     */
-    public DupFinderParser(final int highThreshold, final int normalThreshold) {
-        super(highThreshold, normalThreshold);
-    }
-
-    /**
-     * Creates a new instance of {@link DupFinderParser}. The {@code highThreshold} is set to 50, the {@code normalThreshold}
-     * is set to 25.
-     */
-    public DupFinderParser() {
-        super(50, 25);
-    }
-
-    @Override
-    protected void configureParser(final Digester digester) {
-        var duplicationXPath = "*/DuplicatesReport/Duplicates/Duplicate";
-        digester.addObjectCreate(duplicationXPath, Duplicate.class);
-        digester.addSetProperties(duplicationXPath, "Cost", "cost");
-        digester.addSetNext(duplicationXPath, "add");
-
-        var fragmentXPath = duplicationXPath + "/Fragment";
-        digester.addObjectCreate(fragmentXPath, Fragment.class);
-        digester.addBeanPropertySetter(fragmentXPath + "/FileName", "fileName");
-        digester.addBeanPropertySetter(fragmentXPath + "/Text", "text");
-        digester.addSetNext(fragmentXPath, "addFragment", Fragment.class.getName());
-
-        var lineRangeXPath = fragmentXPath + "/LineRange";
-        digester.addObjectCreate(lineRangeXPath, Range.class);
-        digester.addSetProperties(lineRangeXPath, "Start", "start");
-        digester.addSetProperties(lineRangeXPath, "End", "end");
-        digester.addSetNext(lineRangeXPath, "setLineRange", Range.class.getName());
-
-        var offsetRangeXPath = fragmentXPath + "/OffsetRange";
-        digester.addObjectCreate(offsetRangeXPath, Range.class);
-        digester.addSetProperties(offsetRangeXPath, "Start", "start");
-        digester.addSetProperties(offsetRangeXPath, "End", "end");
-        digester.addSetNext(offsetRangeXPath, "setOffsetRange", Range.class.getName());
-    }
-
-    @Override
-    protected Report convertDuplicationsToIssues(final List duplications, final IssueBuilder issueBuilder) {
-        var report = new Report();
-
-        for (Duplicate duplication : duplications) {
-            var group = new DuplicationGroup();
-            for (Fragment fragment : duplication.getFragments()) {
-                group.setCodeFragment(fragment.getText());
-                var lineRange = fragment.getLineRange();
-                int count = lineRange.getEnd() - lineRange.getStart() + 1;
-                issueBuilder.setSeverity(getPriority(count))
-                        .setLineStart(lineRange.getStart())
-                        .setLineEnd(lineRange.getEnd())
-                        .setFileName(fragment.getFileName())
-                        .setType("DupFinder")
-                        .setAdditionalProperties(group);
-                var issue = issueBuilder.build();
-                group.add(issue);
-                report.add(issue);
-            }
-        }
-
-        return report;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/Duplicate.java b/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/Duplicate.java
deleted file mode 100644
index 240d851a1..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/Duplicate.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package edu.hm.hafner.analysis.parser.dry.dupfinder;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Java Bean class for a Reshaper DupFinder duplicate.
- *
- * @author Rafal Jasica
- */
-public class Duplicate {
-    /** The duplicated cost. */
-    private int cost;
-
-    /** All files of this duplication. */
-    private final List fragments = new ArrayList<>();
-
-    /**
-     * Returns the duplicate cost.
-     *
-     * @return the duplicate cost
-     */
-    public int getCost() {
-        return cost;
-    }
-
-    /**
-     * Sets the duplicate cost to the specified value.
-     *
-     * @param cost the value to set
-     */
-    public void setCost(final int cost) {
-        this.cost = cost;
-    }
-
-    /**
-     * Adds a new file to this duplication.
-     *
-     * @param file
-     *            the new file
-     */
-    public void addFragment(final Fragment file) {
-        fragments.add(file);
-    }
-
-    /**
-     * Returns all files of the duplication. The returned collection is
-     * read-only.
-     *
-     * @return all files
-     */
-    public Collection getFragments() {
-        return Collections.unmodifiableCollection(fragments);
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/Fragment.java b/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/Fragment.java
deleted file mode 100644
index 4b55df7cc..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/Fragment.java
+++ /dev/null
@@ -1,101 +0,0 @@
-package edu.hm.hafner.analysis.parser.dry.dupfinder;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * Java Bean class for a Reshaper DupFinder fragment.
- *
- * @author Rafal Jasica
- */
-@SuppressWarnings("PMD.DataClass")
-@SuppressFBWarnings("EI")
-public class Fragment {
-    @CheckForNull
-    private String fileName;
-    @CheckForNull
-    private String text;
-    @CheckForNull
-    private Range lineRange;
-    @CheckForNull
-    private Range offsetRange;
-
-    /**
-     * Returns the file name.
-     *
-     * @return the path of this file
-     */
-    @CheckForNull
-    public String getFileName() {
-        return fileName;
-    }
-
-    /**
-     * Sets the file name to the specified value.
-     *
-     * @param fileName the value to set
-     */
-    @SuppressFBWarnings("NM")
-    public void setFileName(@CheckForNull final String fileName) {
-        this.fileName = fileName;
-    }
-
-    /**
-     * Returns the text.
-     *
-     * @return the text
-     */
-    @CheckForNull
-    public String getText() {
-        return text;
-    }
-
-    /**
-     * Sets the text to the specified value.
-     *
-     * @param text the value to set
-     */
-    public void setText(@CheckForNull final String text) {
-        this.text = text;
-    }
-
-    /**
-     * Returns the line range.
-     *
-     * @return the line range
-     */
-    public Range getLineRange() {
-        if (lineRange == null) {
-            return new Range();
-        }
-        return lineRange;
-    }
-
-    /**
-     * Sets the line range to the specified value.
-     *
-     * @param lineRange the value to set
-     */
-    public void setLineRange(@CheckForNull final Range lineRange) {
-        this.lineRange = lineRange;
-    }
-
-    /**
-     * Returns the offset range.
-     *
-     * @return the offset range
-     */
-    @CheckForNull
-    public Range getOffsetRange() {
-        return offsetRange;
-    }
-
-    /**
-     * Sets the offset range to the specified value.
-     *
-     * @param offsetRange the value to set
-     */
-    public void setOffsetRange(@CheckForNull final Range offsetRange) {
-        this.offsetRange = offsetRange;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/Range.java b/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/Range.java
deleted file mode 100644
index 917c60cbb..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/Range.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package edu.hm.hafner.analysis.parser.dry.dupfinder;
-
-/**
- * Java Bean class for a Reshaper DupFinder range.
- *
- * @author Rafal Jasica
- */
-@SuppressWarnings("PMD.DataClass")
-public class Range {
-    private int start;
-    private int end;
-
-    /**
-     * Returns the start.
-     *
-     * @return the start
-     */
-    public int getStart() {
-        return start;
-    }
-
-    /**
-     * Sets the start to the specified value.
-     *
-     * @param start the value to set
-     */
-    public void setStart(final int start) {
-        this.start = start;
-    }
-
-    /**
-     * Returns the line range start.
-     *
-     * @return the line range start
-     */
-    public int getEnd() {
-        return end;
-    }
-
-    /**
-     * Sets the end to the specified value.
-     *
-     * @param end the value to set
-     */
-    public void setEnd(final int end) {
-        this.end = end;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/package-info.java b/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/package-info.java
deleted file mode 100644
index 5978eae44..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/dry/dupfinder/package-info.java
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * DupFinder parser.
- */
-@DefaultAnnotation(NonNull.class)
-package edu.hm.hafner.analysis.parser.dry.dupfinder;
-
-import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
-import edu.umd.cs.findbugs.annotations.NonNull;
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/package-info.java b/src/main/java/edu/hm/hafner/analysis/parser/dry/package-info.java
deleted file mode 100644
index 9e3f30a03..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/dry/package-info.java
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * Duplicate Code parsers.
- */
-@DefaultAnnotation(NonNull.class)
-package edu.hm.hafner.analysis.parser.dry;
-
-import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
-import edu.umd.cs.findbugs.annotations.NonNull;
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/simian/Block.java b/src/main/java/edu/hm/hafner/analysis/parser/dry/simian/Block.java
deleted file mode 100644
index 7949207f0..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/dry/simian/Block.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package edu.hm.hafner.analysis.parser.dry.simian;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-
-/**
- * Java Bean class for a duplicated block of a Simian duplication warning.
- *
- * @author Ullrich Hafner
- */
-@SuppressWarnings("PMD.DataClass")
-public class Block {
-    @CheckForNull
-    private String sourceFile;
-    private int startLineNumber;
-    private int endLineNumber;
-
-    /**
-     * Returns the file name.
-     *
-     * @return the file name
-     */
-    @CheckForNull
-    public String getSourceFile() {
-        return sourceFile;
-    }
-
-    /**
-     * Sets the file name to the specified value.
-     *
-     * @param sourceFile
-     *            the value to set
-     */
-    public void setSourceFile(@CheckForNull final String sourceFile) {
-        this.sourceFile = sourceFile;
-    }
-
-    /**
-     * Returns the line number of the start of the duplication.
-     *
-     * @return the line number of the start of the duplication.
-     */
-    public int getStartLineNumber() {
-        return startLineNumber;
-    }
-
-    /**
-     * Sets the line number of the start of the duplication to the specified
-     * value.
-     *
-     * @param startLineNumber
-     *            the value to set
-     */
-    public void setStartLineNumber(final int startLineNumber) {
-        this.startLineNumber = startLineNumber;
-    }
-
-    /**
-     * Returns the line number of the end of the duplication.
-     *
-     * @return the line number of the end of the duplication.
-     */
-    public int getEndLineNumber() {
-        return endLineNumber;
-    }
-
-    /**
-     * Sets the line number of the end of the duplication to the specified
-     * value.
-     *
-     * @param endLineNumber
-     *            the value to set
-     */
-    public void setEndLineNumber(final int endLineNumber) {
-        this.endLineNumber = endLineNumber;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/simian/Set.java b/src/main/java/edu/hm/hafner/analysis/parser/dry/simian/Set.java
deleted file mode 100644
index 4a364db51..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/dry/simian/Set.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package edu.hm.hafner.analysis.parser.dry.simian;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Java Bean class for a Simian duplication set.
- *
- * @author Ullrich Hafner
- */
-@SuppressWarnings("PMD.DataClass")
-public class Set {
-    private int lineCount;
-    private final List blocks = new ArrayList<>();
-
-    /**
-     * Adds a new block to this duplication set.
-     *
-     * @param block
-     *            the new block
-     */
-    public void addBlock(final Block block) {
-        blocks.add(block);
-    }
-
-    /**
-     * Returns all blocks of this duplication set. The returned collection is
-     * read-only.
-     *
-     * @return all files
-     */
-    public Collection getBlocks() {
-        return Collections.unmodifiableCollection(blocks);
-    }
-
-    /**
-     * Returns the number of duplicated lines.
-     *
-     * @return the lineCount
-     */
-    public int getLineCount() {
-        return lineCount;
-    }
-
-    /**
-     * Sets the number of duplicated lines to the specified value.
-     *
-     * @param value the value to set
-     */
-    public void setLineCount(final int value) {
-        lineCount = value;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/simian/SimianParser.java b/src/main/java/edu/hm/hafner/analysis/parser/dry/simian/SimianParser.java
deleted file mode 100644
index e50741166..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/dry/simian/SimianParser.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package edu.hm.hafner.analysis.parser.dry.simian;
-
-import java.io.Serial;
-import java.util.List;
-
-import org.apache.commons.digester3.Digester;
-
-import edu.hm.hafner.analysis.DuplicationGroup;
-import edu.hm.hafner.analysis.IssueBuilder;
-import edu.hm.hafner.analysis.Report;
-import edu.hm.hafner.analysis.parser.dry.AbstractDryParser;
-
-/**
- * A parser for Simian XML files.
- *
- * @author Ullrich Hafner
- */
-public class SimianParser extends AbstractDryParser {
-    /** Unique ID of this class. */
-    @Serial
-    private static final long serialVersionUID = 6507147028628714706L;
-
-    /**
-     * Creates a new instance of {@link SimianParser}.
-     *
-     * @param highThreshold
-     *         minimum number of duplicate lines for high priority warnings
-     * @param normalThreshold
-     *         minimum number of duplicate lines for normal priority warnings
-     */
-    public SimianParser(final int highThreshold, final int normalThreshold) {
-        super(highThreshold, normalThreshold);
-    }
-
-    /**
-     * Creates a new instance of {@link SimianParser}. The {@code highThreshold} is set to 50, the {@code normalThreshold}
-     * is set to 25.
-     */
-    public SimianParser() {
-        super(50, 25);
-    }
-
-    @Override
-    protected void configureParser(final Digester digester) {
-        var duplicationXPath = "*/simian/check/set";
-        digester.addObjectCreate(duplicationXPath, Set.class);
-        digester.addSetProperties(duplicationXPath);
-        digester.addSetNext(duplicationXPath, "add");
-
-        var fileXPath = duplicationXPath + "/block";
-        digester.addObjectCreate(fileXPath, Block.class);
-        digester.addSetProperties(fileXPath);
-        digester.addSetNext(fileXPath, "addBlock", Block.class.getName());
-    }
-
-    @Override
-    protected Report convertDuplicationsToIssues(final List duplications, final IssueBuilder issueBuilder) {
-        var report = new Report();
-
-        for (Set duplication : duplications) {
-            var group = new DuplicationGroup();
-            for (Block file : duplication.getBlocks()) {
-                issueBuilder.setSeverity(getPriority(duplication.getLineCount()))
-                        .setLineStart(file.getStartLineNumber())
-                        .setLineEnd(file.getEndLineNumber())
-                        .setFileName(file.getSourceFile())
-                        .setAdditionalProperties(group)
-                        .setType("Simian");
-                var issue = issueBuilder.build();
-                group.add(issue);
-                report.add(issue);
-            }
-        }
-        return report;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/dry/simian/package-info.java b/src/main/java/edu/hm/hafner/analysis/parser/dry/simian/package-info.java
deleted file mode 100644
index 3eefb2ec3..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/dry/simian/package-info.java
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * Simian parser.
- */
-@DefaultAnnotation(NonNull.class)
-package edu.hm.hafner.analysis.parser.dry.simian;
-
-import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
-import edu.umd.cs.findbugs.annotations.NonNull;
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/fxcop/FxCopRule.java b/src/main/java/edu/hm/hafner/analysis/parser/fxcop/FxCopRule.java
deleted file mode 100644
index 02d3811ca..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/fxcop/FxCopRule.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package edu.hm.hafner.analysis.parser.fxcop;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-
-/**
- * Internal model for a FxCop rule.
- *
- * @author Erik Ramfelt
- */
-@SuppressWarnings({"PMD", "all", "CheckStyle"})
-public class FxCopRule {
-    private final String typeName;
-    private final String category;
-    private final String checkId;
-    @CheckForNull
-    private String name;
-    @CheckForNull
-    private String url;
-    @CheckForNull
-    private String description;
-
-    public FxCopRule(final String typeName, final String category, final String checkId) {
-        this.typeName = typeName;
-        this.category = category;
-        this.checkId = checkId;
-    }
-
-    @CheckForNull
-    public String getName() {
-        return name;
-    }
-
-    public void setName(final String name) {
-        this.name = name;
-    }
-
-    @CheckForNull
-    public String getUrl() {
-        return url;
-    }
-
-    public void setUrl(final String url) {
-        this.url = url;
-    }
-
-    @CheckForNull
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(final String description) {
-        this.description = description;
-    }
-
-    public String getTypeName() {
-        return typeName;
-    }
-
-    public String getCategory() {
-        return category;
-    }
-
-    public String getCheckId() {
-        return checkId;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/fxcop/FxCopRuleSet.java b/src/main/java/edu/hm/hafner/analysis/parser/fxcop/FxCopRuleSet.java
deleted file mode 100644
index 10017eb92..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/fxcop/FxCopRuleSet.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package edu.hm.hafner.analysis.parser.fxcop;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-
-import org.apache.commons.lang3.StringUtils;
-import org.w3c.dom.Element;
-
-import edu.hm.hafner.analysis.util.XmlElementUtil;
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-
-/**
- * Internal set containing rules for FxCop.
- *
- * @author Erik Ramfelt
- */
-@SuppressWarnings({"PMD", "all", "CheckStyle"})
-public class FxCopRuleSet {
-    private final Map rules = new HashMap<>();
-
-    /***
-     * Parse the element and insert the rule into the rule set.
-     * @param element the element
-     */
-    public void addRule(final Element element) {
-        var rule = new FxCopRule(element.getAttribute("TypeName"), element.getAttribute("Category"), element
-                .getAttribute("CheckId"));
-        rule.setUrl(getNamedTagText(element, "Url"));
-        rule.setDescription(getNamedTagText(element, "Description"));
-        rule.setName(getNamedTagText(element, "Name"));
-
-        rules.put(getRuleKey(rule.getCategory(), rule.getCheckId()), rule);
-    }
-
-    /**
-     * Returns the text value of the named child element if it exists
-     *
-     * @param element
-     *         the element to check look for child elements
-     * @param tagName
-     *         the name of the child element
-     *
-     * @return the text value; or "" if no element was found
-     */
-    private String getNamedTagText(final Element element, final String tagName) {
-        Optional foundElement = XmlElementUtil.getFirstChildElementByName(element, tagName);
-        if (foundElement.isPresent()) {
-            return foundElement.get().getTextContent();
-        }
-        else {
-            return StringUtils.EMPTY;
-        }
-    }
-
-    /**
-     * Returns if the rule set contains a rule for the specified category and id
-     *
-     * @param category
-     *         the rule category
-     * @param checkId
-     *         the rule id
-     *
-     * @return {@code true}  if the rule set contains a rule for the specified category and id, {@code false} otherwise
-     */
-    public boolean contains(final String category, final String checkId) {
-        return rules.containsKey(getRuleKey(category, checkId));
-    }
-
-    /**
-     * Returns the specified rule if it exists
-     *
-     * @param category
-     *         the rule category
-     * @param checkId
-     *         the id of the rule
-     *
-     * @return the rule; null otherwise
-     */
-    @CheckForNull
-    public FxCopRule getRule(final String category, final String checkId) {
-        var key = getRuleKey(category, checkId);
-        FxCopRule rule = null;
-        if (rules.containsKey(key)) {
-            rule = rules.get(key);
-        }
-        return rule;
-    }
-
-    /**
-     * Returns the key for the map
-     *
-     * @param category
-     *         category of the rule
-     * @param checkId
-     *         id of the rule
-     *
-     * @return category + "#" + checkid
-     */
-    private String getRuleKey(final String category, final String checkId) {
-        return category + "#" + checkId;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/fxcop/package-info.java b/src/main/java/edu/hm/hafner/analysis/parser/fxcop/package-info.java
deleted file mode 100644
index 6788cf736..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/fxcop/package-info.java
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * FxCop parser.
- */
-@DefaultAnnotation(NonNull.class)
-package edu.hm.hafner.analysis.parser.fxcop;
-
-import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
-import edu.umd.cs.findbugs.annotations.NonNull;
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/gendarme/GendarmeRule.java b/src/main/java/edu/hm/hafner/analysis/parser/gendarme/GendarmeRule.java
deleted file mode 100644
index 3c45e17a7..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/gendarme/GendarmeRule.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package edu.hm.hafner.analysis.parser.gendarme;
-
-import java.net.URL;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-
-@SuppressWarnings("all")
-public class GendarmeRule {
-    @CheckForNull
-    private String name;
-    @CheckForNull
-    private String typeName;
-    @CheckForNull
-    private GendarmeRuleType type;
-    @CheckForNull
-    private URL url;
-
-    @CheckForNull
-    public String getTypeName() {
-        return typeName;
-    }
-
-    public void setTypeName(final String typeName) {
-        this.typeName = typeName;
-    }
-
-    @CheckForNull
-    public String getName() {
-        return name;
-    }
-
-    public void setName(final String name) {
-        this.name = name;
-    }
-
-    @CheckForNull
-    public GendarmeRuleType getType() {
-        return type;
-    }
-
-    public void setType(final GendarmeRuleType type) {
-        this.type = type;
-    }
-
-    @CheckForNull
-    public URL getUrl() {
-        return url;
-    }
-
-    public void setUrl(@CheckForNull final URL url) {
-        this.url = url;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/gendarme/GendarmeRuleType.java b/src/main/java/edu/hm/hafner/analysis/parser/gendarme/GendarmeRuleType.java
deleted file mode 100644
index da1ab5895..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/gendarme/GendarmeRuleType.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package edu.hm.hafner.analysis.parser.gendarme;
-
-@SuppressWarnings("all")
-public enum GendarmeRuleType {
-    Method, Type, Assembly
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/gendarme/package-info.java b/src/main/java/edu/hm/hafner/analysis/parser/gendarme/package-info.java
deleted file mode 100644
index 652a7db4d..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/gendarme/package-info.java
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * Gendarme parser.
- */
-@DefaultAnnotation(NonNull.class)
-package edu.hm.hafner.analysis.parser.gendarme;
-
-import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
-import edu.umd.cs.findbugs.annotations.NonNull;
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/jcreport/File.java b/src/main/java/edu/hm/hafner/analysis/parser/jcreport/File.java
deleted file mode 100644
index db53ddeaa..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/jcreport/File.java
+++ /dev/null
@@ -1,171 +0,0 @@
-package edu.hm.hafner.analysis.parser.jcreport;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-
-/**
- * File-Class. Stores field to create a warning. It represents the File-Tags within the report.xml. The
- * Java-Bean-Conformity was chosen due to the digesters style of assigning.
- *
- * @author Johann Vierthaler, johann.vierthaler@web.de
- */
-@SuppressWarnings("PMD.DataClass")
-public class File {
-    @CheckForNull
-    private String name;
-    @CheckForNull
-    private String packageName;
-    @CheckForNull
-    private String srcdir;
-    private final List items = new ArrayList<>();
-
-    /**
-     * These properties are not used to create Warnings. It was decided to keep them available when Jenkins is modified
-     * and needs access to these fields;
-     */
-    @CheckForNull
-    private String level;
-    @CheckForNull
-    private String loc;
-    @CheckForNull
-    private String classname;
-
-    /**
-     * Getter for the Item-Collection.
-     *
-     * @return unmodifiable collection of Item-Objects
-     */
-    public List getItems() {
-        return Collections.unmodifiableList(items);
-    }
-
-    /**
-     * Adds an Item-Object to the collection items.
-     *
-     * @param item add this item.
-     */
-    public void addItem(final Item item) {
-        items.add(item);
-    }
-
-
-    /**
-     * Getter for className-Field.
-     *
-     * @return String className.
-     */
-    @CheckForNull
-    public String getClassname() {
-        return classname;
-    }
-
-    /**
-     * Setter for className-Field.
-     *
-     * @param classname lassNamesetter
-     */
-    public void setClassname(@CheckForNull final String classname) {
-        this.classname = classname;
-    }
-
-    /**
-     * Getter for level-Field.
-     *
-     * @return level
-     */
-    @CheckForNull
-    public String getLevel() {
-        return level;
-    }
-
-    /**
-     * Setter for level-Field.
-     *
-     * @param level set level
-     */
-    public void setLevel(@CheckForNull final String level) {
-        this.level = level;
-    }
-
-
-    /**
-     * Getter for loc-Field.
-     *
-     * @return loc loc
-     */
-    @CheckForNull
-    public String getLoc() {
-        return loc;
-    }
-
-    /**
-     * Setter for loc-Field.
-     *
-     * @param loc locsetter
-     */
-    public void setLoc(@CheckForNull final String loc) {
-        this.loc = loc;
-    }
-
-
-    /**
-     * Getter for name-Field.
-     *
-     * @return name name
-     */
-    @CheckForNull
-    public String getName() {
-        return name;
-    }
-
-    /**
-     * Setter for Name-Field.
-     *
-     * @param name name
-     */
-    public void setName(@CheckForNull final String name) {
-        this.name = name;
-    }
-
-
-    /**
-     * Getter for packageName-Field.
-     *
-     * @return packageName packageName.
-     */
-    @CheckForNull
-    public String getPackageName() {
-        return packageName;
-    }
-
-    /**
-     * Setter for packageName-Field.
-     *
-     * @param packageName packageName Setter
-     */
-    public void setPackageName(@CheckForNull final String packageName) {
-        this.packageName = packageName;
-    }
-
-    /**
-     * Getter for srcdir-Field.
-     *
-     * @return srcdir srcdir.
-     */
-    @CheckForNull
-    public String getSrcdir() {
-        return srcdir;
-    }
-
-    /**
-     * Setter for srcdir-Field.
-     *
-     * @param srcdir srcdir
-     */
-    public void setSrcdir(@CheckForNull final String srcdir) {
-        this.srcdir = srcdir;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/jcreport/Item.java b/src/main/java/edu/hm/hafner/analysis/parser/jcreport/Item.java
deleted file mode 100644
index e00f0172f..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/jcreport/Item.java
+++ /dev/null
@@ -1,187 +0,0 @@
-package edu.hm.hafner.analysis.parser.jcreport;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-
-/**
- * This the Item-Class The Java-Bean-Conformity was chosen due to the digesters style of assigning. It represents the
- * Item-Tags within the report.xml. Items have properties, that are mandatory to create a warning.
- *
- * @author Johann Vierthaler, johann.vierthaler@web.de
- */
-@SuppressWarnings("PMD.DataClass")
-public class Item {
-    @CheckForNull
-    private String column;
-    @CheckForNull
-    private String findingtype;
-    @CheckForNull
-    private String line;
-    @CheckForNull
-    private String message;
-    @CheckForNull
-    private String origin;
-    @CheckForNull
-    private String severity;
-    @CheckForNull
-    private String endcolumn;
-
-    /**
-     * Although this property is not used. It was decided to keep it available when Jenkins is modified and needs access
-     * to this field;
-     */
-    @CheckForNull
-    private String endline;
-
-    /**
-     * Getter for column-Field.
-     *
-     * @return column string
-     */
-    @CheckForNull
-    public String getColumn() {
-        return column;
-    }
-
-    /**
-     * Setter for Column-Field.
-     *
-     * @param column setter
-     */
-    public void setColumn(@CheckForNull final String column) {
-        this.column = column;
-    }
-
-    /**
-     * Getter for findingtype-Field.
-     *
-     * @return findingtype getter
-     */
-    @CheckForNull
-    public String getFindingtype() {
-        return findingtype;
-    }
-
-    /**
-     * Setter for findingtype-Field.
-     *
-     * @param findingtype setter
-     */
-    public void setFindingtype(@CheckForNull final String findingtype) {
-        this.findingtype = findingtype;
-    }
-
-    /**
-     * Getter for line-Field.
-     *
-     * @return line getter
-     */
-    @CheckForNull
-    public String getLine() {
-        return line;
-    }
-
-    /**
-     * Setter for line-Field.
-     *
-     * @param line setter
-     */
-    public void setLine(@CheckForNull final String line) {
-        this.line = line;
-    }
-
-    /**
-     * Getter for message-Field.
-     *
-     * @return message getter
-     */
-    @CheckForNull
-    public String getMessage() {
-        return message;
-    }
-
-    /**
-     * Setter for message-Field.
-     *
-     * @param message setter
-     */
-    public void setMessage(@CheckForNull final String message) {
-        this.message = message;
-    }
-
-    /**
-     * Getter for origin-Field.
-     *
-     * @return origin getter
-     */
-    @CheckForNull
-    public String getOrigin() {
-        return origin;
-    }
-
-    /**
-     * Setter for origin-Field.
-     *
-     * @param origin setter
-     */
-    public void setOrigin(@CheckForNull final String origin) {
-        this.origin = origin;
-    }
-
-    /**
-     * Getter for severity-Field.
-     *
-     * @return severity getter
-     */
-    @CheckForNull
-    public String getSeverity() {
-        return severity;
-    }
-
-    /**
-     * Setter for severtiy-Field.
-     *
-     * @param severity setter
-     */
-    public void setSeverity(@CheckForNull final String severity) {
-        this.severity = severity;
-    }
-
-
-    /**
-     * Getter for endline-Field.
-     *
-     * @return endline getter
-     */
-    @CheckForNull
-    public String getEndline() {
-        return endline;
-    }
-
-    /**
-     * Setter for endline-Field.
-     *
-     * @param endline setter
-     */
-    public void setEndline(@CheckForNull final String endline) {
-        this.endline = endline;
-    }
-
-    /**
-     * Getter for endcolumn-Field.
-     *
-     * @return endcolumn getter
-     */
-    @CheckForNull
-    public String getEndcolumn() {
-        return endcolumn;
-    }
-
-    /**
-     * Setter for endcolumn-Field.
-     *
-     * @param endcolumn setter
-     */
-    public void setEndcolumn(@CheckForNull final String endcolumn) {
-        this.endcolumn = endcolumn;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/jcreport/JcReportParser.java b/src/main/java/edu/hm/hafner/analysis/parser/jcreport/JcReportParser.java
deleted file mode 100644
index 9976ece9c..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/jcreport/JcReportParser.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package edu.hm.hafner.analysis.parser.jcreport;
-
-import java.io.IOException;
-import java.io.Serial;
-
-import org.xml.sax.SAXException;
-
-import edu.hm.hafner.analysis.IssueBuilder;
-import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.ParsingException;
-import edu.hm.hafner.analysis.ReaderFactory;
-import edu.hm.hafner.analysis.Report;
-import edu.hm.hafner.analysis.SecureDigester;
-
-/**
- * JcReportParser-Class. This class parses from the jcReport.xml and creates warnings from its content.
- *
- * @author Johann Vierthaler, johann.vierthaler@web.de
- */
-public class JcReportParser extends IssueParser {
-    @Serial
-    private static final long serialVersionUID = -1302787609831475403L;
-
-    @Override
-    public Report parse(final ReaderFactory reader) {
-        try (var issueBuilder = new IssueBuilder()) {
-            var report = createReport(reader);
-            var warnings = new Report();
-            for (int i = 0; i < report.getFiles().size(); i++) {
-                var file = report.getFiles().get(i);
-
-                for (int j = 0; j < file.getItems().size(); j++) {
-                    var item = file.getItems().get(j);
-                    issueBuilder.setFileName(file.getName())
-                            .setLineStart(item.getLine())
-                            .setColumnStart(item.getColumn())
-                            .setColumnEnd(item.getEndcolumn())
-                            .setCategory(item.getFindingtype())
-                            .setPackageName(file.getPackageName())
-                            .setMessage(item.getMessage())
-                            .guessSeverity(item.getSeverity());
-
-                    warnings.add(issueBuilder.buildAndClean());
-                }
-            }
-            return warnings;
-        }
-    }
-
-    /**
-     * Creates a Report-Object out of the content within the JcReport.xml.
-     *
-     * @param readerFactory
-     *         the Reader-object that is the source to build the Report-Object.
-     *
-     * @return the finished Report-Object that creates the Warnings.
-     * @throws ParsingException
-     *         due to digester.parse(new InputSource(source))
-     */
-    public edu.hm.hafner.analysis.parser.jcreport.Report createReport(final ReaderFactory readerFactory)
-            throws ParsingException {
-        var digester = new SecureDigester(JcReportParser.class);
-
-        var report = "report";
-        digester.addObjectCreate(report, edu.hm.hafner.analysis.parser.jcreport.Report.class);
-        digester.addSetProperties(report);
-
-        var file = "report/file";
-        digester.addObjectCreate(file, File.class);
-        digester.addSetProperties(file, "package", "packageName");
-        digester.addSetProperties(file, "src-dir", "srcdir");
-        digester.addSetProperties(file);
-        digester.addSetNext(file, "addFile", File.class.getName());
-
-        var item = "report/file/item";
-        digester.addObjectCreate(item, Item.class);
-        digester.addSetProperties(item);
-        digester.addSetProperties(item, "finding-type", "findingtype");
-        digester.addSetProperties(item, "end-line", "endline");
-        digester.addSetProperties(item, "end-column", "endcolumn");
-        digester.addSetNext(item, "addItem", Item.class.getName());
-
-        try (var reader = readerFactory.create()) {
-            return digester.parse(reader);
-        }
-        catch (IOException | SAXException e) {
-            throw new ParsingException(e);
-        }
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/jcreport/Report.java b/src/main/java/edu/hm/hafner/analysis/parser/jcreport/Report.java
deleted file mode 100644
index 9bbb70df4..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/jcreport/Report.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package edu.hm.hafner.analysis.parser.jcreport;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * This is the Report-Class. It is mandatory to create Warnings. It represents the outer-most node within the
- * report.xml.
- *
- * @author Johann Vierthaler, johann.vierthaler@web.de
- */
-@SuppressWarnings("PMD.DataClass")
-@SuppressFBWarnings("EI")
-public class Report {
-    private List files = new ArrayList<>();
-
-    /**
-     * Returns an unmodifiable Collection.
-     *
-     * @return files getter
-     */
-    public List getFiles() {
-        return Collections.unmodifiableList(files);
-    }
-
-    /**
-     * Setter for the List files.
-     *
-     * @param files a list of files.
-     */
-    public void setFiles(final List files) {
-        this.files = files;
-    }
-
-    /**
-     * Adds a new File to the Collection.
-     *
-     * @param file setter
-     */
-    public void addFile(final File file) {
-        files.add(file);
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/jcreport/package-info.java b/src/main/java/edu/hm/hafner/analysis/parser/jcreport/package-info.java
deleted file mode 100644
index e1163d071..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/jcreport/package-info.java
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * JC Report parser.
- */
-@DefaultAnnotation(NonNull.class)
-package edu.hm.hafner.analysis.parser.jcreport;
-
-import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
-import edu.umd.cs.findbugs.annotations.NonNull;
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/pmd/File.java b/src/main/java/edu/hm/hafner/analysis/parser/pmd/File.java
deleted file mode 100644
index 1789afe03..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/pmd/File.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package edu.hm.hafner.analysis.parser.pmd;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-
-/**
- * Java Bean class for a file of the PMD format.
- *
- * @author Ullrich Hafner
- */
-@SuppressWarnings("InstanceVariableMayNotBeInitialized")
-public class File {
-    /** Name of the file. */
-    @CheckForNull
-    private String name;
-    /** All violations of this file. */
-    private final List violations = new ArrayList<>();
-
-    /**
-     * Adds a new violation to this file.
-     *
-     * @param violation
-     *            the new violation
-     */
-    public void addViolation(final Violation violation) {
-        violations.add(violation);
-    }
-
-    /**
-     * Returns all violations of this file. The returned collection is
-     * read-only.
-     *
-     * @return all violations in this file
-     */
-    public Collection getViolations() {
-        return Collections.unmodifiableCollection(violations);
-    }
-
-    /**
-     * Returns the name of this file.
-     *
-     * @return the name of this file
-     */
-    @CheckForNull
-    public String getName() {
-        return name;
-    }
-
-    /**
-     * Sets the name of this file to the specified value.
-     *
-     * @param name the value to set
-     */
-    public void setName(@CheckForNull final String name) {
-        this.name = name;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/pmd/Pmd.java b/src/main/java/edu/hm/hafner/analysis/parser/pmd/Pmd.java
deleted file mode 100644
index ec30d0ae6..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/pmd/Pmd.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package edu.hm.hafner.analysis.parser.pmd;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Java Bean class for warnings of the PMD format.
- *
- * @author Ullrich Hafner
- */
-public class Pmd {
-    private final List files = new ArrayList<>();
-    private final List errors = new ArrayList<>();
-
-    /**
-     * Adds a new file.
-     *
-     * @param file
-     *         the file to add
-     */
-    public void addFile(final File file) {
-        files.add(file);
-    }
-
-    /**
-     * Adds a new error.
-     *
-     * @param error
-     *         the error to add
-     */
-    public void addError(final PmdError error) {
-        errors.add(error);
-    }
-
-    /**
-     * Returns all files. The returned collection is read-only.
-     *
-     * @return all files
-     */
-    public Collection getFiles() {
-        return Collections.unmodifiableCollection(files);
-    }
-
-    /**
-     * Returns all errors. The returned collection is read-only.
-     *
-     * @return all errors
-     */
-    public Collection getErrors() {
-        return Collections.unmodifiableCollection(errors);
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/pmd/PmdError.java b/src/main/java/edu/hm/hafner/analysis/parser/pmd/PmdError.java
deleted file mode 100644
index c4cd2f81a..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/pmd/PmdError.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package edu.hm.hafner.analysis.parser.pmd;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * Java Bean class for an error of the PMD format.
- *
- * @author Ullrich Hafner
- */
-@SuppressWarnings({"InstanceVariableMayNotBeInitialized", "PMD.DataClass"})
-public class PmdError {
-    @CheckForNull
-    private String filename;
-    @CheckForNull
-    private String msg;
-    @CheckForNull
-    private String description;
-
-    @CheckForNull
-    @SuppressFBWarnings("NM")
-    public String getFilename() {
-        return filename;
-    }
-
-    @SuppressFBWarnings("NM")
-    public void setFilename(@CheckForNull final String filename) {
-        this.filename = filename;
-    }
-
-    @CheckForNull
-    public String getMsg() {
-        return msg;
-    }
-
-    public void setMsg(@CheckForNull final String msg) {
-        this.msg = msg;
-    }
-
-    @CheckForNull
-    public String getDescription() {
-        return description;
-    }
-
-    public void setDescription(@CheckForNull final String description) {
-        this.description = description;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/pmd/PmdParser.java b/src/main/java/edu/hm/hafner/analysis/parser/pmd/PmdParser.java
deleted file mode 100644
index 5743f8fee..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/pmd/PmdParser.java
+++ /dev/null
@@ -1,153 +0,0 @@
-package edu.hm.hafner.analysis.parser.pmd;
-
-import java.io.IOException;
-import java.io.Serial;
-
-import org.apache.commons.lang3.StringUtils;
-import org.xml.sax.SAXException;
-
-import edu.hm.hafner.analysis.IssueBuilder;
-import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.ParsingException;
-import edu.hm.hafner.analysis.ReaderFactory;
-import edu.hm.hafner.analysis.Report;
-import edu.hm.hafner.analysis.SecureDigester;
-import edu.hm.hafner.analysis.Severity;
-
-/**
- * A parser for PMD XML files.
- *
- * @author Ullrich Hafner
- */
-public class PmdParser extends IssueParser {
-    @Serial
-    private static final long serialVersionUID = 6507147028628714706L;
-
-    /** PMD priorities smaller than this value are mapped to {@link Severity#WARNING_HIGH}. */
-    private static final int PMD_PRIORITY_MAPPED_TO_HIGH_PRIORITY = 3;
-    /** PMD priorities greater than this value are mapped to {@link Severity#WARNING_LOW}. */
-    private static final int PMD_PRIORITY_MAPPED_TO_LOW_PRIORITY = 4;
-
-    @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException {
-        var issues = parseIssues(readerFactory);
-        parseErrors(readerFactory).stream().forEach(issues::add);
-        return issues;
-    }
-
-    private Report parseIssues(final ReaderFactory readerFactory) {
-        var digester = new SecureDigester(PmdParser.class);
-
-        var rootXPath = "pmd";
-        digester.addObjectCreate(rootXPath, Pmd.class);
-        digester.addSetProperties(rootXPath);
-
-        var fileXPath = "pmd/file";
-        digester.addObjectCreate(fileXPath, File.class);
-        digester.addSetProperties(fileXPath);
-        digester.addSetNext(fileXPath, "addFile", File.class.getName());
-
-        var bugXPath = "pmd/file/violation";
-        digester.addObjectCreate(bugXPath, Violation.class);
-        digester.addSetProperties(bugXPath);
-        digester.addCallMethod(bugXPath, "setMessage", 0);
-        digester.addSetNext(bugXPath, "addViolation", Violation.class.getName());
-
-        try (var reader = readerFactory.create()) {
-            Pmd pmd = digester.parse(reader);
-            if (pmd == null) {
-                throw new ParsingException("Input stream is not a PMD file.");
-            }
-
-            return convertIssues(pmd);
-        }
-        catch (IOException | SAXException exception) {
-            throw new ParsingException(exception);
-        }
-    }
-
-    private Report parseErrors(final ReaderFactory readerFactory) {
-        var digester = new SecureDigester(PmdParser.class);
-
-        var rootXPath = "pmd";
-        digester.addObjectCreate(rootXPath, Pmd.class);
-        digester.addSetProperties(rootXPath);
-
-        var errorXPath = "pmd/error";
-        digester.addObjectCreate(errorXPath, PmdError.class);
-        digester.addSetProperties(errorXPath);
-        digester.addSetNext(errorXPath, "addError", PmdError.class.getName());
-        digester.addCallMethod(errorXPath, "setDescription", 0);
-
-        try (var reader = readerFactory.create()) {
-            Pmd pmd = digester.parse(reader);
-            if (pmd == null) {
-                throw new ParsingException("Input stream is not a PMD file.");
-            }
-
-            return convertErrors(pmd);
-        }
-        catch (IOException | SAXException exception) {
-            throw new ParsingException(exception);
-        }
-    }
-
-    private Report convertIssues(final Pmd pmdIssues) {
-        try (var issueBuilder = new IssueBuilder()) {
-            var report = new Report();
-            for (File file : pmdIssues.getFiles()) {
-                for (Violation warning : file.getViolations()) {
-                    issueBuilder.setSeverity(mapPriority(warning))
-                            .setMessage(createMessage(warning))
-                            .setCategory(warning.getRuleset())
-                            .setType(warning.getRule())
-                            .setLineStart(warning.getBeginline())
-                            .setLineEnd(warning.getEndline())
-                            .setPackageName(warning.getPackage())
-                            .setFileName(file.getName())
-                            .setColumnStart(warning.getBegincolumn())
-                            .setColumnEnd(warning.getEndcolumn());
-                    report.add(issueBuilder.buildAndClean());
-                }
-            }
-            return report;
-        }
-    }
-
-    private Report convertErrors(final Pmd pmdIssues) {
-        try (var issueBuilder = new IssueBuilder()) {
-            var report = new Report();
-            for (PmdError error : pmdIssues.getErrors()) {
-                issueBuilder.setSeverity(Severity.ERROR)
-                        .setMessage(error.getMsg())
-                        .setDescription(error.getDescription())
-                        .setFileName(error.getFilename());
-                report.add(issueBuilder.buildAndClean());
-            }
-            return report;
-        }
-    }
-
-    private Severity mapPriority(final Violation warning) {
-        if (warning.getPriority() < PMD_PRIORITY_MAPPED_TO_HIGH_PRIORITY) {
-            return Severity.WARNING_HIGH;
-        }
-        else if (warning.getPriority() > PMD_PRIORITY_MAPPED_TO_LOW_PRIORITY) {
-            return Severity.WARNING_LOW;
-        }
-        return Severity.WARNING_NORMAL;
-    }
-
-    private String createMessage(final Violation warning) {
-        var original = warning.getMessage();
-        if (original == null) {
-            return StringUtils.EMPTY;
-        }
-        if (StringUtils.endsWith(original, ".")) {
-            return original;
-        }
-        else {
-            return original + ".";
-        }
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/pmd/Violation.java b/src/main/java/edu/hm/hafner/analysis/parser/pmd/Violation.java
deleted file mode 100644
index ddecd23fc..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/pmd/Violation.java
+++ /dev/null
@@ -1,116 +0,0 @@
-package edu.hm.hafner.analysis.parser.pmd;
-
-import edu.umd.cs.findbugs.annotations.CheckForNull;
-
-/**
- * Java Bean class for a violation of the PMD format.
- *
- * @author Ullrich Hafner
- */
-@SuppressWarnings("all")
-public class Violation {
-    /** Type of warning. */
-    @CheckForNull
-    private String rule;
-    /** Category of warning. */
-    @CheckForNull
-    private String ruleset;
-
-    @CheckForNull
-    private String externalInfoUrl;
-    @CheckForNull
-    private String javaPackage;
-    private int priority;
-    @CheckForNull
-    private String message;
-    private int beginline;
-    private int endline;
-    private int begincolumn;
-    private int endcolumn;
-
-    // CHECKSTYLE:OFF
-    @CheckForNull
-    public String getRule() {
-        return rule;
-    }
-
-    public void setRule(final String rule) {
-        this.rule = rule;
-    }
-
-    @CheckForNull
-    public String getRuleset() {
-        return ruleset;
-    }
-
-    public void setRuleset(final String ruleset) {
-        this.ruleset = ruleset;
-    }
-
-    @CheckForNull
-    public String getExternalInfoUrl() {
-        return externalInfoUrl;
-    }
-
-    public void setExternalInfoUrl(final String externalInfoUrl) {
-        this.externalInfoUrl = externalInfoUrl;
-    }
-
-    @CheckForNull
-    public String getPackage() {
-        return javaPackage;
-    }
-
-    public void setPackage(final String packageName) {
-        javaPackage = packageName;
-    }
-
-    public int getPriority() {
-        return priority;
-    }
-
-    public void setPriority(final int priority) {
-        this.priority = priority;
-    }
-
-    @CheckForNull
-    public String getMessage() {
-        return message;
-    }
-
-    public void setMessage(final String message) {
-        this.message = message;
-    }
-
-    public int getBeginline() {
-        return beginline;
-    }
-
-    public void setBeginline(final int beginline) {
-        this.beginline = beginline;
-    }
-
-    public int getEndline() {
-        return endline;
-    }
-
-    public void setEndline(final int endline) {
-        this.endline = endline;
-    }
-
-    public int getEndcolumn() {
-        return endcolumn;
-    }
-
-    public void setEndcolumn(final int endcolumn) {
-        this.endcolumn = endcolumn;
-    }
-
-    public int getBegincolumn() {
-        return begincolumn;
-    }
-
-    public void setBegincolumn(final int begincolumn) {
-        this.begincolumn = begincolumn;
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/pmd/package-info.java b/src/main/java/edu/hm/hafner/analysis/parser/pmd/package-info.java
deleted file mode 100644
index f0b69d753..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/pmd/package-info.java
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- * PMD parser.
- */
-@DefaultAnnotation(NonNull.class)
-package edu.hm.hafner.analysis.parser.pmd;
-
-import edu.umd.cs.findbugs.annotations.DefaultAnnotation;
-import edu.umd.cs.findbugs.annotations.NonNull;
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/pvsstudio/AnalyzerType.java b/src/main/java/edu/hm/hafner/analysis/parser/pvsstudio/AnalyzerType.java
deleted file mode 100644
index 49dfb0856..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/pvsstudio/AnalyzerType.java
+++ /dev/null
@@ -1,252 +0,0 @@
-package edu.hm.hafner.analysis.parser.pvsstudio;
-
-import java.util.Arrays;
-import java.util.Optional;
-import java.util.stream.Stream;
-
-import edu.hm.hafner.analysis.util.IntegerParser;
-
-import static edu.hm.hafner.analysis.IssueParser.*;
-
-/**
- * The AnalyzerType for PVS-Studio static analyzer.
- *
- * @author PVS-Studio Team
- */
-final class AnalyzerType {
-    /**
-     * Diagnosis of 64-bit errors (Viva64, C++).
-     * https://pvs-studio.com/en/docs/warnings/#64CPP
-     */
-    private static final int VIVA64_CCPP_ERRORCODE_BEGIN = 100;
-    /**
-     * Diagnosis of 64-bit errors (Viva64, C++).
-     * https://pvs-studio.com/en/docs/warnings/#64CPP
-     */
-    private static final int VIVA64_CCPP_ERRORCODE_END = 499;
-    /**
-     * General Analysis (C++).
-     * https://pvs-studio.com/en/docs/warnings/#GeneralAnalysisCPP
-     */
-    private static final int GENERAL_CCPP_LOW_ERRORCODE_BEGIN = 500;
-    /**
-     * General Analysis (C++).
-     * https://pvs-studio.com/en/docs/warnings/#GeneralAnalysisCPP
-     */
-    private static final int GENERAL_CCPP_LOW_ERRORCODE_END = 799;
-    /**
-     * Diagnosis of micro-optimizations (C++).
-     * https://pvs-studio.com/en/docs/warnings/#MicroOptimizationsCPP
-     */
-    private static final int OPTIMIZATION_CCPP_ERRORCODE_BEGIN = 800;
-    /**
-     * Diagnosis of micro-optimizations (C++).
-     * https://pvs-studio.com/en/docs/warnings/#MicroOptimizationsCPP
-     */
-    private static final int OPTIMIZATION_CCPP_ERRORCODE_END = 999;
-    /**
-     * General Analysis (C++).
-     * https://pvs-studio.com/en/docs/warnings/
-     */
-    private static final int GENERAL_CCPP_HIGH_ERRORCODE_BEGIN = 1000;
-    /**
-     * General Analysis (C++).
-     * https://pvs-studio.com/en/docs/warnings/
-     */
-    private static final int GENERAL_CCPP_HIGH_ERRORCODE_END = 1999;
-    /**
-     * Customers Specific Requests (C++).
-     * https://pvs-studio.com/en/docs/warnings/#CustomersSpecificRequestsCPP
-     */
-    private static final int CUSTOMERSPECIFIC_CCPP_ERRORCODE_BEGIN = 2000;
-    /**
-     * Customers Specific Requests (C++).
-     * https://pvs-studio.com/en/docs/warnings/#CustomersSpecificRequestsCPP
-     */
-    private static final int CUSTOMERSPECIFIC_CCPP_ERRORCODE_END = 2499;
-    /**
-     * MISRA errors.
-     * https://pvs-studio.com/en/docs/warnings/#MISRA
-     */
-    private static final int MISRA_CCPP_ERRORCODE_BEGIN = 2500;
-    /**
-     * MISRA errors.
-     * https://pvs-studio.com/en/docs/warnings/#MISRA
-     */
-    private static final int MISRA_CCPP_ERRORCODE_END = 2999;
-    /**
-     * General Analysis (C#).
-     * https://pvs-studio.com/en/docs/warnings/#GeneralAnalysisCS
-     */
-    private static final int GENERAL_CS_ERRORCODE_BEGIN = 3000;
-    /**
-     * General Analysis (C#).
-     * https://pvs-studio.com/en/docs/warnings/#GeneralAnalysisCS
-     */
-    private static final int GENERAL_CS_ERRORCODE_END = 3499;
-    /**
-     * General Analysis (Java).
-     * https://pvs-studio.com/en/docs/warnings/#GeneralAnalysisJAVA
-     */
-    private static final int GENERAL_JAVA_ERRORCODE_BEGIN = 6000;
-    /**
-     * General Analysis (Java).
-     * https://pvs-studio.com/en/docs/warnings/#GeneralAnalysisJAVA
-     */
-    private static final int GENERAL_JAVA_ERRORCODE_END = 6999;
-
-    static final String VIVA_64_MESSAGE = "64-bit";
-    static final String GENERAL_MESSAGE = "General Analysis";
-    static final String OPTIMIZATION_MESSAGE = "Micro-optimization";
-    static final String CUSTOMER_SPECIFIC_MESSAGE = "Specific Requests";
-    static final String MISRA_MESSAGE = "MISRA";
-    static final String UNKNOWN_MESSAGE = "Unknown";
-
-    private AnalyzerType() {
-        // prevents instantiation
-    }
-
-    private static final AnalysisType[] ANALYSIS_TYPES = {new Viva64(), new GENERAL(),
-            new OPTIMIZATION(), new CustomerSpecific(), new MISRA()};
-
-    static AnalysisType fromErrorCode(final String errorCodeStr) {
-        if (equalsIgnoreCase(errorCodeStr, "External")) {
-            return new GENERAL();
-        }
-
-        // errorCodeStr format is Vnnn.
-        int errorCode = IntegerParser.parseInt(errorCodeStr.substring(1));
-
-        return Arrays.stream(ANALYSIS_TYPES)
-                .map(type -> type.create(errorCode))
-                .flatMap(o -> o.map(Stream::of).orElseGet(Stream::empty))
-                .findFirst()
-                .orElse(new UNKNOWN());
-    }
-
-    /**
-     * Viva64 AnalysisType.
-     */
-    static final class Viva64 implements AnalysisType {
-        @Override
-        public String getMessage() {
-            return VIVA_64_MESSAGE;
-        }
-
-        @Override
-        public Optional create(final int errorCode) {
-            if (errorCode >= VIVA64_CCPP_ERRORCODE_BEGIN && errorCode <= VIVA64_CCPP_ERRORCODE_END) {
-                return Optional.of(new Viva64());
-            }
-
-            return Optional.empty();
-        }
-    }
-
-    /**
-     * GENERAL AnalysisType.
-     */
-    static final class GENERAL implements AnalysisType {
-        @Override
-        public String getMessage() {
-            return GENERAL_MESSAGE;
-        }
-
-        @Override
-        public Optional create(final int errorCode) {
-            if (errorCode >= GENERAL_CCPP_LOW_ERRORCODE_BEGIN && errorCode <= GENERAL_CCPP_LOW_ERRORCODE_END) {
-                return Optional.of(new GENERAL());
-            }
-            else if (errorCode >= GENERAL_CCPP_HIGH_ERRORCODE_BEGIN && errorCode <= GENERAL_CCPP_HIGH_ERRORCODE_END) {
-                return Optional.of(new GENERAL());
-            }
-            else if (errorCode >= GENERAL_CS_ERRORCODE_BEGIN && errorCode <= GENERAL_CS_ERRORCODE_END) {
-                return Optional.of(new GENERAL());
-            }
-            else if (errorCode >= GENERAL_JAVA_ERRORCODE_BEGIN && errorCode <= GENERAL_JAVA_ERRORCODE_END) {
-                return Optional.of(new GENERAL());
-            }
-            else {
-                return Optional.empty();
-            }
-        }
-    }
-
-    /**
-     * OPTIMIZATION AnalysisType.
-     */
-    static final class OPTIMIZATION implements AnalysisType {
-        @Override
-        public String getMessage() {
-            return OPTIMIZATION_MESSAGE;
-        }
-
-        @Override
-        public Optional create(final int errorCode) {
-            if (errorCode >= OPTIMIZATION_CCPP_ERRORCODE_BEGIN && errorCode <= OPTIMIZATION_CCPP_ERRORCODE_END) {
-                return Optional.of(new OPTIMIZATION());
-            }
-
-            return Optional.empty();
-        }
-    }
-
-    /**
-     * CustomerSpecific AnalysisType.
-     */
-    static final class CustomerSpecific implements AnalysisType {
-        @Override
-        public String getMessage() {
-            return CUSTOMER_SPECIFIC_MESSAGE;
-        }
-
-        @Override
-        public Optional create(final int errorCode) {
-            if (errorCode >= CUSTOMERSPECIFIC_CCPP_ERRORCODE_BEGIN && errorCode <= CUSTOMERSPECIFIC_CCPP_ERRORCODE_END) {
-                return Optional.of(new CustomerSpecific());
-            }
-
-            return Optional.empty();
-        }
-    }
-
-    /**
-     * MISRA AnalysisType.
-     */
-    static final class MISRA implements AnalysisType {
-        @Override
-        public String getMessage() {
-            return MISRA_MESSAGE;
-        }
-
-        @Override
-        public Optional create(final int errorCode) {
-            if (errorCode >= MISRA_CCPP_ERRORCODE_BEGIN && errorCode <= MISRA_CCPP_ERRORCODE_END) {
-                return Optional.of(new MISRA());
-            }
-
-            return Optional.empty();
-        }
-    }
-
-    /**
-     * Unkonwn AnalysisType.
-     */
-    static final class UNKNOWN implements AnalysisType {
-        @Override
-        public String getMessage() {
-            return UNKNOWN_MESSAGE;
-        }
-
-        @Override
-        public Optional create(final int errorCode) {
-            return Optional.empty();
-        }
-    }
-
-    interface AnalysisType {
-        String getMessage();
-
-        Optional create(int errorCodeStr);
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/pvsstudio/PlogMessagesReader.java b/src/main/java/edu/hm/hafner/analysis/parser/pvsstudio/PlogMessagesReader.java
deleted file mode 100644
index 40c718e54..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/pvsstudio/PlogMessagesReader.java
+++ /dev/null
@@ -1,152 +0,0 @@
-package edu.hm.hafner.analysis.parser.pvsstudio;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Locale;
-import java.util.logging.Level;
-import java.util.logging.Logger;
-
-import org.apache.commons.lang3.StringUtils;
-import org.w3c.dom.Element;
-import org.w3c.dom.Node;
-import org.w3c.dom.NodeList;
-
-import edu.hm.hafner.analysis.ReaderFactory;
-import edu.hm.hafner.analysis.util.IntegerParser;
-
-import static edu.hm.hafner.analysis.IssueParser.*;
-
-/**
- * A parser for PVS-Studio Plog/XML files.
- *
- * @author PVS-Studio Team
- */
-class PlogMessagesReader {
-    private int failWarningsCount;
-    private int falseAlarmCount;
-
-    /**
-     * Getting list messages from the report.
-     *
-     * @param readerFactory
-     *         factory containing report file reader
-     *
-     * @return list plog messages
-     */
-    List getMessagesFromReport(final ReaderFactory readerFactory) {
-        var plogDoc = readerFactory.readDocument();
-        plogDoc.getDocumentElement().normalize();
-
-        var nList = plogDoc.getElementsByTagName("PVS-Studio_Analysis_Log");
-
-        List plogMessages = new ArrayList<>();
-        for (int nodeCount = 0; nodeCount < nList.getLength(); nodeCount++) {
-            var nNode = nList.item(nodeCount);
-
-            if (nNode.getNodeType() == Node.ELEMENT_NODE) {
-                processNode(plogMessages, nNode);
-            }
-        }
-
-        if ((plogMessages.size() + falseAlarmCount) == 0 && failWarningsCount > 0) {
-            Logger.getLogger(PvsStudioParser.class.getName()).log(Level.SEVERE, "No messages were parsed!");
-        }
-
-        return plogMessages;
-    }
-
-    private void processNode(final List plogMessages, final Node node) {
-        var eElement = (Element) node;
-
-        var nodeFalseAlarm = eElement.getElementsByTagName("FalseAlarm");
-        if (skipMessage(nodeFalseAlarm)) {
-            ++falseAlarmCount;
-            return;
-        }
-
-        var nodeFile = eElement.getElementsByTagName("File");
-
-        var msg = new PlogMessage();
-
-        if (nodeNotNull(nodeFile)) {
-            msg.file = nodeFile.item(0).getTextContent().trim();
-        }
-
-        if (msg.file.isEmpty()) {
-            ++failWarningsCount;
-            return;
-        }
-
-        var nodeErrorCode = eElement.getElementsByTagName("ErrorCode");
-
-        if (nodeNotNull(nodeErrorCode)) {
-            msg.errorCode = nodeErrorCode.item(0).getTextContent().trim();
-        }
-
-        if (!errorCodeIsValid(msg.errorCode)) {
-            ++failWarningsCount;
-            return;
-        }
-
-        msg.message = ""
-                + msg.errorCode + " "
-                + eElement.getElementsByTagName("Message").item(0).getTextContent();
-
-        msg.level = eElement.getElementsByTagName("Level").item(0).getTextContent();
-
-        msg.lineNumber = IntegerParser.parseInt(eElement.getElementsByTagName("Line").item(0).getTextContent());
-        if (msg.lineNumber <= 0) {
-            ++failWarningsCount;
-            return;
-        }
-
-        plogMessages.add(msg);
-    }
-
-    private boolean skipMessage(final NodeList elements) {
-        return elements != null && elements.item(0) != null
-                && equalsIgnoreCase(elements.item(0).getTextContent(), "true");
-    }
-
-    private boolean nodeNotNull(final NodeList elements) {
-        return elements != null && elements.item(0) != null && elements.item(0).getTextContent() != null;
-    }
-
-    private boolean errorCodeIsValid(final String errorCode) {
-        return !(errorCode.isEmpty() || errorCode.charAt(0) != 'V');
-    }
-
-    static class PlogMessage {
-        private String file = StringUtils.EMPTY;
-        private int lineNumber;
-        private String errorCode = StringUtils.EMPTY;
-        private String message = StringUtils.EMPTY;
-        private String level = StringUtils.EMPTY;
-
-        public String getHash() {
-            return errorCode + message + file + lineNumber;
-        }
-
-        @Override
-        public String toString() {
-            return message;
-        }
-
-        String getFilePath() {
-            return file;
-        }
-
-        public int getLine() {
-            return lineNumber;
-        }
-
-        public String getType() {
-            return errorCode;
-        }
-
-        public String getLevel() {
-            return level;
-        }
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/pvsstudio/PvsStudioParser.java b/src/main/java/edu/hm/hafner/analysis/parser/pvsstudio/PvsStudioParser.java
deleted file mode 100644
index c048e8885..000000000
--- a/src/main/java/edu/hm/hafner/analysis/parser/pvsstudio/PvsStudioParser.java
+++ /dev/null
@@ -1,64 +0,0 @@
-package edu.hm.hafner.analysis.parser.pvsstudio;
-
-import java.io.Serial;
-
-import edu.hm.hafner.analysis.IssueBuilder;
-import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.ParsingCanceledException;
-import edu.hm.hafner.analysis.ParsingException;
-import edu.hm.hafner.analysis.ReaderFactory;
-import edu.hm.hafner.analysis.Report;
-import edu.hm.hafner.analysis.Severity;
-import edu.hm.hafner.analysis.parser.pvsstudio.PlogMessagesReader.PlogMessage;
-
-/**
- * A parser for the PVS-Studio static analyzer.
- *
- * @author PVS-Studio Team
- */
-public class PvsStudioParser extends IssueParser {
-    @Serial
-    private static final long serialVersionUID = -7777775729854832128L;
-    private static final String SEVERITY_HIGH = "1";
-    private static final String SEVERITY_NORMAL = "2";
-    private static final String SEVERITY_LOW = "3";
-
-    private static Severity getSeverity(final String level) {
-        if (SEVERITY_HIGH.equals(level)) {
-            return Severity.WARNING_HIGH;
-        }
-        else if (SEVERITY_NORMAL.equals(level)) {
-            return Severity.WARNING_NORMAL;
-        }
-        else if (SEVERITY_LOW.equals(level)) {
-            return Severity.WARNING_LOW;
-        }
-        else {
-            return Severity.ERROR;
-        }
-    }
-
-    @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingException, ParsingCanceledException {
-        try (var issueBuilder = new IssueBuilder()) {
-            var report = new Report();
-            var parser = new PlogMessagesReader();
-
-            for (PlogMessage plogMessage : parser.getMessagesFromReport(readerFactory)) {
-                issueBuilder.setFileName(plogMessage.getFilePath());
-
-                issueBuilder.setSeverity(getSeverity(plogMessage.getLevel()));
-                issueBuilder.setMessage(plogMessage.toString());
-                issueBuilder.setCategory(plogMessage.getType());
-
-                issueBuilder.setType(AnalyzerType.fromErrorCode(plogMessage.getType()).getMessage());
-
-                issueBuilder.setLineStart(plogMessage.getLine());
-
-                report.add(issueBuilder.buildAndClean());
-            }
-
-            return report;
-        }
-    }
-}
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/violations/AbstractViolationAdapter.java b/src/main/java/edu/hm/hafner/analysis/parser/violations/AbstractViolationAdapter.java
index 1e57b2984..4b362f9c1 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/violations/AbstractViolationAdapter.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/violations/AbstractViolationAdapter.java
@@ -33,7 +33,7 @@ public abstract class AbstractViolationAdapter extends IssueParser {
 
     @SuppressWarnings({"illegalcatch", "OverlyBroadCatchBlock"})
     @Override
-    public Report parse(final ReaderFactory readerFactory)
+    public Report parseReport(final ReaderFactory readerFactory)
             throws ParsingCanceledException, ParsingException {
         try {
             var parser = createParser();
diff --git a/src/main/java/edu/hm/hafner/analysis/parser/violations/JUnitAdapter.java b/src/main/java/edu/hm/hafner/analysis/parser/violations/JUnitAdapter.java
index 350dc209e..2c48cc809 100644
--- a/src/main/java/edu/hm/hafner/analysis/parser/violations/JUnitAdapter.java
+++ b/src/main/java/edu/hm/hafner/analysis/parser/violations/JUnitAdapter.java
@@ -34,8 +34,8 @@ JUnitParser createParser() {
     }
 
     @Override
-    public Report parse(final ReaderFactory readerFactory) throws ParsingCanceledException, ParsingException {
-        var report = super.parse(readerFactory);
+    public Report parseReport(final ReaderFactory readerFactory) throws ParsingCanceledException, ParsingException {
+        var report = super.parseReport(readerFactory);
         int total = count(readerFactory, " asList(final IssueParser... parser)
      * aggregated.
      */
     private static class CompositeParser extends IssueParser {
+        @Serial
         private static final long serialVersionUID = -2319098057308618997L;
 
         private final List parsers = new ArrayList<>();
@@ -77,10 +79,13 @@ private static class CompositeParser extends IssueParser {
         }
 
         @Override
-        public Report parse(final ReaderFactory readerFactory) {
-            var aggregated = new Report();
+        protected Report parseReport(final ReaderFactory readerFactory) {
+            var aggregated = new Report(getId(), getName(), readerFactory.getFileName(), getType());
             for (IssueParser parser : parsers) {
                 if (parser.accepts(readerFactory)) {
+                    parser.setId(getId());
+                    parser.setName(getName());
+                    parser.setType(getType());
                     aggregated.addAll(parser.parse(readerFactory));
                 }
             }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/CoolfluxChessccDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/CoolfluxChessccDescriptor.java
index dc55fecfb..07d67889b 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/CoolfluxChessccDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/CoolfluxChessccDescriptor.java
@@ -17,7 +17,7 @@ class CoolfluxChessccDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new CoolfluxChessccParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/CoverityDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/CoverityDescriptor.java
index 1570fbd90..b98ee4f22 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/CoverityDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/CoverityDescriptor.java
@@ -17,7 +17,7 @@ class CoverityDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new CoverityAdapter();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/CpdDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/CpdDescriptor.java
index fbd9c4e2e..fe315e564 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/CpdDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/CpdDescriptor.java
@@ -1,7 +1,7 @@
 package edu.hm.hafner.analysis.registry;
 
 import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.parser.dry.cpd.CpdParser;
+import edu.hm.hafner.analysis.parser.CpdParser;
 
 /**
  * A descriptor for the CPD parser.
@@ -17,7 +17,7 @@ class CpdDescriptor extends DryDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new CpdParser(getHighThreshold(options), getNormalThreshold(options));
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/CppCheckDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/CppCheckDescriptor.java
index dff3440db..f1cd158a9 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/CppCheckDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/CppCheckDescriptor.java
@@ -17,7 +17,7 @@ class CppCheckDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new CppCheckAdapter();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/CppLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/CppLintDescriptor.java
index 3f79fe273..435494a43 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/CppLintDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/CppLintDescriptor.java
@@ -17,7 +17,7 @@ class CppLintDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new CppLintParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/CrossCoreEmbeddedStudioDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/CrossCoreEmbeddedStudioDescriptor.java
index 9b60c95a3..51ebd0774 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/CrossCoreEmbeddedStudioDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/CrossCoreEmbeddedStudioDescriptor.java
@@ -15,7 +15,7 @@ class CrossCoreEmbeddedStudioDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new CrossCoreEmbeddedStudioParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/CssLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/CssLintDescriptor.java
index 25299fb69..131019db9 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/CssLintDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/CssLintDescriptor.java
@@ -17,7 +17,7 @@ class CssLintDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new LintParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/DScannerDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/DScannerDescriptor.java
index e03104b3a..a21e3f347 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/DScannerDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/DScannerDescriptor.java
@@ -17,7 +17,7 @@ class DScannerDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new DScannerParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/DartAnalyzeDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/DartAnalyzeDescriptor.java
index 3b86780d8..71ab0ac53 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/DartAnalyzeDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/DartAnalyzeDescriptor.java
@@ -17,7 +17,7 @@ class DartAnalyzeDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new DartAnalyzeParserAdapter();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/DetektDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/DetektDescriptor.java
index ff8fa7346..c290b9178 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/DetektDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/DetektDescriptor.java
@@ -1,7 +1,7 @@
 package edu.hm.hafner.analysis.registry;
 
 import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.parser.checkstyle.CheckStyleParser;
+import edu.hm.hafner.analysis.parser.CheckStyleParser;
 
 /**
  * A descriptor for Detekt. Delegates to {@link CheckStyleParser}.
@@ -17,7 +17,7 @@ class DetektDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new CheckStyleParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/DiabCDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/DiabCDescriptor.java
index a484e1048..2028b81f3 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/DiabCDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/DiabCDescriptor.java
@@ -17,7 +17,7 @@ class DiabCDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new DiabCParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/DocFxDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/DocFxDescriptor.java
index 4a0d85cff..77ff46ae2 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/DocFxDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/DocFxDescriptor.java
@@ -17,7 +17,7 @@ class DocFxDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new DocFxAdapter();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/DockerLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/DockerLintDescriptor.java
index 1473b5c40..fb7d3976d 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/DockerLintDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/DockerLintDescriptor.java
@@ -17,7 +17,7 @@ class DockerLintDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new DockerLintParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/DoxygenDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/DoxygenDescriptor.java
index 77684b0fa..8fd7f604a 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/DoxygenDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/DoxygenDescriptor.java
@@ -17,7 +17,7 @@ class DoxygenDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new DoxygenParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/DrMemoryDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/DrMemoryDescriptor.java
index f4a255e23..97ed51723 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/DrMemoryDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/DrMemoryDescriptor.java
@@ -17,7 +17,7 @@ class DrMemoryDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new DrMemoryParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/DryDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/DryDescriptor.java
index 02e08ae46..ae585d479 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/DryDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/DryDescriptor.java
@@ -6,6 +6,7 @@
 
 import edu.hm.hafner.analysis.DuplicationGroup;
 import edu.hm.hafner.analysis.Issue;
+import edu.hm.hafner.analysis.Report.IssueType;
 import edu.umd.cs.findbugs.annotations.CheckForNull;
 
 import j2html.tags.UnescapedText;
@@ -69,7 +70,7 @@ public String getDescription(final Issue issue) {
     }
 
     @Override
-    public Type getType() {
-        return Type.DUPLICATION;
+    public IssueType getType() {
+        return IssueType.DUPLICATION;
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/DupfinderDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/DupfinderDescriptor.java
index 24c658c2f..d18da341a 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/DupfinderDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/DupfinderDescriptor.java
@@ -1,7 +1,7 @@
 package edu.hm.hafner.analysis.registry;
 
 import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.parser.dry.dupfinder.DupFinderParser;
+import edu.hm.hafner.analysis.parser.DupFinderParser;
 
 /**
  * A descriptor for Resharper DupFinder.
@@ -17,7 +17,7 @@ class DupfinderDescriptor extends DryDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new DupFinderParser(getHighThreshold(options), getNormalThreshold(options));
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/EclipseDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/EclipseDescriptor.java
index 465b95d3e..1270efb31 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/EclipseDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/EclipseDescriptor.java
@@ -5,7 +5,7 @@
 import edu.hm.hafner.analysis.IssueParser;
 import edu.hm.hafner.analysis.parser.EclipseMavenParser;
 import edu.hm.hafner.analysis.parser.EclipseParser;
-import edu.hm.hafner.analysis.parser.EclipseXMLParser;
+import edu.hm.hafner.analysis.parser.EclipseXmlParser;
 
 /**
  * A descriptor for the Eclipse compiler (text format).
@@ -22,7 +22,7 @@ class EclipseDescriptor extends CompositeParserDescriptor {
 
     @Override
     protected Collection createParsers() {
-        return asList(new EclipseParser(), new EclipseMavenParser(), new EclipseXMLParser());
+        return asList(new EclipseParser(), new EclipseMavenParser(), new EclipseXmlParser());
     }
 
     @Override
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/EmbeddedEngineerDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/EmbeddedEngineerDescriptor.java
index 2ae56ad3b..d352fb803 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/EmbeddedEngineerDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/EmbeddedEngineerDescriptor.java
@@ -17,7 +17,7 @@ class EmbeddedEngineerDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new EmbeddedEngineerParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/ErlcDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/ErlcDescriptor.java
index 6af9efb7f..5cf1258b4 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/ErlcDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/ErlcDescriptor.java
@@ -17,7 +17,7 @@ class ErlcDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new ErlcParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/ErrorProneDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/ErrorProneDescriptor.java
index 6f3858bc6..22e4161d2 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/ErrorProneDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/ErrorProneDescriptor.java
@@ -3,6 +3,7 @@
 import java.util.Collection;
 
 import edu.hm.hafner.analysis.IssueParser;
+import edu.hm.hafner.analysis.Report.IssueType;
 import edu.hm.hafner.analysis.parser.ErrorProneParser;
 import edu.hm.hafner.analysis.parser.GradleErrorProneParser;
 
@@ -30,7 +31,7 @@ public String getUrl() {
     }
 
     @Override
-    public Type getType() {
-        return Type.BUG;
+    public IssueType getType() {
+        return IssueType.BUG;
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/EsLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/EsLintDescriptor.java
index 19a44255a..eac8e979f 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/EsLintDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/EsLintDescriptor.java
@@ -1,7 +1,7 @@
 package edu.hm.hafner.analysis.registry;
 
 import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.parser.checkstyle.CheckStyleParser;
+import edu.hm.hafner.analysis.parser.CheckStyleParser;
 
 /**
  * A descriptor for ESLint. Delegates to {@link CheckStyleParser}.
@@ -17,7 +17,7 @@ class EsLintDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new CheckStyleParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/FindBugsDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/FindBugsDescriptor.java
index e771e89b9..5dc234e67 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/FindBugsDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/FindBugsDescriptor.java
@@ -2,9 +2,10 @@
 
 import edu.hm.hafner.analysis.Issue;
 import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.parser.findbugs.FindBugsMessages;
-import edu.hm.hafner.analysis.parser.findbugs.FindBugsParser;
-import edu.hm.hafner.analysis.parser.findbugs.FindBugsParser.PriorityProperty;
+import edu.hm.hafner.analysis.Report.IssueType;
+import edu.hm.hafner.analysis.parser.FindBugsMessages;
+import edu.hm.hafner.analysis.parser.FindBugsParser;
+import edu.hm.hafner.analysis.parser.FindBugsParser.PriorityProperty;
 import edu.hm.hafner.analysis.util.Deferred;
 
 /**
@@ -30,7 +31,7 @@ class FindBugsDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         for (Option option : options) {
             if (PRIORITY_OPTION_KEY.equals(option.getKey())
                     && PriorityProperty.CONFIDENCE.name().equals(option.getValue())) {
@@ -51,7 +52,7 @@ public String getDescription(final Issue issue) {
     }
 
     @Override
-    public Type getType() {
-        return Type.BUG;
+    public IssueType getType() {
+        return IssueType.BUG;
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/Flake8Descriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/Flake8Descriptor.java
index 17842eb86..ddc1eb140 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/Flake8Descriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/Flake8Descriptor.java
@@ -17,7 +17,7 @@ class Flake8Descriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new Flake8Adapter();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/FlawfinderDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/FlawfinderDescriptor.java
index 2ec09e581..0bf19206b 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/FlawfinderDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/FlawfinderDescriptor.java
@@ -1,6 +1,7 @@
 package edu.hm.hafner.analysis.registry;
 
 import edu.hm.hafner.analysis.IssueParser;
+import edu.hm.hafner.analysis.Report.IssueType;
 import edu.hm.hafner.analysis.parser.FlawfinderParser;
 
 /**
@@ -17,7 +18,7 @@ class FlawfinderDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new FlawfinderParser();
     }
 
@@ -32,7 +33,7 @@ public String getUrl() {
     }
 
     @Override
-    public Type getType() {
-        return Type.VULNERABILITY;
+    public IssueType getType() {
+        return IssueType.VULNERABILITY;
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/FlexSdkDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/FlexSdkDescriptor.java
index 7aeb5b4b0..4e23ef376 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/FlexSdkDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/FlexSdkDescriptor.java
@@ -17,7 +17,7 @@ class FlexSdkDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new FlexSdkParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/FlowDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/FlowDescriptor.java
index 43332329f..7e6857d4b 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/FlowDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/FlowDescriptor.java
@@ -17,7 +17,7 @@ class FlowDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new FlowParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/FoodCriticDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/FoodCriticDescriptor.java
index b1e888791..e946df53f 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/FoodCriticDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/FoodCriticDescriptor.java
@@ -17,7 +17,7 @@ class FoodCriticDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new FoodcriticParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/FxcopDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/FxcopDescriptor.java
index e5fb44b2b..9827e0f89 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/FxcopDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/FxcopDescriptor.java
@@ -1,7 +1,7 @@
 package edu.hm.hafner.analysis.registry;
 
 import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.parser.fxcop.FxCopParser;
+import edu.hm.hafner.analysis.parser.FxCopParser;
 
 /**
  * A descriptor for FxCop.
@@ -17,7 +17,7 @@ class FxcopDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new FxCopParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/GccDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/GccDescriptor.java
index aba7fd43f..5164f686d 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/GccDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/GccDescriptor.java
@@ -17,7 +17,7 @@ class GccDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new GccParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/GendarmeDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/GendarmeDescriptor.java
index fd9be11dc..bf6d7d36b 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/GendarmeDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/GendarmeDescriptor.java
@@ -1,7 +1,7 @@
 package edu.hm.hafner.analysis.registry;
 
 import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.parser.gendarme.GendarmeParser;
+import edu.hm.hafner.analysis.parser.GendarmeParser;
 
 /**
  * A descriptor for Gendarme violations.
@@ -17,7 +17,7 @@ class GendarmeDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new GendarmeParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/GhsMultiDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/GhsMultiDescriptor.java
index cb66ce4e4..a682e372d 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/GhsMultiDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/GhsMultiDescriptor.java
@@ -17,7 +17,7 @@ class GhsMultiDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new GhsMultiParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/GnatDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/GnatDescriptor.java
index 875311aff..15e133df9 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/GnatDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/GnatDescriptor.java
@@ -17,7 +17,7 @@ class GnatDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new GnatParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/GnuFortranDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/GnuFortranDescriptor.java
index 3051f3e5f..a457397a9 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/GnuFortranDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/GnuFortranDescriptor.java
@@ -17,7 +17,7 @@ class GnuFortranDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new GnuFortranParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/GoLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/GoLintDescriptor.java
index 07962d5e9..58bf8a7ac 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/GoLintDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/GoLintDescriptor.java
@@ -17,7 +17,7 @@ class GoLintDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new GoLintParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/GoVetDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/GoVetDescriptor.java
index 2e6838e02..2f8c1013c 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/GoVetDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/GoVetDescriptor.java
@@ -17,7 +17,7 @@ class GoVetDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new GoVetParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/GrypeDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/GrypeDescriptor.java
index 0008212c8..95442e962 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/GrypeDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/GrypeDescriptor.java
@@ -15,7 +15,7 @@ class GrypeDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new GrypeParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/HadoLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/HadoLintDescriptor.java
index d1c373539..f80b008a6 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/HadoLintDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/HadoLintDescriptor.java
@@ -17,7 +17,7 @@ class HadoLintDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new HadoLintParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/IarCstatDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/IarCstatDescriptor.java
index 3db63801f..f52973d1f 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/IarCstatDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/IarCstatDescriptor.java
@@ -17,7 +17,7 @@ class IarCstatDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new IarCstatParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/IarDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/IarDescriptor.java
index c1d6abc8f..cfb63d30f 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/IarDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/IarDescriptor.java
@@ -17,7 +17,7 @@ class IarDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new IarParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/IbLinterDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/IbLinterDescriptor.java
index d5f7ead6b..33dde7f45 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/IbLinterDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/IbLinterDescriptor.java
@@ -1,7 +1,7 @@
 package edu.hm.hafner.analysis.registry;
 
 import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.parser.checkstyle.CheckStyleParser;
+import edu.hm.hafner.analysis.parser.CheckStyleParser;
 
 /**
  * A descriptor for IbLinter. Delegates to {@link CheckStyleParser}.
@@ -17,7 +17,7 @@ class IbLinterDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new CheckStyleParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/IdeaInspectionDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/IdeaInspectionDescriptor.java
index 6ef4eb588..e55eb01e0 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/IdeaInspectionDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/IdeaInspectionDescriptor.java
@@ -17,7 +17,7 @@ class IdeaInspectionDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new IdeaInspectionParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/InferDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/InferDescriptor.java
index 2e166e23a..6318c675b 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/InferDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/InferDescriptor.java
@@ -1,7 +1,7 @@
 package edu.hm.hafner.analysis.registry;
 
 import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.parser.pmd.PmdParser;
+import edu.hm.hafner.analysis.parser.PmdParser;
 
 /**
  * A descriptor for Infer. Delegates to {@link PmdParser}.
@@ -17,7 +17,7 @@ class InferDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new PmdParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/IntelDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/IntelDescriptor.java
index 2583a2e55..85faf1c1e 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/IntelDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/IntelDescriptor.java
@@ -17,7 +17,7 @@ class IntelDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new IntelParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/InvalidsDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/InvalidsDescriptor.java
index 6dd0a303a..dc48ec109 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/InvalidsDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/InvalidsDescriptor.java
@@ -17,7 +17,7 @@ class InvalidsDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new InvalidsParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/JUnitDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/JUnitDescriptor.java
index e5f1e1095..5d05733a3 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/JUnitDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/JUnitDescriptor.java
@@ -17,7 +17,7 @@ class JUnitDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new JUnitAdapter();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/JavaDocDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/JavaDocDescriptor.java
index b9292a3f4..5c8ffb26d 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/JavaDocDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/JavaDocDescriptor.java
@@ -17,7 +17,7 @@ class JavaDocDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new JavaDocParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/JcreportDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/JcreportDescriptor.java
index 4f6f05932..529da9f9a 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/JcreportDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/JcreportDescriptor.java
@@ -1,7 +1,7 @@
 package edu.hm.hafner.analysis.registry;
 
 import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.parser.jcreport.JcReportParser;
+import edu.hm.hafner.analysis.parser.JcReportParser;
 
 /**
  * A descriptor for the JcReport compiler.
@@ -17,7 +17,7 @@ class JcreportDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new JcReportParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/JsHintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/JsHintDescriptor.java
index d69addbb3..c0f1dfdb4 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/JsHintDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/JsHintDescriptor.java
@@ -17,7 +17,7 @@ class JsHintDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new JsHintAdapter();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/JsLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/JsLintDescriptor.java
index 53bc0c9a7..f63f7cfca 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/JsLintDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/JsLintDescriptor.java
@@ -17,7 +17,7 @@ class JsLintDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new LintParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/KlocWorkDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/KlocWorkDescriptor.java
index 9a9275b6d..fb9d24796 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/KlocWorkDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/KlocWorkDescriptor.java
@@ -17,7 +17,7 @@ class KlocWorkDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new KlocWorkAdapter();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/KotlinDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/KotlinDescriptor.java
index 6d5321344..c7544779c 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/KotlinDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/KotlinDescriptor.java
@@ -17,7 +17,7 @@ class KotlinDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new JavacParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/KtLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/KtLintDescriptor.java
index 2b0b78058..cd93eb22e 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/KtLintDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/KtLintDescriptor.java
@@ -1,7 +1,7 @@
 package edu.hm.hafner.analysis.registry;
 
 import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.parser.checkstyle.CheckStyleParser;
+import edu.hm.hafner.analysis.parser.CheckStyleParser;
 
 /**
  * A descriptor for ktlint. Delegates to {@link CheckStyleParser}.
@@ -17,7 +17,7 @@ class KtLintDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new CheckStyleParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/MavenConsoleDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/MavenConsoleDescriptor.java
index 3d3204b5a..7eb998398 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/MavenConsoleDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/MavenConsoleDescriptor.java
@@ -17,7 +17,7 @@ class MavenConsoleDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new MavenConsoleParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/MentorGraphicsDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/MentorGraphicsDescriptor.java
index fe64b7e95..c20fba07a 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/MentorGraphicsDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/MentorGraphicsDescriptor.java
@@ -17,7 +17,7 @@ class MentorGraphicsDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new MentorParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/MsBuildDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/MsBuildDescriptor.java
index 321e95646..ca8b5072f 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/MsBuildDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/MsBuildDescriptor.java
@@ -17,7 +17,7 @@ class MsBuildDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new MsBuildParser();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/MyPyDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/MyPyDescriptor.java
index 99eed2755..eb9f15505 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/MyPyDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/MyPyDescriptor.java
@@ -17,7 +17,7 @@ class MyPyDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new MyPyAdapter();
     }
 
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/NagFortranDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/NagFortranDescriptor.java
index 9cb668bb5..93847f53d 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/NagFortranDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/NagFortranDescriptor.java
@@ -17,7 +17,7 @@ class NagFortranDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new NagFortranParser();
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/OELintAdvDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/OeLintAdvDescriptor.java
similarity index 62%
rename from src/main/java/edu/hm/hafner/analysis/registry/OELintAdvDescriptor.java
rename to src/main/java/edu/hm/hafner/analysis/registry/OeLintAdvDescriptor.java
index 0ad66cd5a..ddff4e75b 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/OELintAdvDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/OeLintAdvDescriptor.java
@@ -1,22 +1,22 @@
 package edu.hm.hafner.analysis.registry;
 
 import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.parser.OELintAdvParser;
+import edu.hm.hafner.analysis.parser.OeLintAdvParser;
 
 /**
  * Descriptor for oelint-adv.
  */
-class OELintAdvDescriptor extends ParserDescriptor {
+class OeLintAdvDescriptor extends ParserDescriptor {
     private static final String ID = "oelint-adv";
     private static final String NAME = ID;
 
-    OELintAdvDescriptor() {
+    OeLintAdvDescriptor() {
         super(ID, NAME);
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
-        return new OELintAdvParser();
+    public IssueParser create(final Option... options) {
+        return new OeLintAdvParser();
     }
 
     @Override
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/OtDockerLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/OtDockerLintDescriptor.java
index 51493adc1..d470fed01 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/OtDockerLintDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/OtDockerLintDescriptor.java
@@ -1,7 +1,7 @@
 package edu.hm.hafner.analysis.registry;
 
 import edu.hm.hafner.analysis.IssueParser;
-import edu.hm.hafner.analysis.parser.OTDockerLintParser;
+import edu.hm.hafner.analysis.parser.OtDockerLintParser;
 
 /**
  * A descriptor for {@code ot-docker-lint} json report.
@@ -17,8 +17,8 @@ class OtDockerLintDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
-        return new OTDockerLintParser();
+    public IssueParser create(final Option... options) {
+        return new OtDockerLintParser();
     }
 
     @Override
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/OwaspDependencyCheckDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/OwaspDependencyCheckDescriptor.java
index 364ff02d1..3307906ff 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/OwaspDependencyCheckDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/OwaspDependencyCheckDescriptor.java
@@ -1,6 +1,7 @@
 package edu.hm.hafner.analysis.registry;
 
 import edu.hm.hafner.analysis.IssueParser;
+import edu.hm.hafner.analysis.Report.IssueType;
 import edu.hm.hafner.analysis.parser.OwaspDependencyCheckParser;
 
 /**
@@ -15,7 +16,7 @@ class OwaspDependencyCheckDescriptor extends ParserDescriptor {
     }
 
     @Override
-    public IssueParser createParser(final Option... options) {
+    public IssueParser create(final Option... options) {
         return new OwaspDependencyCheckParser();
     }
 
@@ -35,7 +36,7 @@ public String getIconUrl() {
     }
 
     @Override
-    public Type getType() {
-        return Type.VULNERABILITY;
+    public IssueType getType() {
+        return IssueType.VULNERABILITY;
     }
 }
diff --git a/src/main/java/edu/hm/hafner/analysis/registry/ParserDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/ParserDescriptor.java
index 40efb8989..0d6285a6c 100644
--- a/src/main/java/edu/hm/hafner/analysis/registry/ParserDescriptor.java
+++ b/src/main/java/edu/hm/hafner/analysis/registry/ParserDescriptor.java
@@ -1,14 +1,16 @@
 package edu.hm.hafner.analysis.registry;
 
+import java.io.Serial;
 import java.util.AbstractMap.SimpleImmutableEntry;
 
 import org.apache.commons.lang3.StringUtils;
 
 import edu.hm.hafner.analysis.Issue;
 import edu.hm.hafner.analysis.IssueParser;
+import edu.hm.hafner.analysis.Report.IssueType;
 
 /**
- * Interface to describe all descriptors.
+ * Parent class for all descriptors.
  *
  * @author Lorenz Munsch
  */
@@ -34,7 +36,7 @@ public abstract class ParserDescriptor {
      *
      * @return the technical id of the parser
      */
-    public String getId() {
+    public final String getId() {
         return id;
     }
 
@@ -43,19 +45,39 @@ public String getId() {
      *
      * @return the human-readable name
      */
-    public String getName() {
+    public final String getName() {
         return name;
     }
 
     /**
-     * Returns the type of the parser. The type is used to customize parsers in the UI.
-     * This default implementation returns {@link Type#WARNING}.
+     * Returns the type of the parser. The type is used to categorize parsers.
+     *
+     * 

+ * This default implementation returns * {@link IssueType#WARNING}. * Override this method if your parser is of a different type. + *

* * @return the type of the parser */ - public Type getType() { - return Type.WARNING; + public IssueType getType() { + return IssueType.WARNING; + } + + /** + * Creates a new {@link IssueParser} instance. + * + * @param options + * options to configure the parser - may customize the new parser instance (if supported by the selected + * tool) + * + * @return the parser + */ + public final IssueParser createParser(final Option... options) { + var parser = create(options); + parser.setId(getId()); + parser.setName(getName()); + parser.setType(getType()); + return parser; } /** @@ -67,7 +89,7 @@ public Type getType() { * * @return the parser */ - public abstract IssueParser createParser(Option... options); + protected abstract IssueParser create(Option... options); /** * Returns the default filename pattern for this tool. Override if your parser typically works on a specific file. @@ -140,24 +162,11 @@ public String getDescription(final Issue issue) { return issue.getDescription(); } - /** - * Returns the type of the parser. The type is used to customize parsers in the UI. - */ - public enum Type { - /** A parser that scans the output of a build tool to find warnings. */ - WARNING, - /** A parser that scans the output of a build tool to find bugs. */ - BUG, - /** A parser that scans the output of a build tool to find vulnerabilities. */ - VULNERABILITY, - /** A parser that scans the output of a build tool to find vulnerabilities. */ - DUPLICATION - } - /** * A parser configuration option. Basically an immutable key and value pair. */ public static class Option extends SimpleImmutableEntry { + @Serial private static final long serialVersionUID = 7376822311558465523L; /** diff --git a/src/main/java/edu/hm/hafner/analysis/registry/ParserRegistry.java b/src/main/java/edu/hm/hafner/analysis/registry/ParserRegistry.java index 57bdea582..0a068709c 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/ParserRegistry.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/ParserRegistry.java @@ -122,7 +122,7 @@ public class ParserRegistry { new MyPyDescriptor(), new NagFortranDescriptor(), new NativeFormatDescriptor(), - new OELintAdvDescriptor(), + new OeLintAdvDescriptor(), new OtDockerLintDescriptor(), new OwaspDependencyCheckDescriptor(), new PcLintDescriptor(), @@ -167,6 +167,7 @@ public class ParserRegistry { new TnsdlDescriptor(), new TrivyDescriptor(), new TsLintDescriptor(), + new ValeDescriptor(), new ValgrindDescriptor(), new VeraCodePipelineScannerDescriptor(), new XlcDescriptor(), diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PcLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PcLintDescriptor.java index dd3880af6..b151300f1 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PcLintDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PcLintDescriptor.java @@ -17,7 +17,7 @@ class PcLintDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new MsBuildParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/Pep8Descriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/Pep8Descriptor.java index 7d0fe5f78..b255f99ba 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/Pep8Descriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/Pep8Descriptor.java @@ -17,7 +17,7 @@ class Pep8Descriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new Pep8Parser(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PerforceDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PerforceDescriptor.java index 7e3a9622a..475acbbe1 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PerforceDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PerforceDescriptor.java @@ -17,7 +17,7 @@ class PerforceDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new P4Parser(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PerlCriticDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PerlCriticDescriptor.java index 9426f8fe5..ce1969d91 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PerlCriticDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PerlCriticDescriptor.java @@ -17,7 +17,7 @@ class PerlCriticDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new PerlCriticParser(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PhpCodeSnifferDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PhpCodeSnifferDescriptor.java index 4f4beca32..2c95f49af 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PhpCodeSnifferDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PhpCodeSnifferDescriptor.java @@ -1,7 +1,7 @@ package edu.hm.hafner.analysis.registry; import edu.hm.hafner.analysis.IssueParser; -import edu.hm.hafner.analysis.parser.checkstyle.CheckStyleParser; +import edu.hm.hafner.analysis.parser.CheckStyleParser; /** * A descriptor for PHP_CodeSniffer. Delegates to {@link CheckStyleParser}. @@ -17,7 +17,7 @@ class PhpCodeSnifferDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new CheckStyleParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PhpDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PhpDescriptor.java index 5765c52b3..5055a6c8e 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PhpDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PhpDescriptor.java @@ -17,7 +17,7 @@ class PhpDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new PhpParser(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PhpStanDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PhpStanDescriptor.java index 9e99cb760..cf0d38c2b 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PhpStanDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PhpStanDescriptor.java @@ -1,7 +1,7 @@ package edu.hm.hafner.analysis.registry; import edu.hm.hafner.analysis.IssueParser; -import edu.hm.hafner.analysis.parser.checkstyle.CheckStyleParser; +import edu.hm.hafner.analysis.parser.CheckStyleParser; /** * A descriptor for PHPStan. Delegates to {@link CheckStyleParser}. @@ -17,7 +17,7 @@ class PhpStanDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new CheckStyleParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PitDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PitDescriptor.java index ce4bfdaf6..9ba11959d 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PitDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PitDescriptor.java @@ -17,7 +17,7 @@ class PitDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new PitAdapter(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PmdDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PmdDescriptor.java index c78c60dc0..6593e0fbe 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PmdDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PmdDescriptor.java @@ -2,8 +2,8 @@ import edu.hm.hafner.analysis.Issue; import edu.hm.hafner.analysis.IssueParser; -import edu.hm.hafner.analysis.parser.pmd.PmdMessages; -import edu.hm.hafner.analysis.parser.pmd.PmdParser; +import edu.hm.hafner.analysis.parser.PmdMessages; +import edu.hm.hafner.analysis.parser.PmdParser; import edu.hm.hafner.analysis.util.Deferred; /** @@ -22,7 +22,7 @@ class PmdDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new PmdParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PnpmAuditDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PnpmAuditDescriptor.java index 1984bf270..6acc6af7a 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PnpmAuditDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PnpmAuditDescriptor.java @@ -1,6 +1,7 @@ package edu.hm.hafner.analysis.registry; import edu.hm.hafner.analysis.IssueParser; +import edu.hm.hafner.analysis.Report.IssueType; import edu.hm.hafner.analysis.parser.PnpmAuditParser; import static j2html.TagCreator.*; @@ -19,7 +20,7 @@ class PnpmAuditDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new PnpmAuditParser(); } @@ -43,7 +44,7 @@ public String getIconUrl() { } @Override - public Type getType() { - return Type.VULNERABILITY; + public IssueType getType() { + return IssueType.VULNERABILITY; } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PolyspaceDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PolyspaceDescriptor.java index 999d92c11..0d7d1abee 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PolyspaceDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PolyspaceDescriptor.java @@ -18,7 +18,7 @@ class PolyspaceDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new PolyspaceParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PreFastDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PreFastDescriptor.java index 74a46ede6..6b5b72d48 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PreFastDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PreFastDescriptor.java @@ -17,7 +17,7 @@ class PreFastDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new PreFastParser(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PuppetLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PuppetLintDescriptor.java index dcdf7232d..dbfcfd950 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PuppetLintDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PuppetLintDescriptor.java @@ -19,7 +19,7 @@ class PuppetLintDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new PuppetLintParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PvsStudioDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PvsStudioDescriptor.java index 8c18d7d57..712e2cac5 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PvsStudioDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PvsStudioDescriptor.java @@ -1,7 +1,7 @@ package edu.hm.hafner.analysis.registry; import edu.hm.hafner.analysis.IssueParser; -import edu.hm.hafner.analysis.parser.pvsstudio.PvsStudioParser; +import edu.hm.hafner.analysis.parser.PvsStudioParser; /** * A descriptor for the PVS-Studio static analyzer. @@ -17,7 +17,7 @@ class PvsStudioDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new PvsStudioParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PyDocStyleDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PyDocStyleDescriptor.java index 3efb44c85..99b5eceab 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PyDocStyleDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PyDocStyleDescriptor.java @@ -17,7 +17,7 @@ class PyDocStyleDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new PyDocStyleAdapter(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/PyLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/PyLintDescriptor.java index 2d67af972..e9bb0eeab 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/PyLintDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/PyLintDescriptor.java @@ -2,8 +2,8 @@ import edu.hm.hafner.analysis.Issue; import edu.hm.hafner.analysis.IssueParser; +import edu.hm.hafner.analysis.parser.PyLintDescriptions; import edu.hm.hafner.analysis.parser.PyLintParser; -import edu.hm.hafner.analysis.parser.pylint.PyLintDescriptions; import edu.hm.hafner.analysis.util.Deferred; /** @@ -22,7 +22,7 @@ class PyLintDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new PyLintParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/QacSourceCodeAnalyserDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/QacSourceCodeAnalyserDescriptor.java index 9e6e3800d..d04359227 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/QacSourceCodeAnalyserDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/QacSourceCodeAnalyserDescriptor.java @@ -17,7 +17,7 @@ class QacSourceCodeAnalyserDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new QacSourceCodeAnalyserParser(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/QtTranslationDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/QtTranslationDescriptor.java index 0158a4f3b..80b11a3d9 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/QtTranslationDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/QtTranslationDescriptor.java @@ -17,7 +17,7 @@ class QtTranslationDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new QtTranslationParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/ResharperDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/ResharperDescriptor.java index 86ce9d942..dd05a5bc7 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/ResharperDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/ResharperDescriptor.java @@ -17,7 +17,7 @@ class ResharperDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new ResharperInspectCodeAdapter(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/RevApiDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/RevApiDescriptor.java index 71827ccf2..8f3583880 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/RevApiDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/RevApiDescriptor.java @@ -17,7 +17,7 @@ class RevApiDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new RevApiParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/RfLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/RfLintDescriptor.java index ef980f3d5..4d7011454 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/RfLintDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/RfLintDescriptor.java @@ -17,7 +17,7 @@ class RfLintDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new RfLintParser(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/RoboCopyDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/RoboCopyDescriptor.java index fe390540a..b279a4262 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/RoboCopyDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/RoboCopyDescriptor.java @@ -17,7 +17,7 @@ class RoboCopyDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new RobocopyParser(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/RuboCopDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/RuboCopDescriptor.java index 1c786be68..d40194aee 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/RuboCopDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/RuboCopDescriptor.java @@ -17,7 +17,7 @@ class RuboCopDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new RuboCopParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/SarifDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/SarifDescriptor.java index 70b5fc739..00a6143bc 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/SarifDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/SarifDescriptor.java @@ -17,7 +17,7 @@ class SarifDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new SarifAdapter(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/SemgrepDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/SemgrepDescriptor.java index 0e6433863..0a3f07768 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/SemgrepDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/SemgrepDescriptor.java @@ -1,6 +1,7 @@ package edu.hm.hafner.analysis.registry; import edu.hm.hafner.analysis.IssueParser; +import edu.hm.hafner.analysis.Report.IssueType; import edu.hm.hafner.analysis.parser.violations.SemgrepAdapter; /** @@ -17,7 +18,7 @@ class SemgrepDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new SemgrepAdapter(); } @@ -37,7 +38,7 @@ public String getIconUrl() { } @Override - public Type getType() { - return Type.VULNERABILITY; + public IssueType getType() { + return IssueType.VULNERABILITY; } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/SimianDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/SimianDescriptor.java index df656b34f..dd1a3adba 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/SimianDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/SimianDescriptor.java @@ -1,7 +1,7 @@ package edu.hm.hafner.analysis.registry; import edu.hm.hafner.analysis.IssueParser; -import edu.hm.hafner.analysis.parser.dry.simian.SimianParser; +import edu.hm.hafner.analysis.parser.SimianParser; /** * A descriptor for the Simian duplication scanner. @@ -17,7 +17,7 @@ class SimianDescriptor extends DryDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new SimianParser(getHighThreshold(options), getNormalThreshold(options)); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/SimulinkCheckDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/SimulinkCheckDescriptor.java index a576ba747..899fed105 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/SimulinkCheckDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/SimulinkCheckDescriptor.java @@ -18,7 +18,7 @@ class SimulinkCheckDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new SimulinkCheckParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/SpotBugsDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/SpotBugsDescriptor.java index 0667dee5a..e150954ca 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/SpotBugsDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/SpotBugsDescriptor.java @@ -27,9 +27,4 @@ public String getUrl() { public String getIconUrl() { return "https://raw.githubusercontent.com/spotbugs/spotbugs.github.io/master/images/logos/spotbugs_icon_only_zoom_256px.png"; } - - @Override - public Type getType() { - return Type.BUG; - } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/StyleCopDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/StyleCopDescriptor.java index 78cfb5837..1da6a4c94 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/StyleCopDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/StyleCopDescriptor.java @@ -17,7 +17,7 @@ class StyleCopDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new StyleCopParser(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/StyleLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/StyleLintDescriptor.java index 5ae2117e6..736b4e4a1 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/StyleLintDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/StyleLintDescriptor.java @@ -1,7 +1,7 @@ package edu.hm.hafner.analysis.registry; import edu.hm.hafner.analysis.IssueParser; -import edu.hm.hafner.analysis.parser.checkstyle.CheckStyleParser; +import edu.hm.hafner.analysis.parser.CheckStyleParser; /** * A descriptor for Stylelint. Delegates to {@link CheckStyleParser}. @@ -17,7 +17,7 @@ class StyleLintDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new CheckStyleParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/SunCDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/SunCDescriptor.java index 923c4fdb5..9bddab4db 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/SunCDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/SunCDescriptor.java @@ -17,7 +17,7 @@ class SunCDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new SunCParser(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/SwiftLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/SwiftLintDescriptor.java index 083bc4e1e..f57a868f3 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/SwiftLintDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/SwiftLintDescriptor.java @@ -1,7 +1,7 @@ package edu.hm.hafner.analysis.registry; import edu.hm.hafner.analysis.IssueParser; -import edu.hm.hafner.analysis.parser.checkstyle.CheckStyleParser; +import edu.hm.hafner.analysis.parser.CheckStyleParser; /** * A descriptor for SwiftLint. Delegates to {@link CheckStyleParser}. @@ -17,7 +17,7 @@ class SwiftLintDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new CheckStyleParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/TaglistDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/TaglistDescriptor.java index 449b71b8f..2a4c52993 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/TaglistDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/TaglistDescriptor.java @@ -17,7 +17,7 @@ class TaglistDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new TaglistParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/TaskingVxCompilerDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/TaskingVxCompilerDescriptor.java index e8574dc03..327a9a8f4 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/TaskingVxCompilerDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/TaskingVxCompilerDescriptor.java @@ -17,7 +17,7 @@ class TaskingVxCompilerDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new TaskingVxCompilerParser(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/TiCcsDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/TiCcsDescriptor.java index a4926ccd1..b12c0b2ed 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/TiCcsDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/TiCcsDescriptor.java @@ -17,7 +17,7 @@ class TiCcsDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new TiCcsParser(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/TnsdlDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/TnsdlDescriptor.java index 9a484be35..06d6a36f3 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/TnsdlDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/TnsdlDescriptor.java @@ -17,7 +17,7 @@ class TnsdlDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new TnsdlParser(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/TrivyDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/TrivyDescriptor.java index a6eea4ea5..d617acccc 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/TrivyDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/TrivyDescriptor.java @@ -1,6 +1,7 @@ package edu.hm.hafner.analysis.registry; import edu.hm.hafner.analysis.IssueParser; +import edu.hm.hafner.analysis.Report.IssueType; import edu.hm.hafner.analysis.parser.TrivyParser; import static j2html.TagCreator.*; @@ -19,7 +20,7 @@ class TrivyDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new TrivyParser(); } @@ -43,7 +44,7 @@ public String getIconUrl() { } @Override - public Type getType() { - return Type.VULNERABILITY; + public IssueType getType() { + return IssueType.VULNERABILITY; } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/TsLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/TsLintDescriptor.java index 40ff4867b..d215bae0a 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/TsLintDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/TsLintDescriptor.java @@ -1,7 +1,7 @@ package edu.hm.hafner.analysis.registry; import edu.hm.hafner.analysis.IssueParser; -import edu.hm.hafner.analysis.parser.checkstyle.CheckStyleParser; +import edu.hm.hafner.analysis.parser.CheckStyleParser; /** * A descriptor for TSLint. Delegates to {@link CheckStyleParser}. @@ -17,7 +17,7 @@ class TsLintDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new CheckStyleParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/ValeDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/ValeDescriptor.java new file mode 100644 index 000000000..dd4df288e --- /dev/null +++ b/src/main/java/edu/hm/hafner/analysis/registry/ValeDescriptor.java @@ -0,0 +1,36 @@ +package edu.hm.hafner.analysis.registry; + +import edu.hm.hafner.analysis.IssueParser; +import edu.hm.hafner.analysis.parser.ValeParser; + +/** + * Descriptor for the vale prose linter. + */ +public class ValeDescriptor extends ParserDescriptor { + private static final String ID = "vale"; + private static final String NAME = "Vale"; + + ValeDescriptor() { + super(ID, NAME); + } + + @Override + public IssueParser create(final Option... options) { + return new ValeParser(); + } + + @Override + public String getPattern() { + return "**/vale-report.json"; + } + + @Override + public String getUrl() { + return "https://vale.sh/"; + } + + @Override + public String getHelp() { + return "Reads vale report files. Use the flag --output=JSON"; + } +} diff --git a/src/main/java/edu/hm/hafner/analysis/registry/ValgrindDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/ValgrindDescriptor.java index 739e1c2ea..479f6401b 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/ValgrindDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/ValgrindDescriptor.java @@ -17,7 +17,7 @@ class ValgrindDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new ValgrindAdapter(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/VeraCodePipelineScannerDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/VeraCodePipelineScannerDescriptor.java index 796d9a620..2473c3a44 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/VeraCodePipelineScannerDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/VeraCodePipelineScannerDescriptor.java @@ -19,7 +19,7 @@ class VeraCodePipelineScannerDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new VeraCodePipelineScannerParser(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/XmlLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/XmlLintDescriptor.java index 5e0391c25..598ca9195 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/XmlLintDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/XmlLintDescriptor.java @@ -17,7 +17,7 @@ class XmlLintDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new XmlLintAdapter(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/YamlLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/YamlLintDescriptor.java index 89da3ccd2..7b0e43ce7 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/YamlLintDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/YamlLintDescriptor.java @@ -17,7 +17,7 @@ class YamlLintDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new YamlLintAdapter(); } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/YoctoScannerDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/YoctoScannerDescriptor.java index a5bef4f54..3783acb66 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/YoctoScannerDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/YoctoScannerDescriptor.java @@ -1,6 +1,7 @@ package edu.hm.hafner.analysis.registry; import edu.hm.hafner.analysis.IssueParser; +import edu.hm.hafner.analysis.Report.IssueType; import edu.hm.hafner.analysis.parser.YoctoScannerParser; import static j2html.TagCreator.*; @@ -19,7 +20,7 @@ class YoctoScannerDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new YoctoScannerParser(); } @@ -43,7 +44,7 @@ public String getIconUrl() { } @Override - public Type getType() { - return Type.VULNERABILITY; + public IssueType getType() { + return IssueType.VULNERABILITY; } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/YuiCompressorDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/YuiCompressorDescriptor.java index a3bfa80ff..6c35f6dec 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/YuiCompressorDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/YuiCompressorDescriptor.java @@ -17,7 +17,7 @@ class YuiCompressorDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new YuiCompressorParser(); } } diff --git a/src/main/java/edu/hm/hafner/analysis/registry/ZptLintDescriptor.java b/src/main/java/edu/hm/hafner/analysis/registry/ZptLintDescriptor.java index e4cdee560..41a8778e7 100644 --- a/src/main/java/edu/hm/hafner/analysis/registry/ZptLintDescriptor.java +++ b/src/main/java/edu/hm/hafner/analysis/registry/ZptLintDescriptor.java @@ -17,7 +17,7 @@ class ZptLintDescriptor extends ParserDescriptor { } @Override - public IssueParser createParser(final Option... options) { + public IssueParser create(final Option... options) { return new ZptLintAdapter(); } } diff --git a/src/main/resources/edu/hm/hafner/analysis/parser/pylint/pylint-descriptions.json b/src/main/resources/edu/hm/hafner/analysis/parser/pylint-descriptions.json similarity index 100% rename from src/main/resources/edu/hm/hafner/analysis/parser/pylint/pylint-descriptions.json rename to src/main/resources/edu/hm/hafner/analysis/parser/pylint-descriptions.json diff --git a/src/test/java/edu/hm/hafner/analysis/AbstractModuleDetectorTest.java b/src/test/java/edu/hm/hafner/analysis/AbstractModuleDetectorTest.java index 2689bf793..974a88ad7 100644 --- a/src/test/java/edu/hm/hafner/analysis/AbstractModuleDetectorTest.java +++ b/src/test/java/edu/hm/hafner/analysis/AbstractModuleDetectorTest.java @@ -5,11 +5,12 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; +import java.util.List; import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; -import edu.hm.hafner.analysis.ModuleDetector.FileSystem; +import edu.hm.hafner.analysis.ModuleDetectorRunner.FileSystemFacade; import edu.hm.hafner.util.PathUtil; import edu.hm.hafner.util.ResourceTest; @@ -26,13 +27,11 @@ abstract class AbstractModuleDetectorTest extends ResourceTest { @Test void shouldIgnoreExceptionsDuringParsing() { var fileSystem = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(new String[]{ - getPathPrefix() + getProjectFileName() - }); + when(stub.find(any(), anyString())).thenReturn(List.of(getPathPrefix() + getProjectFileName())); when(stub.open(anyString())).thenThrow(new FileNotFoundException("File not found")); }); - var detector = new ModuleDetector(ROOT, fileSystem); + var detector = new ModuleDetectorRunner(ROOT, fileSystem); assertThat(detector.guessModuleName(PREFIX + getFileName())).isEqualTo(StringUtils.EMPTY); } @@ -43,9 +42,9 @@ void shouldIgnoreExceptionsDuringParsing() { abstract String getProjectFileName(); - protected FileSystem createFileSystemStub(final Stub stub) { + protected FileSystemFacade createFileSystemStub(final Stub stub) { try { - var fileSystem = mock(FileSystem.class); + var fileSystem = mock(FileSystemFacade.class); stub.apply(fileSystem); return fileSystem; } @@ -59,10 +58,10 @@ protected InputStream read(final String fileName) { } /** - * Stubs the {@link PackageDetectors.FileSystem} using a lambda. + * Stubs the {@link FileSystemFacade} using a lambda. */ @FunctionalInterface protected interface Stub { - void apply(FileSystem f) throws IOException; + void apply(FileSystemFacade f) throws IOException; } } diff --git a/src/test/java/edu/hm/hafner/analysis/AntModuleDetectorTest.java b/src/test/java/edu/hm/hafner/analysis/AntModuleDetectorTest.java index 4710ebdc0..4f067a464 100644 --- a/src/test/java/edu/hm/hafner/analysis/AntModuleDetectorTest.java +++ b/src/test/java/edu/hm/hafner/analysis/AntModuleDetectorTest.java @@ -1,5 +1,7 @@ package edu.hm.hafner.analysis; +import java.util.List; + import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; @@ -31,10 +33,10 @@ String getProjectFileName() { @Test void shouldIdentifyModuleByReadingAntProjectFile() { var factory = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(new String[]{PATH_PREFIX_ANT + AntModuleDetector.ANT_PROJECT}); + when(stub.find(any(), anyString())).thenReturn(List.of(PATH_PREFIX_ANT + AntModuleDetector.ANT_PROJECT)); when(stub.open(anyString())).thenAnswer(filename -> read(AntModuleDetector.ANT_PROJECT)); }); - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); assertThat(detector.guessModuleName(PREFIX + PATH_PREFIX_ANT + "something.txt")) .isEqualTo(EXPECTED_ANT_MODULE); diff --git a/src/test/java/edu/hm/hafner/analysis/CSharpNamespaceDetectorTest.java b/src/test/java/edu/hm/hafner/analysis/CSharpNamespaceDetectorTest.java deleted file mode 100644 index e9434dc7b..000000000 --- a/src/test/java/edu/hm/hafner/analysis/CSharpNamespaceDetectorTest.java +++ /dev/null @@ -1,47 +0,0 @@ -package edu.hm.hafner.analysis; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -import edu.hm.hafner.util.ResourceTest; - -import static org.assertj.core.api.Assertions.*; - -/** - * Tests the class {@link CSharpNamespaceDetector}. - * - * @author Ullrich Hafner - */ -class CSharpNamespaceDetectorTest extends ResourceTest { - @ParameterizedTest(name = "{index} => file={0}, expected package={1}") - @CsvSource({ - "ActionBinding.cs, Avaloq.SmartClient.Utilities", - "ActionBinding-Original-Formatting.cs, Avaloq.SmartClient.Utilities", - "Program.cs, ConsoleApplication1", // see Jenkins-48869 - "Class1.cs, ConsoleApplication1", // see Jenkins-48869 - "pom.xml, -", - "MavenJavaTest.txt, -"}) - void shouldExtractPackageNameFromJavaSource(final String fileName, final String expectedPackage) throws IOException { - try (var stream = asInputStream(fileName)) { - assertThat(new CSharpNamespaceDetector().detectPackageName(stream, StandardCharsets.UTF_8)) - .isEqualTo(expectedPackage); - } - } - - @Test - void shouldAcceptCorrectFileSuffix() { - var namespaceDetector = new CSharpNamespaceDetector(); - assertThat(namespaceDetector.accepts("ActionBinding.cs")) - .as("Does not accept a C# file.").isTrue(); - assertThat(namespaceDetector.accepts("ActionBinding.cs.c")) - .as("Accepts a non-C# file.").isFalse(); - assertThat(namespaceDetector.accepts("Action.java")) - .as("Accepts a non-C# file.").isFalse(); - assertThat(namespaceDetector.accepts("pom.xml")) - .as("Accepts a non-C# file.").isFalse(); - } -} diff --git a/src/test/java/edu/hm/hafner/analysis/GradleModuleDetectorTest.java b/src/test/java/edu/hm/hafner/analysis/GradleModuleDetectorTest.java index fa24d84db..f2f8be7ef 100644 --- a/src/test/java/edu/hm/hafner/analysis/GradleModuleDetectorTest.java +++ b/src/test/java/edu/hm/hafner/analysis/GradleModuleDetectorTest.java @@ -1,5 +1,7 @@ package edu.hm.hafner.analysis; +import java.util.List; + import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; @@ -33,9 +35,9 @@ String getProjectFileName() { void shouldIdentifyModuleByReadingGradlePath() { var factory = createFileSystemStub(stub -> when(stub.find(any(), anyString())).thenReturn( - new String[]{PATH_PREFIX_GRADLE + GradleModuleDetector.BUILD_GRADLE})); + List.of(PATH_PREFIX_GRADLE + GradleModuleDetector.BUILD_GRADLE))); - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); assertThat(detector.guessModuleName(PREFIX + PATH_PREFIX_GRADLE + "build/reports/something.txt")) .isEqualTo(EXPECTED_GRADLE_MODULE_ROOT_BY_PATH); @@ -46,13 +48,12 @@ void shouldIdentifyModuleByReadingGradlePath() { @Test void shouldIdentifyModuleByFindingClosestGradlePath() { var factory = createFileSystemStub(stub -> - when(stub.find(any(), anyString())).thenReturn(new String[]{ + when(stub.find(any(), anyString())).thenReturn(List.of( PATH_PREFIX_GRADLE + GradleModuleDetector.BUILD_GRADLE, PATH_PREFIX_GRADLE + "moduleB/" + GradleModuleDetector.BUILD_GRADLE, - PATH_PREFIX_GRADLE + "a-module/" + GradleModuleDetector.BUILD_GRADLE, - })); + PATH_PREFIX_GRADLE + "a-module/" + GradleModuleDetector.BUILD_GRADLE))); - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); var gradleWorkspace = PREFIX + PATH_PREFIX_GRADLE; assertThat(detector.guessModuleName( @@ -70,10 +71,10 @@ void shouldIdentifyModuleByFindingClosestGradlePath() { @Test void shouldIdentifyModuleByReadingGradleKtsPath() { var factory = createFileSystemStub(stub -> - when(stub.find(any(), anyString())).thenReturn( - new String[]{PATH_PREFIX_GRADLE + GradleModuleDetector.BUILD_GRADLE_KTS})); + when(stub.find(any(), anyString())).thenReturn(List.of( + PATH_PREFIX_GRADLE + GradleModuleDetector.BUILD_GRADLE_KTS))); - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); assertThat(detector.guessModuleName(PREFIX + PATH_PREFIX_GRADLE + "build/reports/something.txt")) .isEqualTo(EXPECTED_GRADLE_MODULE_ROOT_BY_PATH); @@ -84,15 +85,14 @@ void shouldIdentifyModuleByReadingGradleKtsPath() { @Test void shouldIdentifyModuleByReadingGradleSettings() { var factory = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(new String[]{ + when(stub.find(any(), anyString())).thenReturn(List.of( PATH_PREFIX_GRADLE + GradleModuleDetector.SETTINGS_GRADLE, PATH_PREFIX_GRADLE + "moduleB/" + GradleModuleDetector.BUILD_GRADLE, - PATH_PREFIX_GRADLE + "a-module/" + GradleModuleDetector.BUILD_GRADLE, - }); + PATH_PREFIX_GRADLE + "a-module/" + GradleModuleDetector.BUILD_GRADLE)); when(stub.open(anyString())).thenAnswer(filename -> read("settings-1.gradle")); }); - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); var gradleWorkspace = PREFIX + PATH_PREFIX_GRADLE; assertThat(detector.guessModuleName( @@ -110,13 +110,12 @@ void shouldIdentifyModuleByReadingGradleSettings() { @Test void shouldIdentifyModuleByReadingGradleSettingsKts() { var factory = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(new String[]{ - PATH_PREFIX_GRADLE + GradleModuleDetector.SETTINGS_GRADLE_KTS, - }); + when(stub.find(any(), anyString())).thenReturn(List.of( + PATH_PREFIX_GRADLE + GradleModuleDetector.SETTINGS_GRADLE_KTS)); when(stub.open(anyString())).thenAnswer(filename -> read("settings-1.gradle")); }); - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); var gradleWorkspace = PREFIX + PATH_PREFIX_GRADLE; assertThat(detector.guessModuleName(gradleWorkspace + "build/reports/something.txt")) @@ -128,16 +127,15 @@ void shouldIdentifyModuleByReadingGradleSettingsKts() { @Test void shouldEnsureThatGradleSettingsHasPrecedenceOverRootBuild() { var factory = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(new String[]{ + when(stub.find(any(), anyString())).thenReturn(List.of( PATH_PREFIX_GRADLE + GradleModuleDetector.BUILD_GRADLE, PATH_PREFIX_GRADLE + GradleModuleDetector.SETTINGS_GRADLE, PATH_PREFIX_GRADLE + "moduleB/" + GradleModuleDetector.BUILD_GRADLE, - PATH_PREFIX_GRADLE + "a-module/" + GradleModuleDetector.BUILD_GRADLE, - }); + PATH_PREFIX_GRADLE + "a-module/" + GradleModuleDetector.BUILD_GRADLE)); when(stub.open(anyString())).thenAnswer(filename -> read("settings-1.gradle")); }); - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); var gradleWorkspace = PREFIX + PATH_PREFIX_GRADLE; assertThat(detector.guessModuleName( @@ -155,15 +153,14 @@ void shouldEnsureThatGradleSettingsHasPrecedenceOverRootBuild() { @Test void shouldEnsureThatGradleSettingsCanParseFormat1() { var factory = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(new String[]{ - PATH_PREFIX_GRADLE + GradleModuleDetector.SETTINGS_GRADLE, - }); + when(stub.find(any(), anyString())).thenReturn( + List.of(PATH_PREFIX_GRADLE + GradleModuleDetector.SETTINGS_GRADLE)); when(stub.open(anyString())).thenAnswer(fileName -> read("settings-1.gradle")); }); var gradleWorkspace = PREFIX + PATH_PREFIX_GRADLE; - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); assertThat(detector.guessModuleName(gradleWorkspace + "build/reports/something.txt")) .isEqualTo(EXPECTED_GRADLE_MODULE_ROOT); } @@ -171,15 +168,14 @@ void shouldEnsureThatGradleSettingsCanParseFormat1() { @Test void shouldEnsureThatGradleSettingsCanParseFormat2() { var factory = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(new String[]{ - PATH_PREFIX_GRADLE + GradleModuleDetector.SETTINGS_GRADLE, - }); + when(stub.find(any(), anyString())).thenReturn(List.of( + PATH_PREFIX_GRADLE + GradleModuleDetector.SETTINGS_GRADLE)); when(stub.open(anyString())).thenAnswer(fileName -> read("settings-2.gradle")); }); var gradleWorkspace = PREFIX + PATH_PREFIX_GRADLE; - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); assertThat(detector.guessModuleName(gradleWorkspace + "build/reports/something.txt")) .isEqualTo("root-project-2"); } @@ -187,15 +183,14 @@ void shouldEnsureThatGradleSettingsCanParseFormat2() { @Test void shouldEnsureThatGradleSettingsCanParseFormat3() { var factory = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(new String[]{ - PATH_PREFIX_GRADLE + GradleModuleDetector.SETTINGS_GRADLE, - }); + when(stub.find(any(), anyString())).thenReturn(List.of( + PATH_PREFIX_GRADLE + GradleModuleDetector.SETTINGS_GRADLE)); when(stub.open(anyString())).thenAnswer(fileName -> read("settings-3.gradle")); }); var gradleWorkspace = PREFIX + PATH_PREFIX_GRADLE; - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); assertThat(detector.guessModuleName(gradleWorkspace + "build/reports/something.txt")) .isEqualTo("root-project-3"); } @@ -203,15 +198,14 @@ void shouldEnsureThatGradleSettingsCanParseFormat3() { @Test void shouldEnsureThatGradleSettingsCanParseFormat4() { var factory = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(new String[]{ - PATH_PREFIX_GRADLE + GradleModuleDetector.SETTINGS_GRADLE, - }); + when(stub.find(any(), anyString())).thenReturn(List.of( + PATH_PREFIX_GRADLE + GradleModuleDetector.SETTINGS_GRADLE)); when(stub.open(anyString())).thenAnswer(fileName -> read("settings-4.gradle")); }); var gradleWorkspace = PREFIX + PATH_PREFIX_GRADLE; - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); assertThat(detector.guessModuleName(gradleWorkspace + "build/reports/something.txt")) .isEqualTo("root-project-4"); } @@ -219,16 +213,15 @@ void shouldEnsureThatGradleSettingsCanParseFormat4() { @Test void shouldIgnoreGradleSettingsWithoutProjectName() { var factory = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(new String[]{ + when(stub.find(any(), anyString())).thenReturn(List.of( PATH_PREFIX_GRADLE + GradleModuleDetector.BUILD_GRADLE, - PATH_PREFIX_GRADLE + GradleModuleDetector.SETTINGS_GRADLE, - }); + PATH_PREFIX_GRADLE + GradleModuleDetector.SETTINGS_GRADLE)); when(stub.open(anyString())).thenAnswer(fileName -> read("settings-5.gradle")); }); var gradleWorkspace = PREFIX + PATH_PREFIX_GRADLE; - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); assertThat(detector.guessModuleName(gradleWorkspace + "build/reports/something.txt")) .isEqualTo(EXPECTED_GRADLE_MODULE_ROOT_BY_PATH); } @@ -236,11 +229,10 @@ void shouldIgnoreGradleSettingsWithoutProjectName() { @Test void shouldIgnoreGradleFileWithNoParentPath() { var factory = createFileSystemStub(stub -> - when(stub.find(any(), anyString())).thenReturn(new String[]{ - GradleModuleDetector.BUILD_GRADLE, - })); + when(stub.find(any(), anyString())).thenReturn(List.of( + GradleModuleDetector.BUILD_GRADLE))); - var detector = new ModuleDetector(ROOT_ABSOLUTE, factory); + var detector = new ModuleDetectorRunner(ROOT_ABSOLUTE, factory); assertThat(detector.guessModuleName("build/reports/something.txt")) .isEqualTo(StringUtils.EMPTY); } diff --git a/src/test/java/edu/hm/hafner/analysis/IssueDifferenceTest.java b/src/test/java/edu/hm/hafner/analysis/IssueDifferenceTest.java index 77e1fc99c..6edc8d983 100644 --- a/src/test/java/edu/hm/hafner/analysis/IssueDifferenceTest.java +++ b/src/test/java/edu/hm/hafner/analysis/IssueDifferenceTest.java @@ -203,9 +203,8 @@ void shouldHandleAggregatedResults() { } private Report readSpotBugsWarnings() { - return new ParserRegistry().get("spotbugs") - .createParser() - .parse(new FileReaderFactory(getResourceAsFile("parser/findbugs/spotbugsXml.xml"), - StandardCharsets.UTF_8)); + var reportFile = new FileReaderFactory(getResourceAsFile("parser/findbugs/spotbugsXml.xml"), + StandardCharsets.UTF_8); + return new ParserRegistry().get("spotbugs").createParser().parseReport(reportFile); } } diff --git a/src/test/java/edu/hm/hafner/analysis/JavaPackageDetectorTest.java b/src/test/java/edu/hm/hafner/analysis/JavaPackageDetectorTest.java deleted file mode 100644 index 89e1543f9..000000000 --- a/src/test/java/edu/hm/hafner/analysis/JavaPackageDetectorTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package edu.hm.hafner.analysis; - -import java.io.IOException; - -import org.apache.commons.io.IOUtils; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.ValueSource; - -import edu.hm.hafner.util.ResourceTest; - -import static java.nio.charset.StandardCharsets.*; -import static org.assertj.core.api.Assertions.*; - -/** - * Tests the class {@link JavaPackageDetector}. - * - * @author Ullrich Hafner - */ -class JavaPackageDetectorTest extends ResourceTest { - @ParameterizedTest(name = "{index} => file={0}, expected package={1}") - @CsvSource({ - "MavenJavaTest.txt, hudson.plugins.tasks.util", - "complicated-package-declaration.txt, hudson.plugins.findbugs.util", - "pom.xml, -", - "ActionBinding.cs, -"}) - void shouldExtractPackageNameFromJavaSource(final String fileName, final String expectedPackage) throws IOException { - try (var stream = asInputStream(fileName)) { - assertThat(new JavaPackageDetector().detectPackageName(stream, UTF_8)) - .isEqualTo(expectedPackage); - } - } - - @ParameterizedTest(name = "{index} => Invalid package name: {0}") - @ValueSource(strings = {"package EDU.hm.hafner.analysis;", "package 0123.hm.hafner.analysis;"}) - void shouldSkipPackagesThatDoNotStartWithLowerCase(final String name) throws IOException { - var detector = new JavaPackageDetector(); - - assertThat(detector.detectPackageName(IOUtils.toInputStream(name, UTF_8), UTF_8)).isEqualTo("-"); - } - - @Test - void shouldAcceptCorrectFileSuffix() { - var packageDetector = new JavaPackageDetector(); - assertThat(packageDetector.accepts("Action.java")).as("Does not accept a Java file.") - .isTrue(); - assertThat(packageDetector.accepts("ActionBinding.cs")).as("Accepts a non-Java file.") - .isFalse(); - assertThat(packageDetector.accepts("pom.xml")).as("Accepts a non-C# file.") - .isFalse(); - } -} diff --git a/src/test/java/edu/hm/hafner/analysis/KotlinPackageDetectorTest.java b/src/test/java/edu/hm/hafner/analysis/KotlinPackageDetectorTest.java deleted file mode 100644 index f1cf1f654..000000000 --- a/src/test/java/edu/hm/hafner/analysis/KotlinPackageDetectorTest.java +++ /dev/null @@ -1,53 +0,0 @@ -package edu.hm.hafner.analysis; - -import java.io.IOException; - -import org.apache.commons.io.IOUtils; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; -import org.junit.jupiter.params.provider.ValueSource; - -import edu.hm.hafner.util.ResourceTest; - -import static java.nio.charset.StandardCharsets.*; -import static org.assertj.core.api.Assertions.*; - -/** - * Tests the class {@link KotlinPackageDetector}. - * - * @author Bastian Kersting - */ -class KotlinPackageDetectorTest extends ResourceTest { - @ParameterizedTest(name = "{index} => file={0}, expected package={1}") - @CsvSource({ - "MavenKotlinTest.txt, edu.hm.kersting", - "complicated-package-declaration-kotlin.txt, edu.hm.kersting", - "pom.xml, -", - "ActionBinding.cs, -"}) - void shouldExtractPackageNameFromKotlinSource(final String fileName, final String expectedPackage) throws IOException { - try (var stream = asInputStream(fileName)) { - assertThat(new KotlinPackageDetector().detectPackageName(stream, UTF_8)) - .isEqualTo(expectedPackage); - } - } - - @ParameterizedTest(name = "{index} => Invalid package name: {0}") - @ValueSource(strings = {"package EDU.hm.hafner.analysis;", "package 0123.hm.hafner.analysis;"}) - void shouldSkipPackagesThatDoNotStartWithLowerCase(final String name) throws IOException { - var detector = new KotlinPackageDetector(); - - assertThat(detector.detectPackageName(IOUtils.toInputStream(name, UTF_8), UTF_8)).isEqualTo("-"); - } - - @Test - void shouldAcceptCorrectFileSuffix() { - var packageDetector = new KotlinPackageDetector(); - assertThat(packageDetector.accepts("Action.kt")).as("Does not accept a Kotlin file.") - .isTrue(); - assertThat(packageDetector.accepts("ActionBinding.cs")).as("Accepts a non-Java file.") - .isFalse(); - assertThat(packageDetector.accepts("pom.xml")).as("Accepts a non-C# file.") - .isFalse(); - } -} diff --git a/src/test/java/edu/hm/hafner/analysis/MavenModuleDetectorTest.java b/src/test/java/edu/hm/hafner/analysis/MavenModuleDetectorTest.java index 0922d0230..e02eb55b8 100644 --- a/src/test/java/edu/hm/hafner/analysis/MavenModuleDetectorTest.java +++ b/src/test/java/edu/hm/hafner/analysis/MavenModuleDetectorTest.java @@ -1,5 +1,7 @@ package edu.hm.hafner.analysis; +import java.util.List; + import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; @@ -30,11 +32,11 @@ String getProjectFileName() { void shouldIdentifyModuleByReadingMavenPom() { var factory = createFileSystemStub(stub -> { when(stub.find(any(), anyString())).thenReturn( - new String[]{PATH_PREFIX_MAVEN + MavenModuleDetector.MAVEN_POM}); + List.of(PATH_PREFIX_MAVEN + MavenModuleDetector.MAVEN_POM)); when(stub.open(anyString())).thenAnswer(fileName -> read(MavenModuleDetector.MAVEN_POM)); }); - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); assertThat(detector.guessModuleName(PREFIX + PATH_PREFIX_MAVEN + "something.txt")).isEqualTo( EXPECTED_MAVEN_MODULE); @@ -46,11 +48,11 @@ void shouldIdentifyModuleByReadingMavenPom() { @Test void shouldIdentifyModuleByReadingMavenPomWithoutName() { var factory = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(new String[]{PATH_PREFIX_MAVEN + MavenModuleDetector.MAVEN_POM}); + when(stub.find(any(), anyString())).thenReturn(List.of(PATH_PREFIX_MAVEN + MavenModuleDetector.MAVEN_POM)); when(stub.open(anyString())).thenAnswer(filename -> read("no-name-pom.xml")); }); - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); var artifactId = "com.avaloq.adt.core"; assertThat(detector.guessModuleName(PREFIX + PATH_PREFIX_MAVEN + "something.txt")) diff --git a/src/test/java/edu/hm/hafner/analysis/ModuleDetectorTest.java b/src/test/java/edu/hm/hafner/analysis/ModuleDetectorTest.java index ab16f615d..4f6f355bc 100644 --- a/src/test/java/edu/hm/hafner/analysis/ModuleDetectorTest.java +++ b/src/test/java/edu/hm/hafner/analysis/ModuleDetectorTest.java @@ -4,11 +4,12 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; +import java.util.List; import org.apache.commons.io.IOUtils; import org.junit.jupiter.api.Test; -import edu.hm.hafner.analysis.ModuleDetector.FileSystem; +import edu.hm.hafner.analysis.ModuleDetectorRunner.FileSystemFacade; import edu.hm.hafner.util.PathUtil; import edu.hm.hafner.util.ResourceTest; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -18,7 +19,7 @@ import static org.mockito.Mockito.*; /** - * Tests the class {@link ModuleDetector}. + * Tests the class {@link ModuleDetectorRunner}. */ @SuppressFBWarnings("DMI") class ModuleDetectorTest extends ResourceTest { @@ -42,12 +43,12 @@ void shouldIdentifyModuleIfThereAreMoreEntries() { var factory = createFileSystemStub(stub -> { var ant = PATH_PREFIX_ANT + AntModuleDetector.ANT_PROJECT; var maven = PATH_PREFIX_MAVEN + MavenModuleDetector.MAVEN_POM; - when(stub.find(any(), anyString())).thenReturn(new String[]{ant, maven}); - when(stub.open(PREFIX + ant)).thenReturn(read(AntModuleDetector.ANT_PROJECT)); + when(stub.find(any(), anyString())).thenReturn(List.of(ant, maven)); + when(stub.open(PREFIX + ant)).thenAnswer(fileName -> read(AntModuleDetector.ANT_PROJECT)); when(stub.open(PREFIX + maven)).thenAnswer(filename -> read(MavenModuleDetector.MAVEN_POM)); }); - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); assertThat(detector.guessModuleName(PREFIX + PATH_PREFIX_ANT + "something.txt")) .isEqualTo(EXPECTED_ANT_MODULE); @@ -83,12 +84,12 @@ void shouldEnsureThatOsgiHasPrecedenceOverMavenAndAnt() { @SuppressWarnings("PMD.UseVarargs") private void verifyOrder(final String prefix, final String ant, final String maven, final String[] foundFiles) { var factory = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(foundFiles); - when(stub.open(ant)).thenReturn(read(AntModuleDetector.ANT_PROJECT)); + when(stub.find(any(), anyString())).thenReturn(List.of(foundFiles)); + when(stub.open(ant)).thenAnswer(fileName -> read(AntModuleDetector.ANT_PROJECT)); when(stub.open(maven)).thenAnswer(filename -> read(MavenModuleDetector.MAVEN_POM)); }); - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); assertThat(detector.guessModuleName(prefix + "something.txt")).isEqualTo(EXPECTED_MAVEN_MODULE); } @@ -96,7 +97,7 @@ private void verifyOrder(final String prefix, final String ant, final String mav private void verifyOrder(final String prefix, final String ant, final String maven, final String osgi, final String... foundFiles) { var fileSystem = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(foundFiles); + when(stub.find(any(), anyString())).thenReturn(List.of(foundFiles)); when(stub.open(ant)).thenAnswer(filename -> read(AntModuleDetector.ANT_PROJECT)); when(stub.open(maven)).thenAnswer(filename -> read(MavenModuleDetector.MAVEN_POM)); when(stub.open(osgi)).thenAnswer(filename -> read(MANIFEST)); @@ -104,7 +105,7 @@ private void verifyOrder(final String prefix, final String ant, final String mav when(stub.open(prefix + "/" + OsgiModuleDetector.BUNDLE_PROPERTIES)).thenAnswer(filename -> createEmptyStream()); }); - var detector = new ModuleDetector(ROOT, fileSystem); + var detector = new ModuleDetectorRunner(ROOT, fileSystem); assertThat(detector.guessModuleName(prefix + "something.txt")).isEqualTo(EXPECTED_OSGI_MODULE); } @@ -113,9 +114,9 @@ private InputStream createEmptyStream() { return IOUtils.toInputStream("", "UTF-8"); } - private FileSystem createFileSystemStub(final Stub stub) { + private FileSystemFacade createFileSystemStub(final Stub stub) { try { - var fileSystem = mock(FileSystem.class); + var fileSystem = mock(FileSystemFacade.class); stub.apply(fileSystem); return fileSystem; } @@ -125,10 +126,10 @@ private FileSystem createFileSystemStub(final Stub stub) { } /** - * Stubs the {@link PackageDetectors.FileSystem} using a lambda. + * Stubs the {@link FileSystemFacade} using a lambda. */ @FunctionalInterface private interface Stub { - void apply(FileSystem f) throws IOException; + void apply(FileSystemFacade f) throws IOException; } } diff --git a/src/test/java/edu/hm/hafner/analysis/ModuleResolverTest.java b/src/test/java/edu/hm/hafner/analysis/ModuleResolverTest.java index 459d4cb8e..a8e33c5d1 100644 --- a/src/test/java/edu/hm/hafner/analysis/ModuleResolverTest.java +++ b/src/test/java/edu/hm/hafner/analysis/ModuleResolverTest.java @@ -27,11 +27,11 @@ void shouldAssignModuleName() { var withModule = builder.build(); report.add(withModule); - var detector = mock(ModuleDetector.class); + var detector = mock(ModuleDetectorRunner.class); when(detector.guessModuleName(fileName)).thenReturn("module1"); - var resolver = new ModuleResolver(); - resolver.run(report, detector); + var resolver = new ModuleResolver(detector); + resolver.run(report); assertThat(report.get(0)).hasModuleName("module1"); assertThat(report.get(1)).hasModuleName("module2"); diff --git a/src/test/java/edu/hm/hafner/analysis/OsgiModuleDetectorTest.java b/src/test/java/edu/hm/hafner/analysis/OsgiModuleDetectorTest.java index 9514cc641..11e02e85d 100644 --- a/src/test/java/edu/hm/hafner/analysis/OsgiModuleDetectorTest.java +++ b/src/test/java/edu/hm/hafner/analysis/OsgiModuleDetectorTest.java @@ -1,5 +1,7 @@ package edu.hm.hafner.analysis; +import java.util.List; + import org.apache.commons.lang3.StringUtils; import org.junit.jupiter.api.Test; @@ -32,11 +34,11 @@ String getProjectFileName() { @Test void shouldIdentifyModuleByReadingOsgiBundle() { var factory = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(new String[]{PATH_PREFIX_OSGI + OsgiModuleDetector.OSGI_BUNDLE}); + when(stub.find(any(), anyString())).thenReturn(List.of(PATH_PREFIX_OSGI + OsgiModuleDetector.OSGI_BUNDLE)); when(stub.open(anyString())).thenReturn(read(MANIFEST)); }); - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); assertThat(detector.guessModuleName(PREFIX + PATH_PREFIX_OSGI + "something.txt")) .isEqualTo(EXPECTED_OSGI_MODULE); @@ -49,11 +51,11 @@ void shouldIdentifyModuleByReadingOsgiBundle() { @Test void shouldIdentifyModuleByReadingOsgiBundleWithVendorInL10nProperties() { var factory = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn(new String[]{PATH_PREFIX_OSGI + OsgiModuleDetector.OSGI_BUNDLE}); + when(stub.find(any(), anyString())).thenReturn(List.of(PATH_PREFIX_OSGI + OsgiModuleDetector.OSGI_BUNDLE)); when(stub.open(anyString())).thenReturn(read(MANIFEST), read("l10n.properties")); }); - var detector = new ModuleDetector(ROOT, factory); + var detector = new ModuleDetectorRunner(ROOT, factory); var expectedName = "de.faktorlogik.prototyp (My Vendor)"; assertThat(detector.guessModuleName(PREFIX + PATH_PREFIX_OSGI + "something.txt")) @@ -67,12 +69,11 @@ void shouldIdentifyModuleByReadingOsgiBundleWithVendorInL10nProperties() { @Test void shouldIdentifyModuleByReadingOsgiBundleWithManifestName() { var fileSystem = createFileSystemStub(stub -> { - when(stub.find(any(), anyString())).thenReturn( - new String[]{PATH_PREFIX_OSGI + OsgiModuleDetector.OSGI_BUNDLE}); + when(stub.find(any(), anyString())).thenReturn(List.of(PATH_PREFIX_OSGI + OsgiModuleDetector.OSGI_BUNDLE)); when(stub.open(anyString())).thenReturn(read(MANIFEST_NAME), read("l10n.properties")); }); - var detector = new ModuleDetector(ROOT, fileSystem); + var detector = new ModuleDetectorRunner(ROOT, fileSystem); var expectedName = "My Bundle"; assertThat(detector.guessModuleName(PREFIX + PATH_PREFIX_OSGI + "something.txt")) diff --git a/src/test/java/edu/hm/hafner/analysis/PackageDetectorsTest.java b/src/test/java/edu/hm/hafner/analysis/PackageDetectorsTest.java deleted file mode 100644 index 4caee2cdb..000000000 --- a/src/test/java/edu/hm/hafner/analysis/PackageDetectorsTest.java +++ /dev/null @@ -1,44 +0,0 @@ -package edu.hm.hafner.analysis; - -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; - -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.CsvSource; - -import edu.hm.hafner.analysis.PackageDetectors.FileSystem; -import edu.hm.hafner.util.ResourceTest; - -import static org.assertj.core.api.Assertions.*; -import static org.mockito.Mockito.*; - -/** - * Tests the class {@link PackageDetectors}. - * - * @author Ullrich Hafner - */ -class PackageDetectorsTest extends ResourceTest { - @ParameterizedTest(name = "{index} => file={0}, expected package={1}") - @CsvSource({ - "MavenJavaTest.txt.java, hudson.plugins.tasks.util", - "ActionBinding.cs, Avaloq.SmartClient.Utilities", - "MavenJavaTest.txt, -", - "pom.xml, -"}) - void shouldExtractPackageNames(final String fileName, final String expectedPackage) throws IOException { - try (var stream = asInputStream(fileName)) { - var fileSystem = mock(FileSystem.class); - when(fileSystem.openFile(fileName)).thenReturn(stream); - - ArrayList detectors = new ArrayList<>(Arrays.asList( - new JavaPackageDetector(fileSystem), - new CSharpNamespaceDetector(fileSystem), - new KotlinPackageDetector(fileSystem) - )); - - assertThat(new PackageDetectors(detectors).detectPackageName(fileName, StandardCharsets.UTF_8)) - .isEqualTo(expectedPackage); - } - } -} diff --git a/src/test/java/edu/hm/hafner/analysis/PackageNameResolverBenchmark.java b/src/test/java/edu/hm/hafner/analysis/PackageNameResolverBenchmark.java index cd9740923..b10fcdd3d 100644 --- a/src/test/java/edu/hm/hafner/analysis/PackageNameResolverBenchmark.java +++ b/src/test/java/edu/hm/hafner/analysis/PackageNameResolverBenchmark.java @@ -13,7 +13,8 @@ import org.openjdk.jmh.annotations.Setup; import org.openjdk.jmh.annotations.State; -import edu.hm.hafner.analysis.PackageDetectors.FileSystem; +import edu.hm.hafner.util.PackageDetectorFactory; +import edu.hm.hafner.util.PackageDetectorFactory.FileSystemFacade; import static org.mockito.Mockito.*; @@ -22,6 +23,7 @@ * * @author Lion Kosiuk */ +@SuppressWarnings("NewClassNamingConvention") public class PackageNameResolverBenchmark extends AbstractBenchmark { private static final String FILE_NO_PACKAGE = "one.java"; private static final String FILE_WITH_PACKAGE = "two.java"; @@ -36,14 +38,15 @@ public class PackageNameResolverBenchmark extends AbstractBenchmark { @BenchmarkMode(Mode.Throughput) @Fork(value = 5, warmups = 5) public void benchmark1000IssuesTest(final BenchmarkState state) throws IOException { - var resolver = new PackageNameResolver(createFileSystemStub()); + var detectors = PackageDetectorFactory.createPackageDetectors(createFileSystemStub()); + var resolver = new PackageNameResolver(detectors); resolver.run(state.getReport(), StandardCharsets.UTF_8); } - private FileSystem createFileSystemStub() throws IOException { - var fileSystemStub = mock(FileSystem.class); - when(fileSystemStub.openFile(FILE_NO_PACKAGE)) - .thenReturn(new ByteArrayInputStream("package a.name;".getBytes(StandardCharsets.UTF_8))); + private FileSystemFacade createFileSystemStub() throws IOException { + var fileSystemStub = mock(FileSystemFacade.class); + when(fileSystemStub.openFile(FILE_NO_PACKAGE)).thenAnswer( + r -> new ByteArrayInputStream("package a.name;".getBytes(StandardCharsets.UTF_8))); return fileSystemStub; } @@ -63,11 +66,13 @@ public Report getReport() { */ @Setup(Level.Iteration) public void doSetup() { - report = new Report(); - for (int i = 0; i < 1000; i++) { - report.add(new IssueBuilder().setFileName(FILE_WITH_PACKAGE + i) - .setPackageName("existing") - .build()); + try (var issueBuilder = new IssueBuilder()) { + report = new Report(); + for (int i = 0; i < 1000; i++) { + report.add(issueBuilder.setFileName(FILE_WITH_PACKAGE + i) + .setPackageName("existing") + .build()); + } } } } diff --git a/src/test/java/edu/hm/hafner/analysis/PackageNameResolverTest.java b/src/test/java/edu/hm/hafner/analysis/PackageNameResolverTest.java index 43e1b6a3e..e3cb97ddb 100644 --- a/src/test/java/edu/hm/hafner/analysis/PackageNameResolverTest.java +++ b/src/test/java/edu/hm/hafner/analysis/PackageNameResolverTest.java @@ -6,7 +6,8 @@ import org.junit.jupiter.api.Test; -import edu.hm.hafner.analysis.PackageDetectors.FileSystem; +import edu.hm.hafner.util.PackageDetectorFactory; +import edu.hm.hafner.util.PackageDetectorFactory.FileSystemFacade; import static edu.hm.hafner.analysis.assertions.Assertions.*; import static org.mockito.Mockito.*; @@ -51,7 +52,7 @@ void shouldResolvePackage() throws IOException { var report = createIssues(); report.add(ISSUE_WITHOUT_PACKAGE); - var resolver = new PackageNameResolver(createFileSystemStub()); + var resolver = getResolver(); resolver.run(report, StandardCharsets.UTF_8); @@ -59,13 +60,19 @@ void shouldResolvePackage() throws IOException { assertThat(report.get(0)).hasFileName(FILE_NO_PACKAGE).hasPackageName("a.name"); } + private PackageNameResolver getResolver() throws IOException { + var detectors = PackageDetectorFactory.createPackageDetectors(createFileSystemStub()); + + return new PackageNameResolver(detectors); + } + @Test void shouldResolvePackageAndSkipExistingPackage() throws IOException { var report = createIssues(); report.add(ISSUE_WITHOUT_PACKAGE); report.add(ISSUE_WITH_PACKAGE); - var resolver = new PackageNameResolver(createFileSystemStub()); + var resolver = getResolver(); resolver.run(report, StandardCharsets.UTF_8); @@ -74,10 +81,10 @@ void shouldResolvePackageAndSkipExistingPackage() throws IOException { assertThat(report.get(1)).hasFileName(FILE_WITH_PACKAGE).hasPackageName("existing"); } - private FileSystem createFileSystemStub() throws IOException { - var fileSystemStub = mock(FileSystem.class); - when(fileSystemStub.openFile(FILE_NO_PACKAGE)) - .thenReturn(new ByteArrayInputStream("package a.name;".getBytes(StandardCharsets.UTF_8))); + private FileSystemFacade createFileSystemStub() throws IOException { + var fileSystemStub = mock(FileSystemFacade.class); + when(fileSystemStub.openFile(FILE_NO_PACKAGE)).thenAnswer( + r -> new ByteArrayInputStream("package a.name;".getBytes(StandardCharsets.UTF_8))); return fileSystemStub; } diff --git a/src/test/java/edu/hm/hafner/analysis/ReportTest.java b/src/test/java/edu/hm/hafner/analysis/ReportTest.java index c70e6f7e0..102ef01b7 100644 --- a/src/test/java/edu/hm/hafner/analysis/ReportTest.java +++ b/src/test/java/edu/hm/hafner/analysis/ReportTest.java @@ -22,7 +22,7 @@ import edu.hm.hafner.analysis.Report.IssuePrinter; import edu.hm.hafner.analysis.Report.StandardOutputPrinter; import edu.hm.hafner.analysis.assertions.SoftAssertions; -import edu.hm.hafner.analysis.parser.checkstyle.CheckStyleParser; +import edu.hm.hafner.analysis.parser.CheckStyleParser; import edu.hm.hafner.util.FilteredLog; import edu.hm.hafner.util.LineRange; import edu.hm.hafner.util.LineRangeList; @@ -894,7 +894,7 @@ void shouldPrintAllIssuesToPrintStream() { private Report readCheckStyleReport() { var fileName = "parser/checkstyle/all-severities.xml"; - var report = new CheckStyleParser().parseFile(read(fileName)); + var report = new CheckStyleParser().parse(read(fileName)); report.add(new IssueBuilder().setSeverity(Severity.WARNING_HIGH).setMessage("Severity High warning").build()); assertThat(report).hasSize(4); assertThat(report.getSeverities()).hasSize(4); diff --git a/src/test/java/edu/hm/hafner/analysis/parser/AntJavacParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/AntJavacParserTest.java index 4a81857a1..3b18f43cb 100644 --- a/src/test/java/edu/hm/hafner/analysis/parser/AntJavacParserTest.java +++ b/src/test/java/edu/hm/hafner/analysis/parser/AntJavacParserTest.java @@ -19,7 +19,7 @@ * Tests the class {@link AntJavacParser}. */ class AntJavacParserTest extends AbstractParserTest { - private static final int ALLOWED_TIMEOUT_IN_SECONDS = 5; + private static final Duration TIMEOUT = Duration.ofSeconds(5); AntJavacParserTest() { super("ant-javac.txt"); @@ -39,11 +39,11 @@ void issue67521IgnoreJavaDocWarnings() { */ @Test void issue55805() { - assertTimeoutPreemptively(Duration.ofSeconds(ALLOWED_TIMEOUT_IN_SECONDS), () -> parse("issue55805.txt")); + assertTimeoutPreemptively(TIMEOUT, () -> parse("issue55805.txt")); } /** - * Parses a warning log with JavaDoc warnings. + * Parses a warning log with Javadoc warnings. * * @see Issue 63346 */ @@ -209,7 +209,7 @@ void shouldReadErrors() { @Test void parseJapaneseWarnings() { // force to use windows-31j - the default encoding on Windows Japanese. - var warnings = createParser().parse( + var warnings = createParser().parseReport( new FileReaderFactory(getResourceAsFile("ant-javac-japanese.txt"), Charset.forName("windows-31j"))); diff --git a/src/test/java/edu/hm/hafner/analysis/parser/CcmParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/CcmParserTest.java index d236e2071..62f45690c 100644 --- a/src/test/java/edu/hm/hafner/analysis/parser/CcmParserTest.java +++ b/src/test/java/edu/hm/hafner/analysis/parser/CcmParserTest.java @@ -4,13 +4,12 @@ import edu.hm.hafner.analysis.Report; import edu.hm.hafner.analysis.Severity; import edu.hm.hafner.analysis.assertions.SoftAssertions; -import edu.hm.hafner.analysis.parser.ccm.CcmParser; import edu.hm.hafner.analysis.registry.AbstractParserTest; /** * Tests CCMParser. * - * @author Bruno P. Kinoshita - http://www.kinoshita.eti.br + * @author Bruno P. Kinoshita - ... */ class CcmParserTest extends AbstractParserTest { CcmParserTest() { diff --git a/src/test/java/edu/hm/hafner/analysis/parser/CheckStyleParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/CheckStyleParserTest.java index 24f842d58..00512d880 100644 --- a/src/test/java/edu/hm/hafner/analysis/parser/CheckStyleParserTest.java +++ b/src/test/java/edu/hm/hafner/analysis/parser/CheckStyleParserTest.java @@ -5,7 +5,6 @@ import edu.hm.hafner.analysis.Report; import edu.hm.hafner.analysis.Severity; import edu.hm.hafner.analysis.assertions.SoftAssertions; -import edu.hm.hafner.analysis.parser.checkstyle.CheckStyleParser; import edu.hm.hafner.analysis.registry.AbstractParserTest; import static edu.hm.hafner.analysis.assertions.Assertions.*; diff --git a/src/test/java/edu/hm/hafner/analysis/parser/checkstyle/CheckStyleRulesTest.java b/src/test/java/edu/hm/hafner/analysis/parser/CheckStyleRulesTest.java similarity index 89% rename from src/test/java/edu/hm/hafner/analysis/parser/checkstyle/CheckStyleRulesTest.java rename to src/test/java/edu/hm/hafner/analysis/parser/CheckStyleRulesTest.java index f399a0abc..d3b58ef3d 100644 --- a/src/test/java/edu/hm/hafner/analysis/parser/checkstyle/CheckStyleRulesTest.java +++ b/src/test/java/edu/hm/hafner/analysis/parser/CheckStyleRulesTest.java @@ -1,4 +1,4 @@ -package edu.hm.hafner.analysis.parser.checkstyle; +package edu.hm.hafner.analysis.parser; import java.util.regex.Pattern; @@ -36,7 +36,7 @@ void shouldLoadAndParseAllRules() { .contains("This check controls the style with the usage of annotations."); assertThat(rules.getRule("Undefined").getDescription()) .as("No default text available for undefined rule.") - .isEqualTo(Rule.UNDEFINED_DESCRIPTION); + .isEqualTo(CheckStyleParser.Rule.UNDEFINED_DESCRIPTION); assertThat(rules.getRule("DesignForExtension").getDescription()) .as("Wrong start of rule text.") .startsWith("

Since Checkstyle 3.1

"); @@ -46,9 +46,9 @@ void shouldLoadAndParseAllRules() { .contains("

public MyClass() {}      // empty constructor")
                 .matches(EMPTY_ANNOTATION);
 
-        for (Rule rule : rules.getRules()) {
+        for (CheckStyleParser.Rule rule : rules.getRules()) {
             assertThat(rule.getDescription()).as("Rule %s has no description", rule.getName())
-                    .isNotEqualTo(Rule.UNDEFINED_DESCRIPTION);
+                    .isNotEqualTo(CheckStyleParser.Rule.UNDEFINED_DESCRIPTION);
         }
     }
 }
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/dry/cpd/CpdParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/CpdParserTest.java
similarity index 94%
rename from src/test/java/edu/hm/hafner/analysis/parser/dry/cpd/CpdParserTest.java
rename to src/test/java/edu/hm/hafner/analysis/parser/CpdParserTest.java
index f5be3d409..042679b49 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/dry/cpd/CpdParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/CpdParserTest.java
@@ -1,4 +1,4 @@
-package edu.hm.hafner.analysis.parser.dry.cpd;
+package edu.hm.hafner.analysis.parser;
 
 import org.junit.jupiter.api.Test;
 
@@ -63,7 +63,7 @@ shift input parameter (twice) to leave only files to copy
             done""";
 
     CpdParserTest() {
-        super("cpd.xml");
+        super("cpd/cpd.xml");
     }
 
     @Override
@@ -123,7 +123,7 @@ void shouldAssignPriority() {
 
     private Report parse(final int highThreshold, final int normalThreshold) {
         var parser = new CpdParser(highThreshold, normalThreshold);
-        return parser.parse(createReaderFactory("issue12516.xml"));
+        return parser.parse(createReaderFactory("cpd/issue12516.xml"));
     }
 
     /**
@@ -134,7 +134,7 @@ private Report parse(final int highThreshold, final int normalThreshold) {
      */
     @Test
     void issue12516() {
-        var report = parse("issue12516.xml");
+        var report = parseCpd("issue12516.xml");
 
         assertThat(report).hasSize(2);
         var first = report.get(0);
@@ -154,6 +154,10 @@ void issue12516() {
         assertThat(((DuplicationGroup) additionalProperties).getCodeFragment()).isEqualTo(CODE_FRAGMENT);
     }
 
+    private Report parseCpd(final String fileName) {
+        return parse("cpd/" + fileName);
+    }
+
     /**
      * Verifies the parser on a report that contains four duplication (in two files each). The report is using
      * ISO-8859-1 encoding.
@@ -162,8 +166,7 @@ void issue12516() {
      */
     @Test
     void issue22356() {
-        var fileName = "issue22356.xml";
-        var report = parse(fileName);
+        var report = parseCpd("issue22356.xml");
 
         assertThat(report).hasSize(8);
     }
@@ -173,8 +176,7 @@ void issue22356() {
      */
     @Test
     void scanFileWithOneDuplication() {
-        var fileName = "one-cpd.xml";
-        var report = parse(fileName);
+        var report = parseCpd("one-cpd.xml");
 
         assertThat(report).hasSize(2);
 
@@ -200,14 +202,14 @@ private void assertThatReporterAndPublisherDuplicationsAreCorrectlyLinked(final
 
     @Test
     void shouldIgnoreOtherFile() {
-        var report = parse("otherfile.xml");
+        var report = parseCpd("otherfile.xml");
 
         assertThat(report).hasSize(0);
     }
 
     @Test
     void shouldReadFileWithWindowsEncoding() {
-        var report = parse("pmd-cpd.xml");
+        var report = parseCpd("pmd-cpd.xml");
 
         assertThat(report).hasSize(29);
     }
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/DScannerParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/DScannerParserTest.java
index 2ad347ffa..ac19fd098 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/DScannerParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/DScannerParserTest.java
@@ -58,7 +58,7 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
 
     @Test
     void shouldHandleIncompleteReports() {
-        var report = createParser().parse(createReaderFactory("dscanner-incomplete-report.json"));
+        var report = createParser().parseReport(createReaderFactory("dscanner-incomplete-report.json"));
         assertThat(report).hasSize(2);
         assertThat(report.getErrorMessages()).isEmpty();
     }
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/dry/dupfinder/DupFinderParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/DupFinderParserTest.java
similarity index 91%
rename from src/test/java/edu/hm/hafner/analysis/parser/dry/dupfinder/DupFinderParserTest.java
rename to src/test/java/edu/hm/hafner/analysis/parser/DupFinderParserTest.java
index 636b053be..2872df2b0 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/dry/dupfinder/DupFinderParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/DupFinderParserTest.java
@@ -1,4 +1,4 @@
-package edu.hm.hafner.analysis.parser.dry.dupfinder;
+package edu.hm.hafner.analysis.parser;
 
 import java.util.Objects;
 
@@ -31,7 +31,7 @@ class DupFinderParserTest extends AbstractParserTest {
     private static final String CODE_FRAGMENT = "if (items == null) throw new ArgumentNullException(\"items\");";
 
     DupFinderParserTest() {
-        super("with-sourcecode.xml");
+        super("dupfinder/with-sourcecode.xml");
     }
 
     @Override
@@ -83,7 +83,7 @@ private void assertThatReporterAndPublisherDuplicationsAreCorrectlyLinked(
      */
     @Test
     void scanFileWithoutSourceCode() {
-        var report = parse("without-sourcecode.xml");
+        var report = parseDupfinder("without-sourcecode.xml");
 
         assertThat(report).hasSize(2);
 
@@ -96,9 +96,13 @@ void scanFileWithoutSourceCode() {
         assertThat(publisher.getDescription()).isEmpty();
     }
 
+    private Report parseDupfinder(final String fileName) {
+        return parse("dupfinder/" + fileName);
+    }
+
     @Test
     void shouldIgnoreOtherFile() {
-        var report = parse("otherfile.xml");
+        var report = parseDupfinder("otherfile.xml");
 
         assertThat(report).hasSize(0);
     }
@@ -126,6 +130,6 @@ void shouldAssignPriority() {
 
     private Report parse(final int highThreshold, final int normalThreshold) {
         var parser = new DupFinderParser(highThreshold, normalThreshold);
-        return parser.parse(createReaderFactory("without-sourcecode.xml"));
+        return parser.parseReport(createReaderFactory("dupfinder/without-sourcecode.xml"));
     }
 }
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/EclipseXMLParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/EclipseXmlParserTest.java
similarity index 88%
rename from src/test/java/edu/hm/hafner/analysis/parser/EclipseXMLParserTest.java
rename to src/test/java/edu/hm/hafner/analysis/parser/EclipseXmlParserTest.java
index be50b0219..a0202900b 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/EclipseXMLParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/EclipseXmlParserTest.java
@@ -14,14 +14,14 @@
  *
  * @author Jason Faust
  */
-class EclipseXMLParserTest extends StructuredFileParserTest {
-    EclipseXMLParserTest() {
+class EclipseXmlParserTest extends StructuredFileParserTest {
+    EclipseXmlParserTest() {
         super("eclipse-withinfo.xml");
     }
 
     @Override
-    protected EclipseXMLParser createParser() {
-        return new EclipseXMLParser();
+    protected EclipseXmlParser createParser() {
+        return new EclipseXmlParser();
     }
 
     @Override
@@ -36,7 +36,7 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
                 .hasColumnEnd(0)
                 .hasFileName("C:/devenv/workspace/x/y/src/main/java/y/ECE.java")
                 .hasMessage("Type mismatch: cannot convert from float to Integer")
-                .hasCategory(EclipseXMLParser.TYPE);
+                .hasCategory(EclipseXmlParser.TYPE);
 
         softly.assertThat(report.get(1))
                 .hasSeverity(Severity.ERROR)
@@ -46,7 +46,7 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
                 .hasColumnEnd(0)
                 .hasFileName("C:/devenv/workspace/x/y/src/main/java/y/ECE.java")
                 .hasMessage("Dead code")
-                .hasCategory(EclipseXMLParser.POTENTIAL_PROGRAMMING_PROBLEM);
+                .hasCategory(EclipseXmlParser.POTENTIAL_PROGRAMMING_PROBLEM);
 
         softly.assertThat(report.get(2))
                 .hasSeverity(Severity.WARNING_NORMAL)
@@ -56,7 +56,7 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
                 .hasColumnEnd(0)
                 .hasFileName("C:/devenv/workspace/x/y/src/main/java/y/ECE.java")
                 .hasMessage("The value of the local variable x is not used")
-                .hasCategory(EclipseXMLParser.UNNECESSARY_CODE);
+                .hasCategory(EclipseXmlParser.UNNECESSARY_CODE);
 
         softly.assertThat(report.get(3))
                 .hasSeverity(Severity.WARNING_NORMAL)
@@ -67,7 +67,7 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
                 .hasFileName("C:/devenv/workspace/x/y/src/main/java/y/ECE.java")
                 .hasMessage(
                         "Statement unnecessarily nested within else clause. The corresponding then clause does not complete normally")
-                .hasCategory(EclipseXMLParser.UNNECESSARY_CODE);
+                .hasCategory(EclipseXmlParser.UNNECESSARY_CODE);
 
         softly.assertThat(report.get(4))
                 .hasSeverity(Severity.WARNING_LOW)
@@ -77,7 +77,7 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
                 .hasColumnEnd(0)
                 .hasFileName("C:/devenv/workspace/x/y/src/main/java/y/ECE.java")
                 .hasMessage("Comparing identical expressions")
-                .hasCategory(EclipseXMLParser.POTENTIAL_PROGRAMMING_PROBLEM);
+                .hasCategory(EclipseXmlParser.POTENTIAL_PROGRAMMING_PROBLEM);
 
         softly.assertThat(report.get(5))
                 .hasSeverity(Severity.WARNING_LOW)
@@ -87,7 +87,7 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
                 .hasColumnEnd(0)
                 .hasFileName("C:/devenv/workspace/x/y/src/main/java/y/ECE.java")
                 .hasMessage("The allocated object is never used")
-                .hasCategory(EclipseXMLParser.POTENTIAL_PROGRAMMING_PROBLEM);
+                .hasCategory(EclipseXmlParser.POTENTIAL_PROGRAMMING_PROBLEM);
     }
 
     /**
@@ -105,7 +105,7 @@ void javadocCategory() {
                     .hasLineStart(1)
                     .hasMessage("A default nullness annotation has not been specified for the package a")
                     .hasFileName("C:/devenv/workspace/x/y/src/main/java/a/B.java")
-                    .hasCategory(EclipseXMLParser.POTENTIAL_PROGRAMMING_PROBLEM);
+                    .hasCategory(EclipseXmlParser.POTENTIAL_PROGRAMMING_PROBLEM);
 
             softly.assertThat(warnings.get(1))
                     .hasSeverity(Severity.WARNING_NORMAL)
@@ -133,7 +133,7 @@ void javadocCategory() {
                     .hasLineStart(8)
                     .hasMessage("The value of the local variable unused is not used")
                     .hasFileName("C:/devenv/workspace/x/y/src/main/java/a/B.java")
-                    .hasCategory(EclipseXMLParser.UNNECESSARY_CODE);
+                    .hasCategory(EclipseXmlParser.UNNECESSARY_CODE);
         }
     }
 
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/findbugs/FindBugsMessagesTest.java b/src/test/java/edu/hm/hafner/analysis/parser/FindBugsMessagesTest.java
similarity index 95%
rename from src/test/java/edu/hm/hafner/analysis/parser/findbugs/FindBugsMessagesTest.java
rename to src/test/java/edu/hm/hafner/analysis/parser/FindBugsMessagesTest.java
index a5d9c75d0..df9b61d22 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/findbugs/FindBugsMessagesTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/FindBugsMessagesTest.java
@@ -1,4 +1,4 @@
-package edu.hm.hafner.analysis.parser.findbugs;
+package edu.hm.hafner.analysis.parser;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -7,7 +7,7 @@
 import org.junit.jupiter.api.Test;
 import org.xml.sax.SAXException;
 
-import edu.hm.hafner.analysis.parser.findbugs.FindBugsMessages.Pattern;
+import edu.hm.hafner.analysis.parser.FindBugsMessages.Pattern;
 
 import static org.assertj.core.api.Assertions.*;
 
@@ -72,7 +72,7 @@ void issue55707() {
     }
 
     private List readMessages(final String fileName) {
-        try (var file = read(fileName)) {
+        try (var file = read("findbugs/" + fileName)) {
             return new FindBugsMessages().parse(file);
         }
         catch (IOException | SAXException e) {
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/findbugs/FindBugsParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/FindBugsParserTest.java
similarity index 92%
rename from src/test/java/edu/hm/hafner/analysis/parser/findbugs/FindBugsParserTest.java
rename to src/test/java/edu/hm/hafner/analysis/parser/FindBugsParserTest.java
index 6a1852c2e..7a1c819b3 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/findbugs/FindBugsParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/FindBugsParserTest.java
@@ -1,25 +1,24 @@
-package edu.hm.hafner.analysis.parser.findbugs;
+package edu.hm.hafner.analysis.parser;
 
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 
 import org.junit.jupiter.api.Test;
 
 import edu.hm.hafner.analysis.Issue;
-import edu.hm.hafner.analysis.IssueBuilder;
 import edu.hm.hafner.analysis.ReaderFactory;
 import edu.hm.hafner.analysis.Report;
+import edu.hm.hafner.analysis.Report.IssueType;
 import edu.hm.hafner.analysis.Severity;
 import edu.hm.hafner.analysis.assertions.SoftAssertions;
-import edu.hm.hafner.analysis.parser.findbugs.FindBugsParser.PriorityProperty;
-import edu.hm.hafner.analysis.parser.findbugs.FindBugsParser.XmlBugInstance;
+import edu.hm.hafner.analysis.parser.FindBugsParser.PriorityProperty;
+import edu.hm.hafner.analysis.parser.FindBugsParser.XmlBugInstance;
 
 import static edu.hm.hafner.analysis.assertions.Assertions.*;
-import static edu.hm.hafner.analysis.parser.findbugs.FindBugsParser.PriorityProperty.*;
+import static edu.hm.hafner.analysis.parser.FindBugsParser.PriorityProperty.*;
 import static org.mockito.Mockito.*;
 
 /**
@@ -35,12 +34,16 @@ private Report parseFile(final String fileName, final PriorityProperty priorityP
         var readerFactory = mock(ReaderFactory.class);
         when(readerFactory.create()).thenAnswer(
                 mock -> new InputStreamReader(read(fileName), StandardCharsets.UTF_8));
-        return new FindBugsParser(priorityProperty).parse(readerFactory,
-                Collections.emptyList(), new IssueBuilder());
+        when(readerFactory.getFileName()).thenReturn(fileName);
+        var parser = new FindBugsParser(priorityProperty);
+        parser.setId("findbugs");
+        parser.setName("FindBugs");
+        parser.setType(IssueType.BUG);
+        return parser.parse(readerFactory);
     }
 
     private InputStream read(final String fileName) {
-        return FindBugsParserTest.class.getResourceAsStream(fileName);
+        return FindBugsParserTest.class.getResourceAsStream("findbugs/" + fileName);
     }
 
     /**
@@ -54,11 +57,15 @@ void shouldAssignCorrectSeverity() {
         assertThat(confidenceReport).hasSize(12);
         assertThatReportHasSeverities(confidenceReport,
                 0, 1, 11, 0);
+        assertThat(confidenceReport).hasToString(
+                "FindBugs (findbugs): 12 bugs (high: 1, normal: 11)");
 
         var rankReport = parseFile("findbugs-severities.xml", RANK);
         assertThat(rankReport).hasSize(12);
         assertThatReportHasSeverities(rankReport,
                 0, 0, 0, 12);
+        assertThat(rankReport).hasToString(
+                "FindBugs (findbugs): 12 bugs (low: 12)");
     }
 
     private void assertThatReportHasSeverities(final Report report, final int expectedSizeError,
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/FxcopParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/FxcopParserTest.java
index 7aca0f2c2..e6b7dafef 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/FxcopParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/FxcopParserTest.java
@@ -5,7 +5,6 @@
 import edu.hm.hafner.analysis.Report;
 import edu.hm.hafner.analysis.Severity;
 import edu.hm.hafner.analysis.assertions.SoftAssertions;
-import edu.hm.hafner.analysis.parser.fxcop.FxCopParser;
 import edu.hm.hafner.analysis.registry.AbstractParserTest;
 
 import static org.assertj.core.api.Assertions.*;
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/Gcc4CompilerParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/Gcc4CompilerParserTest.java
index 37cbb3881..e13b9d666 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/Gcc4CompilerParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/Gcc4CompilerParserTest.java
@@ -253,7 +253,7 @@ void shouldNotReportGccWarnings() {
 
     @Test
     void shouldResolveAbsolutePaths() {
-        var warnings = createParser().parse(createReaderFactory("absolute-paths.txt"));
+        var warnings = createParser().parseReport(createReaderFactory("absolute-paths.txt"));
 
         assertThat(warnings).hasSize(188);
 
@@ -269,7 +269,7 @@ void shouldResolveAbsolutePaths() {
      */
     @Test
     void issue66835() {
-        var warnings = createParser().parse(createReaderFactory("issue66835.makefile.log"));
+        var warnings = createParser().parseReport(createReaderFactory("issue66835.makefile.log"));
 
         assertThat(warnings).hasSize(3);
 
@@ -293,7 +293,7 @@ void issue66835() {
      */
     @Test
     void issue66923() {
-        var warnings = createParser().parse(createReaderFactory("issue66923.txt"));
+        var warnings = createParser().parseReport(createReaderFactory("issue66923.txt"));
 
         assertThat(warnings).hasSize(0);
         assertThat(warnings).doesNotHaveErrors();
@@ -306,7 +306,7 @@ void issue66923() {
      */
     @Test
     void issue69242() {
-        var warnings = createParser().parse(createReaderFactory("issue69242.txt"));
+        var warnings = createParser().parseReport(createReaderFactory("issue69242.txt"));
 
         assertThat(warnings).hasSize(2);
         assertThat(warnings).doesNotHaveErrors();
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/GendarmeParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/GendarmeParserTest.java
index 654001fcb..21613a902 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/GendarmeParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/GendarmeParserTest.java
@@ -6,7 +6,6 @@
 import edu.hm.hafner.analysis.Report;
 import edu.hm.hafner.analysis.Severity;
 import edu.hm.hafner.analysis.assertions.SoftAssertions;
-import edu.hm.hafner.analysis.parser.gendarme.GendarmeParser;
 import edu.hm.hafner.analysis.registry.AbstractParserTest;
 
 /**
@@ -17,7 +16,7 @@
  */
 class GendarmeParserTest extends AbstractParserTest {
     protected GendarmeParserTest() {
-        super("gendarme/Gendarme.xml");
+        super("Gendarme.xml");
     }
 
     @Override
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/IarParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/IarParserTest.java
index 3b3e8f64f..71f22eed1 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/IarParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/IarParserTest.java
@@ -87,7 +87,7 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
      */
     @Test
     void issue58159Utf8() {
-        var warnings = createParser().parse(
+        var warnings = createParser().parseReport(
                 new FileReaderFactory(getResourceAsFile("issue58159-2.txt")));
 
         var collect = warnings.stream().map(Objects::toString).collect(Collectors.joining("\n"));
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/JcReportParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/JcReportParserTest.java
index 3eb1aeb14..196f630d8 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/JcReportParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/JcReportParserTest.java
@@ -7,7 +7,6 @@
 import edu.hm.hafner.analysis.Report;
 import edu.hm.hafner.analysis.Severity;
 import edu.hm.hafner.analysis.assertions.SoftAssertions;
-import edu.hm.hafner.analysis.parser.jcreport.JcReportParser;
 import edu.hm.hafner.analysis.registry.AbstractParserTest;
 
 import static edu.hm.hafner.analysis.assertions.Assertions.*;
@@ -36,7 +35,7 @@ void testGetWarningList() {
     /**
      * This test assures that all properties within Report-, File- and Item-Objects are parsed correctly. Not all
      * properties are needed to create a warning. So it was decided to keep them anyway in case Jenkins is modified to
-     * contain more information in the Warning-Objects. For reasons of simplicity only a Report with 1 file and 1 item
+     * contain more information in the Warning-Objects. For reasons of simplicity, only a Report with 1 file and 1 item
      * was created.
      */
     @Test
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/JSLintParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/JsLintParserTest.java
similarity index 89%
rename from src/test/java/edu/hm/hafner/analysis/parser/JSLintParserTest.java
rename to src/test/java/edu/hm/hafner/analysis/parser/JsLintParserTest.java
index 4ebd1cf56..12e219196 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/JSLintParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/JsLintParserTest.java
@@ -5,6 +5,7 @@
 import edu.hm.hafner.analysis.Report;
 import edu.hm.hafner.analysis.Severity;
 import edu.hm.hafner.analysis.assertions.SoftAssertions;
+import edu.hm.hafner.analysis.parser.LintParser.JsLintXmlSaxParser;
 import edu.hm.hafner.analysis.registry.AbstractParserTest;
 
 import static edu.hm.hafner.analysis.assertions.Assertions.*;
@@ -14,10 +15,10 @@
  *
  * @author Gavin Mogan
  */
-class JSLintParserTest extends AbstractParserTest {
+class JsLintParserTest extends AbstractParserTest {
     private static final String EXPECTED_FILE_NAME = "duckworth/hudson-jslint-freestyle/src/prototype.js";
 
-    JSLintParserTest() {
+    JsLintParserTest() {
         super("jslint/multi.xml");
     }
 
@@ -35,7 +36,7 @@ void issue19127() {
         try (var softly = new SoftAssertions()) {
             softly.assertThat(warnings.get(0))
                     .hasSeverity(Severity.WARNING_HIGH)
-                    .hasCategory(LintParser.JSLintXmlSaxParser.CATEGORY_UNDEFINED_VARIABLE)
+                    .hasCategory(JsLintXmlSaxParser.CATEGORY_UNDEFINED_VARIABLE)
                     .hasLineStart(3)
                     .hasLineEnd(3)
                     .hasMessage("'window' is not defined.")
@@ -74,7 +75,7 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
 
         softly.assertThat(report.get(0))
                 .hasSeverity(Severity.WARNING_HIGH)
-                .hasCategory(LintParser.JSLintXmlSaxParser.CATEGORY_PARSING)
+                .hasCategory(JsLintXmlSaxParser.CATEGORY_PARSING)
                 .hasLineStart(10)
                 .hasLineEnd(10)
                 .hasMessage("Expected 'Version' to have an indentation at 5 instead at 3.")
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/JsonLogParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/JsonLogParserTest.java
index 62192af86..59aa23ab0 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/JsonLogParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/JsonLogParserTest.java
@@ -37,7 +37,6 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
                 .hasMessage("msg")
                 .hasPackageName("pn")
                 .hasModuleName("mdl")
-                .hasOrigin("orgn")
                 .hasFingerprint("fgpt")
                 .hasAdditionalProperties("ap")
                 .hasId(UUID.fromString("823b92b6-98eb-41c4-83ce-b6ec1ed6f98f"));
@@ -99,7 +98,7 @@ void shouldReportLineBreak() {
     @Test
     void emptyReport() {
         var parser = createParser();
-        var report = parser.parse(createReaderFactory("json-issues-empty.txt"));
+        var report = parser.parseReport(createReaderFactory("json-issues-empty.txt"));
         assertThat(report).hasSize(0);
     }
 
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/JsonParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/JsonParserTest.java
index 19928eb66..0a5093c4e 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/JsonParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/JsonParserTest.java
@@ -34,7 +34,6 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
                 .hasMessage("message")
                 .hasPackageName("packageName")
                 .hasModuleName("moduleName")
-                .hasOrigin("origin")
                 .hasFingerprint("9CED6585900DD3CFB97B914A3CEB0E79")
                 .hasAdditionalProperties("additionalProperties")
                 .hasId(UUID.fromString("e7011244-2dab-4a54-a27b-2d0697f8f813"));
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/OELintAdvParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/OeLintAdvParserTest.java
similarity index 95%
rename from src/test/java/edu/hm/hafner/analysis/parser/OELintAdvParserTest.java
rename to src/test/java/edu/hm/hafner/analysis/parser/OeLintAdvParserTest.java
index 8fa49bb2a..3efecaa16 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/OELintAdvParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/OeLintAdvParserTest.java
@@ -7,10 +7,10 @@
 import edu.hm.hafner.analysis.registry.AbstractParserTest;
 
 /**
- * Tests the class {@link OELintAdvParser}.
+ * Tests the class {@link OeLintAdvParser}.
  */
-class OELintAdvParserTest extends AbstractParserTest {
-    OELintAdvParserTest() {
+class OeLintAdvParserTest extends AbstractParserTest {
+    OeLintAdvParserTest() {
         super("oelint-adv.txt");
     }
 
@@ -69,6 +69,6 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
 
     @Override
     protected IssueParser createParser() {
-        return new OELintAdvParser();
+        return new OeLintAdvParser();
     }
 }
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/OTDockerLintParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/OtDockerLintParserTest.java
similarity index 85%
rename from src/test/java/edu/hm/hafner/analysis/parser/OTDockerLintParserTest.java
rename to src/test/java/edu/hm/hafner/analysis/parser/OtDockerLintParserTest.java
index 37a9304d6..3592df16c 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/OTDockerLintParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/OtDockerLintParserTest.java
@@ -15,12 +15,12 @@
 import static edu.hm.hafner.analysis.assertions.Assertions.*;
 
 /**
- * Tests the class {@link OTDockerLintParser}.
+ * Tests the class {@link OtDockerLintParser}.
  *
  * @author Abhishek Dubey
  */
-class OTDockerLintParserTest extends AbstractParserTest {
-    OTDockerLintParserTest() {
+class OtDockerLintParserTest extends AbstractParserTest {
+    OtDockerLintParserTest() {
         super("ot-docker-linter.json");
     }
 
@@ -42,14 +42,14 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
 
     @Override
     protected IssueParser createParser() {
-        return new OTDockerLintParser();
+        return new OtDockerLintParser();
     }
 
     @Test
     void accepts() {
-        assertThat(new OTDockerLintParser().accepts(
+        assertThat(new OtDockerLintParser().accepts(
                 new FileReaderFactory(FileSystems.getDefault().getPath("lint.json")))).isTrue();
-        assertThat(new OTDockerLintParser().accepts(
+        assertThat(new OtDockerLintParser().accepts(
                 new FileReaderFactory(FileSystems.getDefault().getPath("foo.txt")))).isFalse();
     }
 
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/ParserBenchmark.java b/src/test/java/edu/hm/hafner/analysis/parser/ParserBenchmark.java
index b0448d6ad..1676cfe4d 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/ParserBenchmark.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/ParserBenchmark.java
@@ -17,8 +17,6 @@
 import edu.hm.hafner.analysis.AbstractBenchmark;
 import edu.hm.hafner.analysis.FileReaderFactory;
 import edu.hm.hafner.analysis.ReaderFactory;
-import edu.hm.hafner.analysis.parser.checkstyle.CheckStyleParser;
-import edu.hm.hafner.analysis.parser.pmd.PmdParser;
 
 /**
  * Performance benchmarks for analysis parsers parsing xml files.
@@ -39,7 +37,7 @@ public class ParserBenchmark extends AbstractBenchmark {
      */
     @Benchmark
     public void benchmarkCheckStyleParser(final BenchmarkState state, final Blackhole blackhole) {
-        var report = new CheckStyleParser().parse(state.getCheckstyleFileReaderFactory());
+        var report = new CheckStyleParser().parseReport(state.getCheckstyleFileReaderFactory());
 
         blackhole.consume(report);
     }
@@ -54,7 +52,7 @@ public void benchmarkCheckStyleParser(final BenchmarkState state, final Blackhol
      */
     @Benchmark
     public void benchmarkPmdParser(final BenchmarkState state, final Blackhole blackhole) {
-        var report = new PmdParser().parse(state.getPmdFileReaderFactory());
+        var report = new PmdParser().parseReport(state.getPmdFileReaderFactory());
 
         blackhole.consume(report);
     }
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/pmd/PmdMessagesTest.java b/src/test/java/edu/hm/hafner/analysis/parser/PmdMessagesTest.java
similarity index 94%
rename from src/test/java/edu/hm/hafner/analysis/parser/pmd/PmdMessagesTest.java
rename to src/test/java/edu/hm/hafner/analysis/parser/PmdMessagesTest.java
index d9dcc7901..55de4a15c 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/pmd/PmdMessagesTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/PmdMessagesTest.java
@@ -1,4 +1,4 @@
-package edu.hm.hafner.analysis.parser.pmd;
+package edu.hm.hafner.analysis.parser;
 
 import org.junit.jupiter.api.Test;
 
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/PmdParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/PmdParserTest.java
index c5c370916..45162cd9a 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/PmdParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/PmdParserTest.java
@@ -6,7 +6,6 @@
 import edu.hm.hafner.analysis.Report;
 import edu.hm.hafner.analysis.Severity;
 import edu.hm.hafner.analysis.assertions.SoftAssertions;
-import edu.hm.hafner.analysis.parser.pmd.PmdParser;
 import edu.hm.hafner.analysis.registry.AbstractParserTest;
 
 import static edu.hm.hafner.analysis.assertions.Assertions.*;
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/pvsstudio/PvsStudioParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/PvsStudioParserTest.java
similarity index 71%
rename from src/test/java/edu/hm/hafner/analysis/parser/pvsstudio/PvsStudioParserTest.java
rename to src/test/java/edu/hm/hafner/analysis/parser/PvsStudioParserTest.java
index 21a0c47c1..fb5d84ec3 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/pvsstudio/PvsStudioParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/PvsStudioParserTest.java
@@ -1,4 +1,4 @@
-package edu.hm.hafner.analysis.parser.pvsstudio;
+package edu.hm.hafner.analysis.parser;
 
 import java.util.Locale;
 
@@ -26,12 +26,12 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
 
         assertThatReportHasSeverities(report, 1, 5, 24, 3);
 
-        softly.assertThat(report.filter(Issue.byType(AnalyzerType.GENERAL_MESSAGE)).getSize()).isEqualTo(7);
-        softly.assertThat(report.filter(Issue.byType(AnalyzerType.OPTIMIZATION_MESSAGE)).getSize()).isEqualTo(1);
-        softly.assertThat(report.filter(Issue.byType(AnalyzerType.CUSTOMER_SPECIFIC_MESSAGE)).getSize()).isEqualTo(3);
-        softly.assertThat(report.filter(Issue.byType(AnalyzerType.VIVA_64_MESSAGE)).getSize()).isEqualTo(11);
-        softly.assertThat(report.filter(Issue.byType(AnalyzerType.MISRA_MESSAGE)).getSize()).isEqualTo(9);
-        softly.assertThat(report.filter(Issue.byType(AnalyzerType.UNKNOWN_MESSAGE)).getSize()).isEqualTo(2);
+        softly.assertThat(report.filter(Issue.byType(PvsStudioParser.AnalyzerType.GENERAL_MESSAGE)).getSize()).isEqualTo(7);
+        softly.assertThat(report.filter(Issue.byType(PvsStudioParser.AnalyzerType.OPTIMIZATION_MESSAGE)).getSize()).isEqualTo(1);
+        softly.assertThat(report.filter(Issue.byType(PvsStudioParser.AnalyzerType.CUSTOMER_SPECIFIC_MESSAGE)).getSize()).isEqualTo(3);
+        softly.assertThat(report.filter(Issue.byType(PvsStudioParser.AnalyzerType.VIVA_64_MESSAGE)).getSize()).isEqualTo(11);
+        softly.assertThat(report.filter(Issue.byType(PvsStudioParser.AnalyzerType.MISRA_MESSAGE)).getSize()).isEqualTo(9);
+        softly.assertThat(report.filter(Issue.byType(PvsStudioParser.AnalyzerType.UNKNOWN_MESSAGE)).getSize()).isEqualTo(2);
 
         softly.assertThat(report.get(0))
                 .hasSeverity(Severity.ERROR)
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/pylint/PyLintDescriptionsTest.java b/src/test/java/edu/hm/hafner/analysis/parser/PyLintDescriptionsTest.java
similarity index 92%
rename from src/test/java/edu/hm/hafner/analysis/parser/pylint/PyLintDescriptionsTest.java
rename to src/test/java/edu/hm/hafner/analysis/parser/PyLintDescriptionsTest.java
index d304cd9f3..edfda622a 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/pylint/PyLintDescriptionsTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/PyLintDescriptionsTest.java
@@ -1,4 +1,4 @@
-package edu.hm.hafner.analysis.parser.pylint;
+package edu.hm.hafner.analysis.parser;
 
 import org.junit.jupiter.api.Test;
 
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/dry/simian/SimianParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/SimianParserTest.java
similarity index 90%
rename from src/test/java/edu/hm/hafner/analysis/parser/dry/simian/SimianParserTest.java
rename to src/test/java/edu/hm/hafner/analysis/parser/SimianParserTest.java
index 0b5a2af09..71591e622 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/dry/simian/SimianParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/SimianParserTest.java
@@ -1,4 +1,4 @@
-package edu.hm.hafner.analysis.parser.dry.simian;
+package edu.hm.hafner.analysis.parser;
 
 import java.util.Locale;
 
@@ -19,7 +19,7 @@ class SimianParserTest extends AbstractParserTest {
     private static final String MAVEN_BUILD = "C:/java/hudson/maven/MavenBuild.java";
 
     SimianParserTest() {
-        super("onefile.xml");
+        super("simian/onefile.xml");
     }
 
     @Override
@@ -55,7 +55,7 @@ protected void assertThatIssuesArePresent(final Report report,
 
     @Test
     void shouldFindOneDuplicationInTwoFiles() {
-        var report = parse("twofile.xml");
+        var report = parseSimian("twofile.xml");
 
         assertThat(report).hasSize(2);
 
@@ -69,9 +69,13 @@ void shouldFindOneDuplicationInTwoFiles() {
                 .hasSeverity(Severity.WARNING_LOW);
     }
 
+    private Report parseSimian(final String fileName) {
+        return parse("simian/" + fileName);
+    }
+
     @Test
     void shouldFindTwoDuplicationsInTwoFiles() {
-        var report = parse("twosets.xml");
+        var report = parseSimian("twosets.xml");
 
         assertThat(report).hasSize(4);
 
@@ -98,7 +102,7 @@ void shouldFindTwoDuplicationsInTwoFiles() {
 
     @Test
     void shouldFindOneDuplicationInFourFiles() {
-        var report = parse("fourfile.xml");
+        var report = parseSimian("fourfile.xml");
 
         assertThat(report).hasSize(4);
 
@@ -114,14 +118,14 @@ private String getFileName(final int number) {
 
     @Test
     void shouldSupportSimianParserVersion2331() {
-        var report = parse("simian-2.3.31.xml");
+        var report = parseSimian("simian-2.3.31.xml");
 
         assertThat(report).hasSize(132);
     }
 
     @Test
     void shouldIgnoreOtherFile() {
-        var report = parse("otherfile.xml");
+        var report = parseSimian("otherfile.xml");
 
         assertThat(report).hasSize(0);
     }
@@ -149,6 +153,6 @@ void shouldAssignPriority() {
 
     private Report parse(final int highThreshold, final int normalThreshold) {
         var parser = new SimianParser(highThreshold, normalThreshold);
-        return parser.parse(createReaderFactory("twofile.xml"));
+        return parser.parseReport(createReaderFactory("simian/twofile.xml"));
     }
 }
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/SimulinkCheckParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/SimulinkCheckParserTest.java
index d1cbc75ec..f24cba0ee 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/SimulinkCheckParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/SimulinkCheckParserTest.java
@@ -53,5 +53,9 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
                 .hasModuleName("SW01-181.NestedComments");
         softly.assertThat(report.get(9))
                 .hasModuleName("SW02-430.CompareFloatEquality");
+
+        for (int i = 0; i < report.size(); i++) {
+            softly.assertThat(report.get(i)).hasFileName("sldemo_mdladv.slx");
+        }
     }
 }
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/ValeParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/ValeParserTest.java
new file mode 100644
index 000000000..b43bbef28
--- /dev/null
+++ b/src/test/java/edu/hm/hafner/analysis/parser/ValeParserTest.java
@@ -0,0 +1,47 @@
+package edu.hm.hafner.analysis.parser;
+
+import edu.hm.hafner.analysis.IssueParser;
+import edu.hm.hafner.analysis.Report;
+import edu.hm.hafner.analysis.Severity;
+import edu.hm.hafner.analysis.assertions.SoftAssertions;
+import edu.hm.hafner.analysis.registry.AbstractParserTest;
+
+class ValeParserTest extends AbstractParserTest {
+    ValeParserTest() {
+        super("vale-report.json");
+    }
+
+    @Override
+    protected void assertThatIssuesArePresent(final Report report, final SoftAssertions softly) {
+        softly.assertThat(report).hasSize(3).hasDuplicatesSize(0);
+        softly.assertThat(report.get(0))
+                .hasFileName("file1.adoc")
+                .hasDescription("RedHat.SentenceLength")
+                .hasMessage("Try to keep sentences to an average of 32 words or fewer.")
+                .hasLineStart(10)
+                .hasColumnStart(1)
+                .hasColumnEnd(5)
+                .hasSeverity(Severity.WARNING_LOW);
+        softly.assertThat(report.get(1))
+                .hasFileName("file2.adoc")
+                .hasDescription("RedHat.TermsWarnings")
+                .hasMessage("Consider using 'might' or 'can' rather than 'may' unless updating existing content that uses the term.")
+                .hasLineStart(39)
+                .hasColumnStart(143)
+                .hasColumnEnd(145)
+                .hasSeverity(Severity.WARNING_NORMAL);
+        softly.assertThat(report.get(2))
+                .hasFileName("file2.adoc")
+                .hasDescription("RedHat.Using")
+                .hasMessage("Use 'by using' instead of 'using' when it follows a noun for clarity and grammatical correctness.")
+                .hasLineStart(51)
+                .hasColumnStart(44)
+                .hasColumnEnd(64)
+                .hasSeverity(Severity.ERROR);
+    }
+
+    @Override
+    protected IssueParser createParser() {
+        return new ValeParser();
+    }
+}
diff --git a/src/test/java/edu/hm/hafner/analysis/parser/XmlParserTest.java b/src/test/java/edu/hm/hafner/analysis/parser/XmlParserTest.java
index 370061eb8..c58338286 100644
--- a/src/test/java/edu/hm/hafner/analysis/parser/XmlParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/parser/XmlParserTest.java
@@ -49,7 +49,6 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
                 .hasDescription("description")
                 .hasPackageName("package-name")
                 .hasModuleName("module-name")
-                .hasOrigin("origin")
                 .hasFingerprint("fingerprint")
                 .hasAdditionalProperties("")
                 .hasOnlyLineRanges(new LineRange(5, 6));
@@ -63,7 +62,7 @@ protected XmlParser createParser() {
     @Test
     void shouldParseWithCustomPath() {
         var parser = new XmlParser(CUSTOM_PATH);
-        var report = parser.parse(createReaderFactory(ISSUES_CUSTOM_PATH_FILE));
+        var report = parser.parseReport(createReaderFactory(ISSUES_CUSTOM_PATH_FILE));
         var iterator = report.iterator();
         try (var softly = new SoftAssertions()) {
             softly.assertThat(report)
@@ -81,7 +80,6 @@ void shouldParseWithCustomPath() {
                     .hasDescription("description")
                     .hasPackageName("package-name")
                     .hasModuleName("module-name")
-                    .hasOrigin("origin")
                     .hasFingerprint("fingerprint")
                     .hasAdditionalProperties("")
                     .hasOnlyLineRanges(new LineRange(5, 6));
@@ -99,7 +97,6 @@ void shouldParseWithCustomPath() {
                     .hasDescription("description")
                     .hasPackageName("package-name")
                     .hasModuleName("module-name")
-                    .hasOrigin("origin")
                     .hasFingerprint("fingerprint")
                     .hasAdditionalProperties("")
                     .hasOnlyLineRanges(new LineRange(42, 43), new LineRange(44, 45));
@@ -113,7 +110,7 @@ void shouldAcceptSampleFile() {
 
     @Test
     void shouldProduceIssuesEvenIfThereAreIncompatibleValues() {
-        var report = createParser().parse(createReaderFactory(ISSUES_INCOMPATIBLE_VALUE));
+        var report = createParser().parseReport(createReaderFactory(ISSUES_INCOMPATIBLE_VALUE));
         Iterator iterator = report.iterator();
         try (var softly = new SoftAssertions()) {
             softly.assertThat(report)
@@ -131,7 +128,6 @@ void shouldProduceIssuesEvenIfThereAreIncompatibleValues() {
                     .hasDescription("")
                     .hasPackageName("-")
                     .hasModuleName("-")
-                    .hasOrigin("")
                     .hasReference("")
                     .hasFingerprint("-")
                     .hasAdditionalProperties("")
diff --git a/src/test/java/edu/hm/hafner/analysis/registry/AbstractParserTest.java b/src/test/java/edu/hm/hafner/analysis/registry/AbstractParserTest.java
index 600733bfa..26a30f32d 100644
--- a/src/test/java/edu/hm/hafner/analysis/registry/AbstractParserTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/registry/AbstractParserTest.java
@@ -85,7 +85,7 @@ void shouldRegisterParser() {
 
         Set> parsers = parserRegistry.getAllDescriptors()
                 .stream()
-                .map(ParserDescriptor::createParser)
+                .map(ParserDescriptor::create)
                 .map(IssueParser::getClass)
                 .collect(Collectors.toSet());
 
@@ -95,7 +95,7 @@ void shouldRegisterParser() {
                 .map(CompositeParserDescriptor::createParsers)
                 .flatMap(Collection::stream)
                 .map(IssueParser::getClass)
-                .collect(Collectors.toList());
+                .toList();
         parsers.addAll(compositeParsers);
 
         assertThat(parsers)
@@ -171,7 +171,7 @@ void shouldHandleEmptyFile() {
      * @return the found issues
      */
     protected Report parse(final String fileName) {
-        return createParser().parseFile(createReaderFactory(fileName));
+        return createParser().parse(createReaderFactory(fileName));
     }
 
     /**
diff --git a/src/test/java/edu/hm/hafner/analysis/registry/ParserRegistryTest.java b/src/test/java/edu/hm/hafner/analysis/registry/ParserRegistryTest.java
index d16108447..b95d7bf70 100644
--- a/src/test/java/edu/hm/hafner/analysis/registry/ParserRegistryTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/registry/ParserRegistryTest.java
@@ -7,6 +7,7 @@
 import org.junit.jupiter.api.Test;
 
 import edu.hm.hafner.analysis.FileReaderFactory;
+import edu.hm.hafner.analysis.Report.IssueType;
 import edu.hm.hafner.analysis.Severity;
 import edu.hm.hafner.analysis.registry.ParserDescriptor.Option;
 import edu.hm.hafner.util.ResourceTest;
@@ -21,7 +22,7 @@
 class ParserRegistryTest extends ResourceTest {
     // Note for parser developers: if you add a new parser,
     // please check if you are using the correct type and increment the corresponding count
-    private static final long WARNING_PARSERS_COUNT = 127L;
+    private static final long WARNING_PARSERS_COUNT = 128L;
     private static final long BUG_PARSERS_COUNT = 3L;
     private static final long VULNERABILITY_PARSERS_COUNT = 7L;
     private static final long DUPLICATION_PARSERS_COUNT = 3L;
@@ -47,10 +48,10 @@ void shouldAssignCorrectParserType() {
         var typeCountMap = parserRegistry.getAllDescriptors().stream()
                 .collect(Collectors.groupingBy(ParserDescriptor::getType, Collectors.counting()));
         assertThat(typeCountMap)
-                .containsEntry(ParserDescriptor.Type.WARNING, WARNING_PARSERS_COUNT)
-                .containsEntry(ParserDescriptor.Type.BUG, BUG_PARSERS_COUNT)
-                .containsEntry(ParserDescriptor.Type.VULNERABILITY, VULNERABILITY_PARSERS_COUNT)
-                .containsEntry(ParserDescriptor.Type.DUPLICATION, DUPLICATION_PARSERS_COUNT);
+                .containsEntry(IssueType.WARNING, WARNING_PARSERS_COUNT)
+                .containsEntry(IssueType.BUG, BUG_PARSERS_COUNT)
+                .containsEntry(IssueType.VULNERABILITY, VULNERABILITY_PARSERS_COUNT)
+                .containsEntry(IssueType.DUPLICATION, DUPLICATION_PARSERS_COUNT);
     }
 
     @Test
@@ -58,34 +59,39 @@ void shouldFindSomeParsers() {
         var parserRegistry = new ParserRegistry();
 
         assertThat(parserRegistry).hasIds(SPOTBUGS, CHECKSTYLE, PMD).hasNames("SpotBugs", "CheckStyle", "PMD");
-        assertThat(parserRegistry.get(SPOTBUGS)).hasId(SPOTBUGS).hasName("SpotBugs").hasType(ParserDescriptor.Type.BUG);
-        assertThat(parserRegistry.get("owasp-dependency-check")).hasName("OWASP Dependency Check").hasType(ParserDescriptor.Type.VULNERABILITY);
+        assertThat(parserRegistry.get(SPOTBUGS))
+                .hasId(SPOTBUGS)
+                .hasName("SpotBugs")
+                .hasType(IssueType.BUG);
+        assertThat(parserRegistry.get("owasp-dependency-check"))
+                .hasName("OWASP Dependency Check")
+                .hasType(IssueType.VULNERABILITY);
         assertThat(parserRegistry.contains(SPOTBUGS)).isTrue();
         assertThat(parserRegistry.contains("nothing")).isFalse();
         List descriptors = parserRegistry.getAllDescriptors();
         assertThat(descriptors).filteredOn(d -> "spotbugs".equals(d.getId())).hasSize(1);
-        descriptors.forEach(d -> assertThat(d.createParser()).isNotNull());
+        descriptors.forEach(d -> assertThat(d.create()).isNotNull());
     }
 
     @Test
     void shouldConfigureCpdParser() {
         var parserRegistry = new ParserRegistry();
         var cpdDescriptor = parserRegistry.get("cpd");
-        assertThat(cpdDescriptor).hasType(ParserDescriptor.Type.DUPLICATION).hasName("CPD");
+        assertThat(cpdDescriptor).hasType(IssueType.DUPLICATION).hasName("CPD");
 
-        var parser = cpdDescriptor.createParser();
+        var parser = cpdDescriptor.create();
 
         var report = parser.parse(new FileReaderFactory(getResourceAsFile("one-cpd.xml")));
         assertThat(report).hasSize(2).hasSeverities(Severity.WARNING_NORMAL);
 
-        var highParser = cpdDescriptor.createParser(
+        var highParser = cpdDescriptor.create(
                 new Option(CpdDescriptor.HIGH_OPTION_KEY, "20"),
                 new Option(CpdDescriptor.NORMAL_OPTION_KEY, "10"));
 
         var highReport = highParser.parse(new FileReaderFactory(getResourceAsFile("one-cpd.xml")));
         assertThat(highReport).hasSize(2).hasSeverities(Severity.WARNING_HIGH);
 
-        var lowParser = cpdDescriptor.createParser(
+        var lowParser = cpdDescriptor.create(
                 new Option(CpdDescriptor.HIGH_OPTION_KEY, "100"),
                 new Option(CpdDescriptor.NORMAL_OPTION_KEY, "50"));
 
@@ -104,7 +110,7 @@ private void verifyPriority(final String type, final int expectedHighSize, final
         var parserRegistry = new ParserRegistry();
         var findbugsDescriptor = parserRegistry.get("findbugs");
 
-        var parser = findbugsDescriptor.createParser(new Option(FindBugsDescriptor.PRIORITY_OPTION_KEY, type));
+        var parser = findbugsDescriptor.create(new Option(FindBugsDescriptor.PRIORITY_OPTION_KEY, type));
 
         var confidenceReport = parser.parse(new FileReaderFactory(getResourceAsFile("findbugs-severities.xml")));
         assertThat(confidenceReport).hasSize(12);
diff --git a/src/test/java/edu/hm/hafner/analysis/registry/ParsersTest.java b/src/test/java/edu/hm/hafner/analysis/registry/ParsersTest.java
index bf8fc8215..3b15b3eeb 100644
--- a/src/test/java/edu/hm/hafner/analysis/registry/ParsersTest.java
+++ b/src/test/java/edu/hm/hafner/analysis/registry/ParsersTest.java
@@ -877,7 +877,7 @@ private Report findIssuesOfTool(final int expectedSizeOfIssues, final String too
 
         var allIssues = new Report();
         for (String fileName : fileNames) {
-            var parser = descriptor.createParser();
+            var parser = descriptor.create();
             var report = parser.parse(new FileReaderFactory(getResourceAsFile("../parser/").resolve(fileName)));
             allIssues.addAll(report);
         }
diff --git a/src/test/resources/design.puml b/src/test/resources/design.puml
index 75f25b9b1..c468a6283 100644
--- a/src/test/resources/design.puml
+++ b/src/test/resources/design.puml
@@ -6,18 +6,22 @@ skinparam component {
   BackgroundColor #f8f8f8
 }
 
-[Parsers] <<..analysis.parser..>>
+[Violation Adapters] <<..analysis.parser.violations>>
+[Parsers] <<..analysis.parser>>
 [Assertions] <<..assertj>>
 [Model] <<..analysis>>
 [Registry] <<..registry>>
 
 [Utilities] <<..util>>
 
+[Violation Adapters] -> [Model]
 [Parsers] --> [Model]
+[Registry] --> [Violation Adapters]
 [Registry] --> [Parsers]
 [Registry] --> [Model]
 [Registry] --> [Utilities]
 [Parsers] --> [Utilities]
+[Violation Adapters] --> [Utilities]
 [Parsers] --> [Assertions]
 [Model] --> [Utilities]
 [Assertions] --> [Model]
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/gendarme/Gendarme_unix.xml b/src/test/resources/edu/hm/hafner/analysis/parser/Gendarme_unix.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/gendarme/Gendarme_unix.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/Gendarme_unix.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/cpd/cpd.xml b/src/test/resources/edu/hm/hafner/analysis/parser/cpd/cpd.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/cpd/cpd.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/cpd/cpd.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/cpd/issue12516.xml b/src/test/resources/edu/hm/hafner/analysis/parser/cpd/issue12516.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/cpd/issue12516.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/cpd/issue12516.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/cpd/issue22356.xml b/src/test/resources/edu/hm/hafner/analysis/parser/cpd/issue22356.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/cpd/issue22356.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/cpd/issue22356.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/cpd/one-cpd.xml b/src/test/resources/edu/hm/hafner/analysis/parser/cpd/one-cpd.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/cpd/one-cpd.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/cpd/one-cpd.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/cpd/otherfile.xml b/src/test/resources/edu/hm/hafner/analysis/parser/cpd/otherfile.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/cpd/otherfile.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/cpd/otherfile.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/cpd/pmd-cpd.xml b/src/test/resources/edu/hm/hafner/analysis/parser/cpd/pmd-cpd.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/cpd/pmd-cpd.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/cpd/pmd-cpd.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/dupfinder/otherfile.xml b/src/test/resources/edu/hm/hafner/analysis/parser/dupfinder/otherfile.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/dupfinder/otherfile.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/dupfinder/otherfile.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/dupfinder/with-sourcecode.xml b/src/test/resources/edu/hm/hafner/analysis/parser/dupfinder/with-sourcecode.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/dupfinder/with-sourcecode.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/dupfinder/with-sourcecode.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/dupfinder/without-sourcecode.xml b/src/test/resources/edu/hm/hafner/analysis/parser/dupfinder/without-sourcecode.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/dupfinder/without-sourcecode.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/dupfinder/without-sourcecode.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/gendarme/Gendarme.xml b/src/test/resources/edu/hm/hafner/analysis/parser/gendarme/Gendarme.xml
deleted file mode 100644
index 94bfd4c71..000000000
--- a/src/test/resources/edu/hm/hafner/analysis/parser/gendarme/Gendarme.xml
+++ /dev/null
@@ -1,214 +0,0 @@
-
-
-  
-    C:\Dev\src\hudson\Hudson.Domain\bin\Debug\Hudson.Domain.dll
-  
-  
-    Gendarme.Rules.BadPractice.CloneMethodShouldNotReturnNullRule
-    Gendarme.Rules.BadPractice.CheckNewThreadWithoutStartRule
-    Gendarme.Rules.BadPractice.ReplaceIncompleteOddnessCheckRule
-    Gendarme.Rules.BadPractice.ToStringShouldNotReturnNullRule
-    Gendarme.Rules.BadPractice.DoNotForgetNotImplementedMethodsRule
-    Gendarme.Rules.BadPractice.AvoidAssemblyVersionMismatchRule
-    Gendarme.Rules.BadPractice.PreferEmptyInstanceOverNullRule
-    Gendarme.Rules.BadPractice.DoNotUseGetInterfaceToCheckAssignabilityRule
-    Gendarme.Rules.BadPractice.DisableDebuggingCodeRule
-    Gendarme.Rules.BadPractice.AvoidVisibleConstantFieldRule
-    Gendarme.Rules.BadPractice.AvoidCallingProblematicMethodsRule
-    Gendarme.Rules.BadPractice.GetEntryAssemblyMayReturnNullRule
-    Gendarme.Rules.BadPractice.ObsoleteMessagesShouldNotBeEmptyRule
-    Gendarme.Rules.BadPractice.EqualsShouldHandleNullArgRule
-    Gendarme.Rules.BadPractice.ConstructorShouldNotCallVirtualMethodsRule
-    Gendarme.Rules.BadPractice.CheckNewExceptionWithoutThrowingRule
-    Gendarme.Rules.Concurrency.ProtectCallToEventDelegatesRule
-    Gendarme.Rules.Concurrency.WriteStaticFieldFromInstanceMethodRule
-    Gendarme.Rules.Concurrency.DoubleCheckLockingRule
-    Gendarme.Rules.Concurrency.DoNotUseMethodImplOptionsSynchronizedRule
-    Gendarme.Rules.Concurrency.DoNotUseLockedRegionOutsideMethodRule
-    Gendarme.Rules.Concurrency.NonConstantStaticFieldsShouldNotBeVisibleRule
-    Gendarme.Rules.Concurrency.DoNotLockOnWeakIdentityObjectsRule
-    Gendarme.Rules.Concurrency.DoNotLockOnThisOrTypesRule
-    Gendarme.Rules.Concurrency.ReviewLockUsedOnlyForOperationsOnVariablesRule
-    Gendarme.Rules.Correctness.ReviewUselessControlFlowRule
-    Gendarme.Rules.Correctness.ReviewSelfAssignmentRule
-    Gendarme.Rules.Correctness.CallingEqualsWithNullArgRule
-    Gendarme.Rules.Correctness.ReviewUseOfModuloOneOnIntegersRule
-    Gendarme.Rules.Correctness.DoNotRoundIntegersRule
-    Gendarme.Rules.Correctness.AvoidFloatingPointEqualityRule
-    Gendarme.Rules.Correctness.BadRecursiveInvocationRule
-    Gendarme.Rules.Correctness.FinalizersShouldCallBaseClassFinalizerRule
-    Gendarme.Rules.Correctness.CheckParametersNullityInVisibleMethodsRule
-    Gendarme.Rules.Correctness.MethodCanBeMadeStaticRule
-    Gendarme.Rules.Correctness.DoNotCompareWithNaNRule
-    Gendarme.Rules.Correctness.ReviewCastOnIntegerMultiplicationRule
-    Gendarme.Rules.Correctness.UseValueInPropertySetterRule
-    Gendarme.Rules.Correctness.DoNotRecurseInEqualityRule
-    Gendarme.Rules.Correctness.DisposableFieldsShouldBeDisposedRule
-    Gendarme.Rules.Correctness.ReviewInconsistentIdentityRule
-    Gendarme.Rules.Correctness.AvoidConstructorsInStaticTypesRule
-    Gendarme.Rules.Correctness.AttributeStringLiteralsShouldParseCorrectlyRule
-    Gendarme.Rules.Correctness.ReviewCastOnIntegerDivisionRule
-    Gendarme.Rules.Correctness.ReviewDoubleAssignmentRule
-    Gendarme.Rules.Correctness.ProvideCorrectRegexPatternRule
-    Gendarme.Rules.Correctness.EnsureLocalDisposalRule
-    Gendarme.Rules.Correctness.ReviewUseOfInt64BitsToDoubleRule
-    Gendarme.Rules.Correctness.ProvideCorrectArgumentsToFormattingMethodsRule
-    Gendarme.Rules.Design.TypesWithNativeFieldsShouldBeDisposableRule
-    Gendarme.Rules.Design.AvoidVisibleFieldsRule
-    Gendarme.Rules.Design.MissingAttributeUsageOnCustomAttributeRule
-    Gendarme.Rules.Design.MarkAssemblyWithComVisibleRule
-    Gendarme.Rules.Design.DisposableTypesShouldHaveFinalizerRule
-    Gendarme.Rules.Design.AvoidPropertiesWithoutGetAccessorRule
-    Gendarme.Rules.Design.EnsureSymmetryForOverloadedOperatorsRule
-    Gendarme.Rules.Design.DeclareEventHandlersCorrectlyRule
-    Gendarme.Rules.Design.ImplementICloneableCorrectlyRule
-    Gendarme.Rules.Design.ProvideAlternativeNamesForOperatorOverloadsRule
-    Gendarme.Rules.Design.MainShouldNotBePublicRule
-    Gendarme.Rules.Design.ImplementIComparableCorrectlyRule
-    Gendarme.Rules.Design.AvoidEmptyInterfaceRule
-    Gendarme.Rules.Design.AbstractTypesShouldNotHavePublicConstructorsRule
-    Gendarme.Rules.Design.TypesShouldBeInsideNamespacesRule
-    Gendarme.Rules.Design.PreferEventsOverMethodsRule
-    Gendarme.Rules.Design.FinalizersShouldBeProtectedRule
-    Gendarme.Rules.Design.EnumsShouldUseInt32Rule
-    Gendarme.Rules.Design.ConsiderUsingStaticTypeRule
-    Gendarme.Rules.Design.EnumsShouldDefineAZeroValueRule
-    Gendarme.Rules.Design.AvoidVisibleNestedTypesRule
-    Gendarme.Rules.Design.AvoidSmallNamespaceRule
-    Gendarme.Rules.Design.TypesWithDisposableFieldsShouldBeDisposableRule
-    Gendarme.Rules.Design.MarkAssemblyWithCLSCompliantRule
-    Gendarme.Rules.Design.ImplementEqualsAndGetHashCodeInPairRule
-    Gendarme.Rules.Design.ConsiderConvertingMethodToPropertyRule
-    Gendarme.Rules.Design.DoNotDeclareVirtualMethodsInSealedTypeRule
-    Gendarme.Rules.Design.ConsiderConvertingFieldToNullableRule
-    Gendarme.Rules.Design.OperatorEqualsShouldBeOverloadedRule
-    Gendarme.Rules.Design.InternalNamespacesShouldNotExposeTypesRule
-    Gendarme.Rules.Design.FlagsShouldNotDefineAZeroValueRule
-    Gendarme.Rules.Design.DoNotDeclareProtectedMembersInSealedTypeRule
-    Gendarme.Rules.Design.ConsiderAddingInterfaceRule
-    Gendarme.Rules.Design.AvoidRefAndOutParametersRule
-    Gendarme.Rules.Design.AvoidMultidimensionalIndexerRule
-    Gendarme.Rules.Design.AttributeArgumentsShouldHaveAccessorsRule
-    Gendarme.Rules.Design.PreferIntegerOrStringForIndexersRule
-    Gendarme.Rules.Design.OverrideEqualsMethodRule
-    Gendarme.Rules.Design.MarkAssemblyWithAssemblyVersionRule
-    Gendarme.Rules.Design.Generic.ImplementGenericCollectionInterfacesRule
-    Gendarme.Rules.Design.Generic.PreferGenericsOverRefObjectRule
-    Gendarme.Rules.Design.Generic.AvoidMethodWithUnusedGenericTypeRule
-    Gendarme.Rules.Design.Generic.DoNotExposeNestedGenericSignaturesRule
-    Gendarme.Rules.Design.Generic.UseGenericEventHandlerRule
-    Gendarme.Rules.Design.Linq.AvoidExtensionMethodOnSystemObjectRule
-    Gendarme.Rules.Exceptions.MissingExceptionConstructorsRule
-    Gendarme.Rules.Exceptions.DoNotThrowInUnexpectedLocationRule
-    Gendarme.Rules.Exceptions.AvoidArgumentExceptionDefaultConstructorRule
-    Gendarme.Rules.Exceptions.ExceptionShouldBeVisibleRule
-    Gendarme.Rules.Exceptions.DoNotSwallowErrorsCatchingNonSpecificExceptionsRule
-    Gendarme.Rules.Exceptions.AvoidThrowingBasicExceptionsRule
-    Gendarme.Rules.Exceptions.InstantiateArgumentExceptionCorrectlyRule
-    Gendarme.Rules.Exceptions.DoNotDestroyStackTraceRule
-    Gendarme.Rules.Exceptions.DoNotThrowReservedExceptionRule
-    Gendarme.Rules.Interoperability.DoNotAssumeIntPtrSizeRule
-    Gendarme.Rules.Interoperability.UseManagedAlternativesToPInvokeRule
-    Gendarme.Rules.Interoperability.MarshalStringsInPInvokeDeclarationsRule
-    Gendarme.Rules.Interoperability.MarshalBooleansInPInvokeDeclarationsRule
-    Gendarme.Rules.Interoperability.PInvokeShouldNotBeVisibleRule
-    Gendarme.Rules.Interoperability.GetLastErrorMustBeCalledRightAfterPInvokeRule
-    Gendarme.Rules.Naming.DoNotPrefixEventsWithAfterOrBeforeRule
-    Gendarme.Rules.Naming.AvoidRedundancyInMethodNameRule
-    Gendarme.Rules.Naming.DoNotPrefixValuesWithEnumNameRule
-    Gendarme.Rules.Naming.UsePreferredTermsRule
-    Gendarme.Rules.Naming.UseCorrectSuffixRule
-    Gendarme.Rules.Naming.ParameterNamesShouldMatchOverriddenMethodRule
-    Gendarme.Rules.Naming.UsePluralNameInEnumFlagsRule
-    Gendarme.Rules.Naming.UseSingularNameInEnumsUnlessAreFlagsRule
-    Gendarme.Rules.Naming.DoNotUseReservedInEnumValueNamesRule
-    Gendarme.Rules.Naming.AvoidTypeInterfaceInconsistencyRule
-    Gendarme.Rules.Naming.UseCorrectPrefixRule
-    Gendarme.Rules.Naming.AvoidRedundancyInTypeNameRule
-    Gendarme.Rules.Naming.UseCorrectCasingRule
-    Gendarme.Rules.Naming.AvoidNonAlphanumericIdentifierRule
-    Gendarme.Rules.Naming.AvoidDeepNamespaceHierarchyRule
-    Gendarme.Rules.Performance.AvoidUnneededUnboxingRule
-    Gendarme.Rules.Performance.AvoidUnneededFieldInitializationRule
-    Gendarme.Rules.Performance.RemoveUnusedLocalVariablesRule
-    Gendarme.Rules.Performance.AvoidRepetitiveCastsRule
-    Gendarme.Rules.Performance.UseIsOperatorRule
-    Gendarme.Rules.Performance.RemoveUnneededFinalizerRule
-    Gendarme.Rules.Performance.AvoidUnneededCallsOnStringRule
-    Gendarme.Rules.Performance.AvoidUnsealedUninheritedInternalTypeRule
-    Gendarme.Rules.Performance.PreferLiteralOverInitOnlyFieldsRule
-    Gendarme.Rules.Performance.OverrideValueTypeDefaultsRule
-    Gendarme.Rules.Performance.AvoidLargeStructureRule
-    Gendarme.Rules.Performance.AvoidTypeGetTypeForConstantStringsRule
-    Gendarme.Rules.Performance.ImplementEqualsTypeRule
-    Gendarme.Rules.Performance.AvoidUnsealedConcreteAttributesRule
-    Gendarme.Rules.Performance.AvoidUnusedParametersRule
-    Gendarme.Rules.Performance.AvoidUnusedPrivateFieldsRule
-    Gendarme.Rules.Performance.AvoidUninstantiatedInternalClassesRule
-    Gendarme.Rules.Performance.AvoidLargeNumberOfLocalVariablesRule
-    Gendarme.Rules.Performance.UseSuppressFinalizeOnIDisposableTypeWithFinalizerRule
-    Gendarme.Rules.Performance.ConsiderCustomAccessorsForNonVisibleEventsRule
-    Gendarme.Rules.Performance.PreferCharOverloadRule
-    Gendarme.Rules.Performance.MathMinMaxCandidateRule
-    Gendarme.Rules.Performance.AvoidUncalledPrivateCodeRule
-    Gendarme.Rules.Performance.AvoidReturningArraysOnPropertiesRule
-    Gendarme.Rules.Performance.CompareWithEmptyStringEfficientlyRule
-    Gendarme.Rules.Performance.UseStringEmptyRule
-    Gendarme.Rules.Performance.DoNotIgnoreMethodResultRule
-    Gendarme.Rules.Performance.UseTypeEmptyTypesRule
-    Gendarme.Rules.Portability.NewLineLiteralRule
-    Gendarme.Rules.Portability.MonoCompatibilityReviewRule
-    Gendarme.Rules.Portability.FeatureRequiresRootPrivilegeOnUnixRule
-    Gendarme.Rules.Portability.ExitCodeIsLimitedOnUnixRule
-    Gendarme.Rules.Portability.DoNotHardcodePathsRule
-    Gendarme.Rules.Security.ArrayFieldsShouldNotBeReadOnlyRule
-    Gendarme.Rules.Security.NativeFieldsShouldNotBeVisibleRule
-    Gendarme.Rules.Security.StaticConstructorsShouldBePrivateRule
-    Gendarme.Rules.Security.DoNotShortCircuitCertificateCheckRule
-    Gendarme.Rules.Security.Cas.ReviewSuppressUnmanagedCodeSecurityUsageRule
-    Gendarme.Rules.Security.Cas.DoNotReduceTypeSecurityOnMethodsRule
-    Gendarme.Rules.Security.Cas.ReviewSealedTypeWithInheritanceDemandRule
-    Gendarme.Rules.Security.Cas.SecureGetObjectDataOverridesRule
-    Gendarme.Rules.Security.Cas.DoNotExposeMethodsProtectedByLinkDemandRule
-    Gendarme.Rules.Security.Cas.ReviewNonVirtualMethodWithInheritanceDemandRule
-    Gendarme.Rules.Security.Cas.DoNotExposeFieldsInSecuredTypeRule
-    Gendarme.Rules.Security.Cas.AddMissingTypeInheritanceDemandRule
-    Gendarme.Rules.Serialization.MissingSerializationConstructorRule
-    Gendarme.Rules.Serialization.DeserializeOptionalFieldRule
-    Gendarme.Rules.Serialization.UseCorrectSignatureForSerializationMethodsRule
-    Gendarme.Rules.Serialization.MissingSerializableAttributeOnISerializableTypeRule
-    Gendarme.Rules.Serialization.ImplementISerializableCorrectlyRule
-    Gendarme.Rules.Serialization.MarkAllNonSerializableFieldsRule
-    Gendarme.Rules.Serialization.MarkEnumerationsAsSerializableRule
-    Gendarme.Rules.Serialization.CallBaseMethodsOnISerializableTypesRule
-    Gendarme.Rules.UI.UseSTAThreadAttributeOnSWFEntryPointsRule
-    Gendarme.Rules.UI.GtkSharpExecutableTargetRule
-    Gendarme.Rules.UI.SystemWindowsFormsExecutableTargetRule
-    Gendarme.Rules.Maintainability.AvoidComplexMethodsRule
-    Gendarme.Rules.Maintainability.AvoidUnnecessarySpecializationRule
-    Gendarme.Rules.Maintainability.PreferStringIsNullOrEmptyRule
-    Gendarme.Rules.Maintainability.AvoidAlwaysNullFieldRule
-    Gendarme.Rules.Maintainability.AvoidLackOfCohesionOfMethodsRule
-    Gendarme.Rules.Maintainability.AvoidDeepInheritanceTreeRule
-    Gendarme.Rules.Maintainability.ConsiderUsingStopwatchRule
-  
-  
-    
-      This assembly is not decorated with the [CLSCompliant] attribute.
-      Add this attribute to ease the use (or non-use) of your assembly by CLS consumers.
-      
-        
-      
-    
-    
-      This method does not use any instance fields, properties or methods and can be made static.
-      Make this method static.
-      
-        
-      
-      
-        
-      
-    
-  
-
\ No newline at end of file
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/simian/fourfile.xml b/src/test/resources/edu/hm/hafner/analysis/parser/simian/fourfile.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/simian/fourfile.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/simian/fourfile.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/simian/onefile.xml b/src/test/resources/edu/hm/hafner/analysis/parser/simian/onefile.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/simian/onefile.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/simian/onefile.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/simian/otherfile.xml b/src/test/resources/edu/hm/hafner/analysis/parser/simian/otherfile.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/simian/otherfile.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/simian/otherfile.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/simian/simian-2.3.31.xml b/src/test/resources/edu/hm/hafner/analysis/parser/simian/simian-2.3.31.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/simian/simian-2.3.31.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/simian/simian-2.3.31.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/simian/twofile.xml b/src/test/resources/edu/hm/hafner/analysis/parser/simian/twofile.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/simian/twofile.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/simian/twofile.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/dry/simian/twosets.xml b/src/test/resources/edu/hm/hafner/analysis/parser/simian/twosets.xml
similarity index 100%
rename from src/test/resources/edu/hm/hafner/analysis/parser/dry/simian/twosets.xml
rename to src/test/resources/edu/hm/hafner/analysis/parser/simian/twosets.xml
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/spotbugsXml.xml b/src/test/resources/edu/hm/hafner/analysis/parser/spotbugsXml.xml
index c895290fb..0ddc20878 100644
--- a/src/test/resources/edu/hm/hafner/analysis/parser/spotbugsXml.xml
+++ b/src/test/resources/edu/hm/hafner/analysis/parser/spotbugsXml.xml
@@ -14,4 +14,4 @@ Common false-positive cases include:</p>
 to instruct SpotBugs that ignoring the return value of this method is acceptable.
 </p>
 
-    Bad use of return value from method
+    Bad use of return value from method
diff --git a/src/test/resources/edu/hm/hafner/analysis/parser/vale-report.json b/src/test/resources/edu/hm/hafner/analysis/parser/vale-report.json
new file mode 100644
index 000000000..2d1a71dc3
--- /dev/null
+++ b/src/test/resources/edu/hm/hafner/analysis/parser/vale-report.json
@@ -0,0 +1,64 @@
+{
+  "file1.adoc": [
+    {
+      "Action": {
+        "Name": "",
+        "Params": null
+      },
+      "Span": [
+        1,
+        5
+      ],
+      "Check": "RedHat.SentenceLength",
+      "Description": "",
+      "Link": "https://redhat-documentation.github.io/vale-at-red-hat/docs/main/reference-guide/sentencelength/",
+      "Message": "Try to keep sentences to an average of 32 words or fewer.",
+      "Severity": "suggestion",
+      "Match": "While",
+      "Line": 10
+    }
+  ],
+  "file2.adoc": [
+    {
+      "Action": {
+        "Name": "replace",
+        "Params": [
+          "might",
+          "can"
+        ]
+      },
+      "Span": [
+        143,
+        145
+      ],
+      "Check": "RedHat.TermsWarnings",
+      "Description": "",
+      "Link": "https://redhat-documentation.github.io/vale-at-red-hat/docs/main/reference-guide/termswarnings/",
+      "Message": "Consider using 'might' or 'can' rather than 'may' unless updating existing content that uses the term.",
+      "Severity": "warning",
+      "Match": "may",
+      "Line": 39
+    },
+    {
+      "Action": {
+        "Name": "edit",
+        "Params": [
+          "regex",
+          "(\\w+)( using)",
+          "$1 by using"
+        ]
+      },
+      "Span": [
+        44,
+        64
+      ],
+      "Check": "RedHat.Using",
+      "Description": "",
+      "Link": "https://redhat-documentation.github.io/vale-at-red-hat/docs/main/reference-guide/using/",
+      "Message": "Use 'by using' instead of 'using' when it follows a noun for clarity and grammatical correctness.",
+      "Severity": "error",
+      "Match": "functionalities using",
+      "Line": 51
+    }
+  ]
+}
\ No newline at end of file
diff --git a/src/test/resources/edu/hm/hafner/analysis/report.ser b/src/test/resources/edu/hm/hafner/analysis/report.ser
index 1cddee2c7..39b55f879 100644
Binary files a/src/test/resources/edu/hm/hafner/analysis/report.ser and b/src/test/resources/edu/hm/hafner/analysis/report.ser differ