diff --git a/japicmp-ant-task/src/main/java/japicmp/ant/JApiCmpTask.java b/japicmp-ant-task/src/main/java/japicmp/ant/JApiCmpTask.java index 8268bc9ba..51fa4e57f 100644 --- a/japicmp-ant-task/src/main/java/japicmp/ant/JApiCmpTask.java +++ b/japicmp-ant-task/src/main/java/japicmp/ant/JApiCmpTask.java @@ -5,6 +5,9 @@ import japicmp.config.Options; import japicmp.exception.JApiCmpException; import japicmp.model.JApiClass; +import japicmp.output.html.HtmlOutput; +import japicmp.output.html.HtmlOutputGenerator; +import japicmp.output.html.HtmlOutputGeneratorOptions; import japicmp.output.incompatible.IncompatibleErrorOutput; import japicmp.output.semver.SemverOut; import japicmp.output.stdout.StdoutOutputGenerator; @@ -18,6 +21,10 @@ import org.apache.tools.ant.types.Path; import org.apache.tools.ant.types.Reference; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -280,14 +287,28 @@ private void generateOutput(Options options, List jApiClasses, JarArc } SemverOut semverOut = new SemverOut(options, jApiClasses); - XmlOutputGeneratorOptions xmlOutputGeneratorOptions = new XmlOutputGeneratorOptions(); - xmlOutputGeneratorOptions.setCreateSchemaFile(true); - xmlOutputGeneratorOptions.setSemanticVersioningInformation(semverOut.generate()); - XmlOutputGenerator xmlGenerator = new XmlOutputGenerator(jApiClasses, options, xmlOutputGeneratorOptions); - try (XmlOutput xmlOutput = xmlGenerator.generate()) { - XmlOutputGenerator.writeToFiles(options, xmlOutput); - } catch (Exception e) { - throw new BuildException("Could not close output streams: " + e.getMessage(), e); + String semanticVersioningInformation = semverOut.generate(); + if (options.getXmlOutputFile().isPresent()) { + XmlOutputGeneratorOptions xmlOutputGeneratorOptions = new XmlOutputGeneratorOptions(); + xmlOutputGeneratorOptions.setCreateSchemaFile(true); + xmlOutputGeneratorOptions.setSemanticVersioningInformation(semanticVersioningInformation); + XmlOutputGenerator xmlGenerator = new XmlOutputGenerator(jApiClasses, options, xmlOutputGeneratorOptions); + try (XmlOutput xmlOutput = xmlGenerator.generate()) { + XmlOutputGenerator.writeToFiles(options, xmlOutput); + } catch (Exception e) { + throw new BuildException("Writing XML report failed: " + e.getMessage(), e); + } + } + if (options.getHtmlOutputFile().isPresent()) { + HtmlOutputGeneratorOptions htmlOutputGeneratorOptions = new HtmlOutputGeneratorOptions(); + htmlOutputGeneratorOptions.setSemanticVersioningInformation(semanticVersioningInformation); + HtmlOutputGenerator htmlOutputGenerator = new HtmlOutputGenerator(jApiClasses, options, htmlOutputGeneratorOptions); + HtmlOutput htmlOutput = htmlOutputGenerator.generate(); + try { + Files.write(Paths.get(options.getHtmlOutputFile().get()), htmlOutput.getHtml().getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new BuildException("Writing HTML report failed: " + e.getMessage(), e); + } } } } diff --git a/japicmp-ant-task/src/test/java/japicmp/ant/JApiCmpTaskTest.java b/japicmp-ant-task/src/test/java/japicmp/ant/JApiCmpTaskTest.java index 507cf9e96..9c662f81f 100644 --- a/japicmp-ant-task/src/test/java/japicmp/ant/JApiCmpTaskTest.java +++ b/japicmp-ant-task/src/test/java/japicmp/ant/JApiCmpTaskTest.java @@ -7,7 +7,7 @@ import org.junit.Test; import static org.hamcrest.CoreMatchers.containsString; -import static org.junit.Assert.assertThat; +import static org.hamcrest.MatcherAssert.assertThat; public class JApiCmpTaskTest { @Rule diff --git a/japicmp-maven-plugin/src/main/java/japicmp/maven/JApiCmpMojo.java b/japicmp-maven-plugin/src/main/java/japicmp/maven/JApiCmpMojo.java index cc146e6ea..0614ee561 100644 --- a/japicmp-maven-plugin/src/main/java/japicmp/maven/JApiCmpMojo.java +++ b/japicmp-maven-plugin/src/main/java/japicmp/maven/JApiCmpMojo.java @@ -11,6 +11,9 @@ import japicmp.model.JApiClass; import japicmp.model.JApiCompatibilityChangeType; import japicmp.model.JApiSemanticVersionLevel; +import japicmp.output.html.HtmlOutput; +import japicmp.output.html.HtmlOutputGenerator; +import japicmp.output.html.HtmlOutputGeneratorOptions; import japicmp.output.incompatible.IncompatibleErrorOutput; import japicmp.output.semver.SemverOut; import japicmp.output.stdout.StdoutOutputGenerator; @@ -42,6 +45,10 @@ import java.io.IOException; import java.io.OutputStreamWriter; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.jar.JarFile; import java.util.regex.Matcher; @@ -112,7 +119,7 @@ public void execute() throws MojoExecutionException, MojoFailureException { executeWithParameters(pluginParameters, mavenParameters); } - Optional executeWithParameters(PluginParameters pluginParameters, MavenParameters mavenParameters) throws MojoFailureException, MojoExecutionException { + Optional executeWithParameters(PluginParameters pluginParameters, MavenParameters mavenParameters) throws MojoFailureException, MojoExecutionException { if (pluginParameters.getSkipParam()) { getLog().info("Skipping execution because parameter 'skip' was set to true."); return Optional.absent(); @@ -138,16 +145,30 @@ Optional executeWithParameters(PluginParameters pluginParameters, Mav PostAnalysisScriptExecutor postAnalysisScriptExecutor = new PostAnalysisScriptExecutor(); jApiClasses = postAnalysisScriptExecutor.apply(pluginParameters.getParameterParam(), jApiClasses, getLog()); File jApiCmpBuildDir = createJapiCmpBaseDir(pluginParameters); - generateDiffOutput(mavenParameters, pluginParameters, options, jApiClasses, jApiCmpBuildDir); - XmlOutput xmlOutput = generateXmlOutput(jApiClasses, jApiCmpBuildDir, options, mavenParameters, pluginParameters); - if (pluginParameters.isWriteToFiles()) { - List filesWritten = XmlOutputGenerator.writeToFiles(options, xmlOutput); - for (File file : filesWritten) { - getLog().info("Written file '" + file.getAbsolutePath() + "'."); + SemverOut semverOut = new SemverOut(options, jApiClasses); + String semanticVersioningInformation = semverOut.generate(); + generateDiffOutput(mavenParameters, pluginParameters, options, jApiClasses, jApiCmpBuildDir, semanticVersioningInformation); + if (!skipXmlReport(pluginParameters)) { + XmlOutput xmlOutput = generateXmlOutput(jApiClasses, jApiCmpBuildDir, options, mavenParameters, pluginParameters, semanticVersioningInformation); + if (pluginParameters.isWriteToFiles()) { + List filesWritten = XmlOutputGenerator.writeToFiles(options, xmlOutput); + for (File file : filesWritten) { + getLog().info("Written file '" + file.getAbsolutePath() + "'."); + } + } + } + Optional retVal = Optional.absent(); + if (!skipHtmlReport(pluginParameters)) { + HtmlOutput htmlOutput = generateHtmlOutput(jApiClasses, jApiCmpBuildDir, options, mavenParameters, pluginParameters, semanticVersioningInformation); + retVal = Optional.of(htmlOutput); + if (pluginParameters.isWriteToFiles() && options.getHtmlOutputFile().isPresent()) { + Path path = Paths.get(options.getHtmlOutputFile().get()); + Files.write(path, htmlOutput.getHtml().getBytes(StandardCharsets.UTF_8)); + getLog().info("Written file '" + path + "'."); } } breakBuildIfNecessary(jApiClasses, pluginParameters.getParameterParam(), options, jarArchiveComparator); - return Optional.of(xmlOutput); + return retVal; } catch (IOException e) { throw new MojoFailureException(String.format("Failed to construct output directory: %s", e.getMessage()), e); } @@ -197,19 +218,19 @@ private enum ConfigurationVersion { } private static DefaultArtifact createDefaultArtifact(MavenProject mavenProject, String version) { - org.apache.maven.artifact.Artifact artifact = mavenProject.getArtifact(); - return createDefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), artifact.getType(), version); + org.apache.maven.artifact.Artifact artifact = mavenProject.getArtifact(); + return createDefaultArtifact(artifact.getGroupId(), artifact.getArtifactId(), artifact.getClassifier(), artifact.getType(), version); } - private static DefaultArtifact createDefaultArtifact(String groupId, String artifactId, String classifier, String type, String version) { - String mappedType = type; - if("bundle".equals(type) || "ejb".equals(type)) { - mappedType ="jar"; - } - DefaultArtifact artifactVersion = new DefaultArtifact(groupId, artifactId, classifier, mappedType, - version); - return artifactVersion; - } + private static DefaultArtifact createDefaultArtifact(String groupId, String artifactId, String classifier, String type, String version) { + String mappedType = type; + if ("bundle".equals(type) || "ejb".equals(type)) { + mappedType = "jar"; + } + DefaultArtifact artifactVersion = new DefaultArtifact(groupId, artifactId, classifier, mappedType, + version); + return artifactVersion; + } private Artifact getComparisonArtifact(final MavenParameters mavenParameters, final PluginParameters pluginParameters, final ConfigurationVersion configurationVersion) throws MojoFailureException, MojoExecutionException { @@ -223,7 +244,7 @@ private Artifact getComparisonArtifact(final MavenParameters mavenParameters, fi filterSnapshots(versions, pluginParameters); filterVersionPattern(versions, pluginParameters); if (!versions.isEmpty()) { - DefaultArtifact artifactVersion = createDefaultArtifact(mavenProject, versions.get(versions.size()-1).toString()); + DefaultArtifact artifactVersion = createDefaultArtifact(mavenProject, versions.get(versions.size() - 1).toString()); ArtifactRequest artifactRequest = new ArtifactRequest(artifactVersion, mavenParameters.getRemoteRepos(), null); ArtifactResult artifactResult = mavenParameters.getRepoSystem().resolveArtifact(mavenParameters.getRepoSession(), artifactRequest); processArtifactResult(artifactVersion, artifactResult, pluginParameters, configurationVersion); @@ -231,11 +252,11 @@ private Artifact getComparisonArtifact(final MavenParameters mavenParameters, fi } else { if (ignoreMissingOldVersion(pluginParameters, configurationVersion)) { getLog().warn("Ignoring missing old artifact version: " + - artifactVersionRange.getGroupId() + ":" + artifactVersionRange.getArtifactId()); + artifactVersionRange.getGroupId() + ":" + artifactVersionRange.getArtifactId()); return null; } else { throw new MojoFailureException("Could not find previous version for artifact: " + artifactVersionRange.getGroupId() + ":" - + artifactVersionRange.getArtifactId()); + + artifactVersionRange.getArtifactId()); } } } catch (final VersionRangeResolutionException | ArtifactResolutionException e) { @@ -252,7 +273,7 @@ private void processArtifactResult(DefaultArtifact artifactVersion, ArtifactResu getLog().debug(exception.getMessage(), exception); } } - if (artifactResult.isMissing()){ + if (artifactResult.isMissing()) { if (ignoreMissingArtifact(pluginParameters, configurationVersion)) { getLog().warn("Ignoring missing artifact: " + artifactResult.getArtifact()); } else { @@ -539,7 +560,8 @@ private File createJapiCmpBaseDir(PluginParameters pluginParameters) throws Mojo } } - private void generateDiffOutput(MavenParameters mavenParameters, PluginParameters pluginParameters, Options options, List jApiClasses, File jApiCmpBuildDir) throws IOException, MojoFailureException { + private void generateDiffOutput(MavenParameters mavenParameters, PluginParameters pluginParameters, Options options, + List jApiClasses, File jApiCmpBuildDir, String semanticVersioningInformation) throws IOException, MojoFailureException { boolean skipDiffReport = false; if (pluginParameters.getParameterParam() != null) { skipDiffReport = pluginParameters.getParameterParam().isSkipDiffReport(); @@ -547,31 +569,41 @@ private void generateDiffOutput(MavenParameters mavenParameters, PluginParameter if (!skipDiffReport) { StdoutOutputGenerator stdoutOutputGenerator = new StdoutOutputGenerator(options, jApiClasses); String diffOutput = stdoutOutputGenerator.generate(); + diffOutput += "\nSemantic versioning suggestion: " + semanticVersioningInformation; File output = new File(jApiCmpBuildDir.getCanonicalPath() + File.separator + createFilename(mavenParameters) + ".diff"); writeToFile(diffOutput, output); } } - private XmlOutput generateXmlOutput(List jApiClasses, File jApiCmpBuildDir, Options options, MavenParameters mavenParameters, PluginParameters pluginParameters) throws IOException { + private XmlOutput generateXmlOutput(List jApiClasses, File jApiCmpBuildDir, Options options, MavenParameters mavenParameters, + PluginParameters pluginParameters, String semanticVersioningInformation) throws IOException { String filename = createFilename(mavenParameters); - if (!skipXmlReport(pluginParameters)) { - options.setXmlOutputFile(Optional.of(jApiCmpBuildDir.getCanonicalPath() + File.separator + filename + ".xml")); - } - if (!skipHtmlReport(pluginParameters)) { - options.setHtmlOutputFile(Optional.of(jApiCmpBuildDir.getCanonicalPath() + File.separator + filename + ".html")); - } - SemverOut semverOut = new SemverOut(options, jApiClasses); + options.setXmlOutputFile(Optional.of(jApiCmpBuildDir.getCanonicalPath() + File.separator + filename + ".xml")); XmlOutputGeneratorOptions xmlOutputGeneratorOptions = new XmlOutputGeneratorOptions(); xmlOutputGeneratorOptions.setCreateSchemaFile(true); - xmlOutputGeneratorOptions.setSemanticVersioningInformation(semverOut.generate()); + xmlOutputGeneratorOptions.setSemanticVersioningInformation(semanticVersioningInformation); if (pluginParameters.getParameterParam() != null) { String optionalTitle = pluginParameters.getParameterParam().getHtmlTitle(); - xmlOutputGeneratorOptions.setTitle(optionalTitle!=null ?optionalTitle :options.getDifferenceDescription()); + xmlOutputGeneratorOptions.setTitle(optionalTitle != null ? optionalTitle : options.getDifferenceDescription()); } XmlOutputGenerator xmlGenerator = new XmlOutputGenerator(jApiClasses, options, xmlOutputGeneratorOptions); return xmlGenerator.generate(); } + private HtmlOutput generateHtmlOutput(List jApiClasses, File jApiCmpBuildDir, Options options, MavenParameters mavenParameters, + PluginParameters pluginParameters, String semanticVersioningInformation) throws IOException { + String filename = createFilename(mavenParameters); + options.setHtmlOutputFile(Optional.of(jApiCmpBuildDir.getCanonicalPath() + File.separator + filename + ".html")); + HtmlOutputGeneratorOptions htmlOutputGeneratorOptions = new HtmlOutputGeneratorOptions(); + htmlOutputGeneratorOptions.setSemanticVersioningInformation(semanticVersioningInformation); + if (pluginParameters.getParameterParam() != null) { + String title = pluginParameters.getParameterParam().getHtmlTitle(); + htmlOutputGeneratorOptions.setTitle(title != null ? title : options.getDifferenceDescription()); + } + HtmlOutputGenerator htmlOutputGenerator = new HtmlOutputGenerator(jApiClasses, options, htmlOutputGeneratorOptions); + return htmlOutputGenerator.generate(); + } + private boolean skipHtmlReport(PluginParameters pluginParameters) { boolean skipReport = false; if (pluginParameters.getParameterParam() != null) { @@ -682,8 +714,8 @@ private Set getCompileArtifacts(final MavenProject mavenProject) { for (org.apache.maven.artifact.Artifact dep : projectDependencies) { if (dep.getArtifactHandler().isAddedToClasspath()) { if (org.apache.maven.artifact.Artifact.SCOPE_COMPILE.equals(dep.getScope()) - || org.apache.maven.artifact.Artifact.SCOPE_PROVIDED.equals(dep.getScope()) - || org.apache.maven.artifact.Artifact.SCOPE_SYSTEM.equals(dep.getScope())) { + || org.apache.maven.artifact.Artifact.SCOPE_PROVIDED.equals(dep.getScope()) + || org.apache.maven.artifact.Artifact.SCOPE_SYSTEM.equals(dep.getScope())) { result.add(RepositoryUtils.toArtifact(dep)); } } diff --git a/japicmp-maven-plugin/src/main/java/japicmp/maven/JApiCmpReport.java b/japicmp-maven-plugin/src/main/java/japicmp/maven/JApiCmpReport.java index f0406bdfc..33b934f92 100644 --- a/japicmp-maven-plugin/src/main/java/japicmp/maven/JApiCmpReport.java +++ b/japicmp-maven-plugin/src/main/java/japicmp/maven/JApiCmpReport.java @@ -1,7 +1,7 @@ package japicmp.maven; import japicmp.config.Options; -import japicmp.output.xml.XmlOutput; +import japicmp.output.html.HtmlOutput; import japicmp.util.Optional; import org.apache.maven.artifact.repository.ArtifactRepository; import org.apache.maven.doxia.sink.Sink; @@ -17,7 +17,6 @@ import org.eclipse.aether.RepositorySystemSession; import org.eclipse.aether.repository.RemoteRepository; -import java.io.ByteArrayOutputStream; import java.io.File; import java.util.List; import java.util.Locale; @@ -72,29 +71,12 @@ protected void executeReport(Locale locale) throws MavenReportException { getLog().info("japicmp module set to skip"); return; } - Optional xmlOutputOptional = mojo.executeWithParameters(this.pluginParameters, this.mavenParameters); - if (xmlOutputOptional.isPresent()) { - XmlOutput xmlOutput = xmlOutputOptional.get(); - if (xmlOutput.getHtmlOutputStream().isPresent()) { - ByteArrayOutputStream htmlOutputStream = xmlOutput.getHtmlOutputStream().get(); - String htmlString = htmlOutputStream.toString("UTF-8"); - htmlString = htmlString.replaceAll("", ""); - htmlString = htmlString.replaceAll("", ""); - htmlString = htmlString.replaceAll("", ""); - htmlString = htmlString.replaceAll("[^<]*", ""); - htmlString = htmlString.replaceAll("]*>", ""); - Sink sink = getSink(); - String htmlTitle = getHtmlTitle(); - if (htmlTitle != null) { - sink.head(); - sink.title(); - sink.text(pluginParameters.getParameterParam().getHtmlTitle()); - sink.title_(); - sink.head_(); - } - sink.rawText(htmlString); - sink.close(); - } + Optional htmlOutputOptional = mojo.executeWithParameters(this.pluginParameters, this.mavenParameters); + if (htmlOutputOptional.isPresent()) { + HtmlOutput htmlOutput = htmlOutputOptional.get(); + String htmlString = htmlOutput.getHtml(); + htmlString = replaceHtmlTags(htmlString); + writeToSink(htmlString); } } catch (Exception e) { String msg = "Failed to generate report: " + e.getMessage(); @@ -105,16 +87,42 @@ protected void executeReport(Locale locale) throws MavenReportException { } } + private void writeToSink(String htmlString) { + Sink sink = getSink(); + try { + String htmlTitle = getHtmlTitle(); + if (htmlTitle != null) { + sink.head(); + sink.title(); + sink.text(pluginParameters.getParameterParam().getHtmlTitle()); + sink.title_(); + sink.head_(); + } + sink.rawText(htmlString); + } finally { + sink.close(); + } + } + + private static String replaceHtmlTags(String html) { + html = html.replaceAll("", ""); + html = html.replaceAll("", ""); + html = html.replaceAll("", ""); + html = html.replaceAll("[^<]*", ""); + html = html.replaceAll("]*>", ""); + return html; + } + private JApiCmpMojo getMojo() { if (this.mojo != null) { return this.mojo; } this.mojo = new JApiCmpMojo(); this.mavenParameters = new MavenParameters(this.artifactRepositories, - this.mavenProject, this.mojoExecution, this.versionRangeWithProjectVersion, this.repoSystem, this.repoSession, - this.remoteRepos); + this.mavenProject, this.mojoExecution, this.versionRangeWithProjectVersion, this.repoSystem, this.repoSession, + this.remoteRepos); this.pluginParameters = new PluginParameters(this.skip, this.newVersion, this.oldVersion, this.parameter, this.dependencies, Optional.absent(), Optional.of( - this.outputDirectory), false, this.oldVersions, this.newVersions, this.oldClassPathDependencies, this.newClassPathDependencies); + this.outputDirectory), false, this.oldVersions, this.newVersions, this.oldClassPathDependencies, this.newClassPathDependencies); return this.mojo; } @@ -165,6 +173,6 @@ public String getDescription(Locale locale) { private boolean isPomModuleNeedingSkip() { return this.pluginParameters.getParameterParam().getSkipPomModules() - && "pom".equalsIgnoreCase(this.mavenProject.getArtifact().getType()); + && "pom".equalsIgnoreCase(this.mavenProject.getArtifact().getType()); } } diff --git a/japicmp-testbase/japicmp-test-maven-plugin/src/test/java/japicmp/test/ITClassFileFormatVersion.java b/japicmp-testbase/japicmp-test-maven-plugin/src/test/java/japicmp/test/ITClassFileFormatVersion.java index 003d9cc8d..4da3a7335 100644 --- a/japicmp-testbase/japicmp-test-maven-plugin/src/test/java/japicmp/test/ITClassFileFormatVersion.java +++ b/japicmp-testbase/japicmp-test-maven-plugin/src/test/java/japicmp/test/ITClassFileFormatVersion.java @@ -8,14 +8,14 @@ import org.junit.Test; import java.io.IOException; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; -import static org.junit.Assert.assertThat; public class ITClassFileFormatVersion { @@ -25,14 +25,14 @@ public void testClassFileFormatVersionIsPresent() throws IOException { if (!Files.exists(htmlPath)) { return; //in JDK 1.7 case } - Document document = Jsoup.parse(htmlPath.toFile(), Charset.forName("UTF-8").toString()); + Document document = Jsoup.parse(htmlPath.toFile(), StandardCharsets.UTF_8.toString()); Elements classFileFormatElements = document.select(".class_fileFormatVersion"); assertThat(classFileFormatElements.isEmpty(), is(false)); Elements tdCells = classFileFormatElements.select("table > tbody > tr > td"); assertThat(tdCells.isEmpty(), is(false)); for (Element element : tdCells) { String text = element.text(); - if (!"MODIFIED".equals(text) && !"50.0".equals(text) && !"52.0".equals(text)) { + if (!"MODIFIED (!)".equals(text) && !"50.0".equals(text) && !"52.0".equals(text)) { Assert.fail("text of HTML element does not equal 'MODIFIED' or 50.0 or 52.0: " + text); } } @@ -45,7 +45,7 @@ public void testStoutDiffClassFileFormatVersionIsPresent() throws IOException { return; //in JDK 1.7 case } assertThat(Files.exists(path), is(true)); - List lines = Files.readAllLines(path, Charset.forName("UTF-8")); + List lines = Files.readAllLines(path, StandardCharsets.UTF_8); boolean found = false; for (String line : lines) { if (line.contains("***! CLASS FILE FORMAT VERSION: 52.0 <- 50.0")) { diff --git a/japicmp-testbase/japicmp-test-maven-plugin/src/test/java/japicmp/test/ITStylesheet.java b/japicmp-testbase/japicmp-test-maven-plugin/src/test/java/japicmp/test/ITStylesheet.java index 136f11112..3342b024a 100644 --- a/japicmp-testbase/japicmp-test-maven-plugin/src/test/java/japicmp/test/ITStylesheet.java +++ b/japicmp-testbase/japicmp-test-maven-plugin/src/test/java/japicmp/test/ITStylesheet.java @@ -3,16 +3,16 @@ import org.junit.Test; import java.io.IOException; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; +import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsCollectionContaining.hasItem; import static org.hamcrest.core.IsNot.not; -import static org.junit.Assert.assertThat; public class ITStylesheet { @@ -20,8 +20,8 @@ public class ITStylesheet { public void testStylesheetIsUsed() throws IOException { Path htmlPath = Paths.get(System.getProperty("user.dir"), "target", "japicmp", "single-version.html"); assertThat(Files.exists(htmlPath), is(true)); - List htmlLines = Files.readAllLines(htmlPath, Charset.forName("UTF-8")); - List cssLines = Files.readAllLines(Paths.get(System.getProperty("user.dir"), "src", "main", "resources", "css", "stylesheet.css"), Charset.forName("UTF-8")); + List htmlLines = Files.readAllLines(htmlPath, StandardCharsets.UTF_8); + List cssLines = Files.readAllLines(Paths.get(System.getProperty("user.dir"), "src", "main", "resources", "css", "stylesheet.css"), StandardCharsets.UTF_8); assertThat(htmlLines.size(), not(is(0))); assertThat(cssLines.size(), not(is(0))); for (String cssLine : cssLines) { diff --git a/japicmp/src/main/java/japicmp/cmp/JApiCmpArchive.java b/japicmp/src/main/java/japicmp/cmp/JApiCmpArchive.java index d1652ae1d..91c4df65f 100644 --- a/japicmp/src/main/java/japicmp/cmp/JApiCmpArchive.java +++ b/japicmp/src/main/java/japicmp/cmp/JApiCmpArchive.java @@ -8,16 +8,18 @@ public class JApiCmpArchive { private File file; private byte[] bytes; private final Version version; + private String name; public JApiCmpArchive(File file, String version) { this.file = file; this.version = new Version(version); } - public JApiCmpArchive(byte[] bytes, String version) { + public JApiCmpArchive(byte[] bytes, String version, String name) { this.bytes = bytes; this.version = new Version(version); - } + this.name = name; + } public File getFile() { return file; @@ -31,9 +33,13 @@ public byte[] getBytes() { return bytes; } + public String getName() { + return name; + } + @Override public String toString() { - final StringBuilder sb = new StringBuilder("JApiCmpArchive{"); + final StringBuffer sb = new StringBuffer("JApiCmpArchive{"); sb.append("file=").append(file); sb.append(", bytes="); if (bytes == null) sb.append("null"); @@ -44,6 +50,7 @@ public String toString() { sb.append(']'); } sb.append(", version=").append(version); + sb.append(", name='").append(name).append('\''); sb.append('}'); return sb.toString(); } diff --git a/japicmp/src/main/java/japicmp/config/Options.java b/japicmp/src/main/java/japicmp/config/Options.java index 8db5fe6ce..f8842ad9f 100644 --- a/japicmp/src/main/java/japicmp/config/Options.java +++ b/japicmp/src/main/java/japicmp/config/Options.java @@ -1,22 +1,15 @@ package japicmp.config; import com.google.common.base.Joiner; -import japicmp.util.Optional; import com.google.common.base.Splitter; import com.google.common.collect.ImmutableList; import japicmp.cli.CliParser; import japicmp.cli.JApiCli; import japicmp.cmp.JApiCmpArchive; import japicmp.exception.JApiCmpException; -import japicmp.filter.AnnotationBehaviorFilter; -import japicmp.filter.AnnotationClassFilter; -import japicmp.filter.AnnotationFieldFilter; -import japicmp.filter.Filter; -import japicmp.filter.JavaDocLikeClassFilter; -import japicmp.filter.JavadocLikeBehaviorFilter; -import japicmp.filter.JavadocLikeFieldFilter; -import japicmp.filter.JavadocLikePackageFilter; +import japicmp.filter.*; import japicmp.model.AccessModifier; +import japicmp.util.Optional; import java.io.File; import java.io.IOException; @@ -329,9 +322,17 @@ private List toPathList(List archives) { List paths = new ArrayList<>(archives.size()); for (JApiCmpArchive archive : archives) { if (this.reportOnlyFilename) { - paths.add(archive.getFile().getName()); + if (archive.getFile() != null) { + paths.add(archive.getFile().getName()); + } else { + paths.add(archive.getName()); + } } else { - paths.add(archive.getFile().getAbsolutePath()); + if (archive.getFile() != null) { + paths.add(archive.getFile().getAbsolutePath()); + } else { + paths.add(archive.getName()); + } } } return paths; @@ -349,36 +350,32 @@ private List toVersionList(List archives) { } public String joinOldArchives() { - Joiner joiner = Joiner.on(";"); - String join = joiner.join(toPathList(oldArchives)); - if (join.trim().length() == 0) { + String join = Joiner.on(";").join(toPathList(oldArchives)); + if (join.trim().isEmpty()) { return N_A; } return join; } public String joinNewArchives() { - Joiner joiner = Joiner.on(";"); - String join = joiner.join(toPathList(newArchives)); - if (join.trim().length() == 0) { + String join = Joiner.on(";").join(toPathList(newArchives)); + if (join.trim().isEmpty()) { return N_A; } return join; } public String joinOldVersions() { - Joiner joiner = Joiner.on(";"); - String join = joiner.join(toVersionList(oldArchives)); - if (join.trim().length() == 0) { + String join = Joiner.on(";").join(toVersionList(oldArchives)); + if (join.trim().isEmpty()) { return N_A; } return join; } public String joinNewVersions() { - Joiner joiner = Joiner.on(";"); - String join = joiner.join(toVersionList(newArchives)); - if (join.trim().length() == 0) { + String join = Joiner.on(";").join(toVersionList(newArchives)); + if (join.trim().isEmpty()) { return N_A; } return join; diff --git a/japicmp/src/main/java/japicmp/output/html/HtmlOutputGenerator.java b/japicmp/src/main/java/japicmp/output/html/HtmlOutputGenerator.java index 9d91bdff2..8f5aeb470 100644 --- a/japicmp/src/main/java/japicmp/output/html/HtmlOutputGenerator.java +++ b/japicmp/src/main/java/japicmp/output/html/HtmlOutputGenerator.java @@ -20,10 +20,12 @@ public class HtmlOutputGenerator extends OutputGenerator { private final HtmlOutputGeneratorOptions htmlOutputGeneratorOptions; private final static SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + private final TemplateEngine templateEngine; public HtmlOutputGenerator(List jApiClasses, Options options, HtmlOutputGeneratorOptions htmlOutputGeneratorOptions) { super(options, jApiClasses); this.htmlOutputGeneratorOptions = htmlOutputGeneratorOptions; + this.templateEngine = new TemplateEngine(); } @Override @@ -34,11 +36,11 @@ public HtmlOutput generate() { sb.append("\n"); sb.append("\n"); sb.append("").append(getTitle()).append("\n"); - sb.append("\n"); + sb.append("\n"); sb.append("\n"); sb.append("\n"); sb.append("").append(getTitle()).append("\n"); - sb.append("
"); + sb.append("
\n"); metaInformation(sb); warningMissingClasses(sb); toc(sb); @@ -51,7 +53,7 @@ public HtmlOutput generate() { private void classes(StringBuilder sb) { sb.append(jApiClasses.stream() - .map(jApiClass -> loadAndFillTemplate("/html/class-entry.html", mapOf( + .map(jApiClass -> templateEngine.loadAndFillTemplate("/html/class-entry.html", mapOf( "fullyQualifiedName", jApiClass.getFullyQualifiedName(), "outputChangeStatus", outputChangeStatus(jApiClass), "javaObjectSerializationCompatible", javaObjectSerializationCompatible(jApiClass), @@ -59,7 +61,7 @@ private void classes(StringBuilder sb) { "classType", classType(jApiClass), "compatibilityChanges", compatibilityChanges(jApiClass, false), "classFileFormatVersion", classFileFormatVersion(jApiClass), - "genericTemplates", genericTemplates(jApiClass), + "genericTemplates", genericTemplates(jApiClass, false), "superclass", superclass(jApiClass), "interfaces", interfaces(jApiClass), "serialVersionUid", serialVersionUid(jApiClass), @@ -73,7 +75,7 @@ private void classes(StringBuilder sb) { private String methods(JApiClass jApiClass) { if (!jApiClass.getMethods().isEmpty()) { - return loadAndFillTemplate("/html/methods.html", mapOf( + return templateEngine.loadAndFillTemplate("/html/methods.html", mapOf( "tbody", methodsTBody(jApiClass.getMethods()) )); } @@ -86,13 +88,13 @@ private String methodsTBody(List methods) { .map(method -> "\n" + "" + outputChangeStatus(method) + "\n" + "" + modifiers(method) + "\n" + - "" + genericTemplates(method) + "\n" + + "" + genericTemplates(method, true) + "\n" + "" + returnType(method) + "\n" + "" + method.getName() + "(" + parameters(method) + ")" + annotations(method.getAnnotations()) + "\n" + "" + exceptions(method) + "\n" + "" + compatibilityChanges(method, true) + "\n" + "" + - loadAndFillTemplate("/html/line-numbers.html", mapOf( + templateEngine.loadAndFillTemplate("/html/line-numbers.html", mapOf( "oldLineNumber", method.getOldLineNumberAsString(), "newLineNumber", method.getNewLineNumberAsString())) + "\n" + "\n") @@ -120,7 +122,7 @@ private String returnTypeValue(JApiReturnType returnType) { private String constructors(JApiClass jApiClass) { if (!jApiClass.getConstructors().isEmpty()) { - return loadAndFillTemplate("/html/constructors.html", mapOf( + return templateEngine.loadAndFillTemplate("/html/constructors.html", mapOf( "tbody", constructors(jApiClass.getConstructors()) )); } @@ -132,12 +134,12 @@ private String constructors(List constructors) { .map(constructor -> "\n" + "" + outputChangeStatus(constructor) + "\n" + "" + modifiers(constructor) + "\n" + - "" + genericTemplates(constructor) + "\n" + + "" + genericTemplates(constructor, true) + "\n" + "" + constructor.getName() + "(" + parameters(constructor) + ")" + annotations(constructor.getAnnotations()) + "\n" + "" + exceptions(constructor) + "\n" + "" + compatibilityChanges(constructor, true) + "\n" + "" + - loadAndFillTemplate("/html/line-numbers.html", mapOf( + templateEngine.loadAndFillTemplate("/html/line-numbers.html", mapOf( "oldLineNumber", constructor.getOldLineNumberAsString(), "newLineNumber", constructor.getNewLineNumberAsString())) + "\n" + "\n") @@ -146,7 +148,7 @@ private String constructors(List constructors) { private String exceptions(JApiBehavior jApiBehavior) { if (!jApiBehavior.getExceptions().isEmpty()) { - return loadAndFillTemplate("/html/exceptions.html", mapOf( + return templateEngine.loadAndFillTemplate("/html/exceptions.html", mapOf( "tbody", exceptionsTBody(jApiBehavior.getExceptions()) )); } @@ -174,7 +176,7 @@ private String parameters(JApiBehavior jApiBehavior) { private String fields(JApiClass jApiClass) { if (!jApiClass.getFields().isEmpty()) { - return loadAndFillTemplate("/html/fields.html", mapOf( + return templateEngine.loadAndFillTemplate("/html/fields.html", mapOf( "tbody", fields(jApiClass.getFields()) )); } @@ -216,7 +218,7 @@ private String typeValue(JApiField field) { private String annotations(List annotations) { if (!annotations.isEmpty()) { - return loadAndFillTemplate("/html/annotations.html", mapOf( + return templateEngine.loadAndFillTemplate("/html/annotations.html", mapOf( "tbody", annotationsTBody(annotations) )); } @@ -236,7 +238,7 @@ private String annotationsTBody(List annotations) { private String elements(JApiAnnotation annotation) { if (!annotation.getElements().isEmpty()) { - return loadAndFillTemplate("/html/annotation-elements.html", mapOf( + return templateEngine.loadAndFillTemplate("/html/annotation-elements.html", mapOf( "tbody", annotationElements(annotation.getElements()) )); } else { @@ -282,7 +284,7 @@ private String values(JApiAnnotationElementValue value) { private String serialVersionUid(JApiClass jApiClass) { if (jApiClass.getSerialVersionUid().isSerializableOld() || jApiClass.getSerialVersionUid().isSerializableNew()) { - return loadAndFillTemplate("/html/serial-version-uid.html", mapOf( + return templateEngine.loadAndFillTemplate("/html/serial-version-uid.html", mapOf( "tbody", serialVersionUidTBody(jApiClass.getSerialVersionUid()) )); } @@ -306,7 +308,7 @@ private String serialVersionUidTBody(JApiSerialVersionUid serialVersionUid) { private String interfaces(JApiClass jApiClass) { if (!jApiClass.getInterfaces().isEmpty()) { - return loadAndFillTemplate("/html/interfaces.html", mapOf( + return templateEngine.loadAndFillTemplate("/html/interfaces.html", mapOf( "tbody", interfacesTBody(jApiClass.getInterfaces()) )); } @@ -332,7 +334,7 @@ private String superclass(JApiClass jApiClass) { (superclass.getChangeStatus() == JApiChangeStatus.UNCHANGED && !superclass.getSuperclassOld().equalsIgnoreCase("java.lang.Object")) ) ) { - return loadAndFillTemplate("/html/superclass.html", mapOf( + return templateEngine.loadAndFillTemplate("/html/superclass.html", mapOf( "tbody", superclassTBody(jApiClass.getSuperclass()) )); } @@ -360,15 +362,15 @@ private String superclassName(JApiSuperclass superclass) { return ""; } - private String genericTemplates(JApiHasGenericTemplates jApiHasGenericTemplates) { + private String genericTemplates(JApiHasGenericTemplates jApiHasGenericTemplates, boolean withNA) { List genericTemplates = jApiHasGenericTemplates.getGenericTemplates(); if (!genericTemplates.isEmpty()) { return "Generic Templates:\n" + - loadAndFillTemplate("/html/generic-templates.html", mapOf( + templateEngine.loadAndFillTemplate("/html/generic-templates.html", mapOf( "tbody", genericTemplatesTBody(genericTemplates) )); } - return ""; + return withNA ? "n.a." : ""; } private String genericTemplatesTBody(List genericTemplates) { @@ -402,16 +404,16 @@ private String genericParameterTypesRecursive(JApiGenericType jApiGenericType) { return ""; } - private String genericParameterWithWildcard(JApiGenericType jApiGenericType1) { - switch (jApiGenericType1.getGenericWildCard()) { + private String genericParameterWithWildcard(JApiGenericType jApiGenericType) { + switch (jApiGenericType.getGenericWildCard()) { case NONE: - return jApiGenericType1.getType(); + return jApiGenericType.getType(); case EXTENDS: - return "? extends " + jApiGenericType1.getType(); + return "? extends " + jApiGenericType.getType(); case SUPER: - return "? super " + jApiGenericType1.getType(); + return "? super " + jApiGenericType.getType(); case UNBOUNDED: - return "? " + jApiGenericType1.getType(); + return "?"; } return ""; } @@ -448,7 +450,7 @@ private String genericTypes(String header, List genericTypes) { private String classFileFormatVersion(JApiClass jApiClass) { if (jApiClass.getClassFileFormatVersion().getChangeStatus() == JApiChangeStatus.MODIFIED) { - return loadAndFillTemplate("/html/class-file-format-version.html", mapOf( + return templateEngine.loadAndFillTemplate("/html/class-file-format-version.html", mapOf( "tbody", classFileFormatVersionTBody(jApiClass) )); } @@ -473,7 +475,7 @@ private String classFileFormatVersionString(int majorVersion, int minorVersion) private String compatibilityChanges(JApiCompatibility jApiClass, boolean withNA) { if (!jApiClass.getCompatibilityChanges().isEmpty()) { - return loadAndFillTemplate("/html/compatibility-changes.html", mapOf( + return templateEngine.loadAndFillTemplate("/html/compatibility-changes.html", mapOf( "tbody", jApiClass.getCompatibilityChanges().stream() .map(this::compatibilityChange) .collect(Collectors.joining()) @@ -553,7 +555,7 @@ private void toc(StringBuilder sb) { sb.append("Classes\n"); sb.append("\n"); sb.append("\n"); - sb.append(loadAndFillTemplate("/html/toc.html", mapOf( + sb.append(templateEngine.loadAndFillTemplate("/html/toc.html", mapOf( "tbody", tocEntries() ))); } @@ -561,7 +563,7 @@ private void toc(StringBuilder sb) { private String tocEntries() { return jApiClasses.stream() - .map(jApiClass -> loadAndFillTemplate("/html/toc-entry.html", mapOf( + .map(jApiClass -> templateEngine.loadAndFillTemplate("/html/toc-entry.html", mapOf( "outputChangeStatus", outputChangeStatus(jApiClass), "fullyQualifiedName", jApiClass.getFullyQualifiedName() ))) @@ -607,13 +609,13 @@ private String getStyle() { throw new JApiCmpException(JApiCmpException.Reason.IoException, "Failed to load stylesheet: " + e.getMessage(), e); } } else { - styleSheet = loadTemplate("/style.css"); + styleSheet = templateEngine.loadTemplate("/style.css"); } return styleSheet; } private void metaInformation(StringBuilder sb) { - sb.append(loadAndFillTemplate("/html/meta-information.html", mapOf( + sb.append(templateEngine.loadAndFillTemplate("/html/meta-information.html", mapOf( "oldJar", options.joinOldArchives(), "newJar", options.joinNewArchives(), "newJar", options.joinNewArchives(), @@ -637,22 +639,6 @@ private Map mapOf(String... args) { return map; } - private String loadAndFillTemplate(String path, Map params) { - String template = loadTemplate(path); - for (Map.Entry entry : params.entrySet()) { - template = template.replace("${" + entry.getKey() + "}", entry.getValue()); - } - return template; - } - - private String loadTemplate(String path) { - InputStream resourceAsStream = HtmlOutputGenerator.class.getResourceAsStream(path); - if (resourceAsStream == null) { - throw new JApiCmpException(JApiCmpException.Reason.ResourceNotFound, "Failed to load: " + path); - } - return Streams.asString(resourceAsStream); - } - private String getTitle() { if (this.htmlOutputGeneratorOptions.getTitle().isPresent()) { return this.htmlOutputGeneratorOptions.getTitle().get(); diff --git a/japicmp/src/main/java/japicmp/output/html/TemplateEngine.java b/japicmp/src/main/java/japicmp/output/html/TemplateEngine.java new file mode 100644 index 000000000..4277e2f39 --- /dev/null +++ b/japicmp/src/main/java/japicmp/output/html/TemplateEngine.java @@ -0,0 +1,33 @@ +package japicmp.output.html; + +import japicmp.exception.JApiCmpException; +import japicmp.util.Streams; + +import java.io.InputStream; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class TemplateEngine { + private static final Map templateCache = new ConcurrentHashMap<>(); + + public String loadAndFillTemplate(String path, Map params) { + String template = loadTemplate(path); + for (Map.Entry entry : params.entrySet()) { + template = template.replace("${" + entry.getKey() + "}", entry.getValue()); + } + return template; + } + + public String loadTemplate(String path) { + String template = templateCache.get(path); + if (template == null) { + InputStream resourceAsStream = HtmlOutputGenerator.class.getResourceAsStream(path); + if (resourceAsStream == null) { + throw new JApiCmpException(JApiCmpException.Reason.ResourceNotFound, "Failed to load: " + path); + } + template = Streams.asString(resourceAsStream); + templateCache.put(path, template); + } + return template; + } +} diff --git a/japicmp/src/main/java/japicmp/output/xml/XmlOutput.java b/japicmp/src/main/java/japicmp/output/xml/XmlOutput.java index 832045630..37d71695f 100644 --- a/japicmp/src/main/java/japicmp/output/xml/XmlOutput.java +++ b/japicmp/src/main/java/japicmp/output/xml/XmlOutput.java @@ -1,13 +1,12 @@ package japicmp.output.xml; -import japicmp.util.Optional; import japicmp.output.xml.model.JApiCmpXmlRoot; +import japicmp.util.Optional; import java.io.ByteArrayOutputStream; public class XmlOutput implements AutoCloseable { private Optional xmlOutputStream = Optional.absent(); - private Optional htmlOutputStream = Optional.absent(); private japicmp.output.xml.model.JApiCmpXmlRoot JApiCmpXmlRoot; public Optional getXmlOutputStream() { @@ -18,22 +17,11 @@ public void setXmlOutputStream(Optional xmlOutputStream) this.xmlOutputStream = xmlOutputStream; } - public Optional getHtmlOutputStream() { - return htmlOutputStream; - } - - public void setHtmlOutputStream(Optional htmlOutputStream) { - this.htmlOutputStream = htmlOutputStream; - } - @Override public void close() throws Exception { if (this.xmlOutputStream.isPresent()) { this.xmlOutputStream.get().close(); } - if (this.htmlOutputStream.isPresent()) { - this.htmlOutputStream.get().close(); - } } public void setJApiCmpXmlRoot(JApiCmpXmlRoot JApiCmpXmlRoot) {