diff --git a/japicmp/src/main/java/japicmp/JApiCmp.java b/japicmp/src/main/java/japicmp/JApiCmp.java index 7e4e16bd0..3b3788d71 100644 --- a/japicmp/src/main/java/japicmp/JApiCmp.java +++ b/japicmp/src/main/java/japicmp/JApiCmp.java @@ -42,7 +42,7 @@ public static void main(String[] args) { } } catch (Exception e) { LOGGER.log(Level.FINE, CAUGHT_EXCEPTION + e.getLocalizedMessage(), e); - System.err.println(String.format("Execution of %s failed: %s", JApiCmp.class.getSimpleName(), e.getMessage())); + System.err.printf("Execution of %s failed: %s%n", JApiCmp.class.getSimpleName(), e.getMessage()); e.printStackTrace(); systemExit.exit(1); } diff --git a/japicmp/src/main/java/japicmp/cli/JApiCli.java b/japicmp/src/main/java/japicmp/cli/JApiCli.java index 1e78f1e0b..9ebf17816 100644 --- a/japicmp/src/main/java/japicmp/cli/JApiCli.java +++ b/japicmp/src/main/java/japicmp/cli/JApiCli.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; @@ -12,6 +15,10 @@ import japicmp.output.xml.XmlOutputGenerator; import japicmp.output.xml.XmlOutputGeneratorOptions; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.List; public class JApiCli { @@ -39,8 +46,8 @@ private void generateOutput(Options options, List jApiClasses, JarArc System.out.println(output); return; } - if (options.getXmlOutputFile().isPresent() || options.getHtmlOutputFile().isPresent()) { - SemverOut semverOut = new SemverOut(options, jApiClasses); + SemverOut semverOut = new SemverOut(options, jApiClasses); + if (options.getXmlOutputFile().isPresent()) { XmlOutputGeneratorOptions xmlOutputGeneratorOptions = new XmlOutputGeneratorOptions(); xmlOutputGeneratorOptions.setCreateSchemaFile(true); xmlOutputGeneratorOptions.setSemanticVersioningInformation(semverOut.generate()); @@ -48,9 +55,20 @@ private void generateOutput(Options options, List jApiClasses, JarArc try (XmlOutput xmlOutput = xmlGenerator.generate()) { XmlOutputGenerator.writeToFiles(options, xmlOutput); } catch (Exception e) { - throw new JApiCmpException(JApiCmpException.Reason.IoException, "Could not close output streams: " + e.getMessage(), e); + throw new JApiCmpException(JApiCmpException.Reason.IoException, "Could not write XML file: " + e.getMessage(), e); } } + if (options.getHtmlOutputFile().isPresent()) { + HtmlOutputGeneratorOptions htmlOutputGeneratorOptions = new HtmlOutputGeneratorOptions(); + htmlOutputGeneratorOptions.setSemanticVersioningInformation(semverOut.generate()); + HtmlOutputGenerator outputGenerator = new HtmlOutputGenerator(jApiClasses, options, htmlOutputGeneratorOptions); + HtmlOutput htmlOutput = outputGenerator.generate(); + try { + Files.write(Paths.get(options.getHtmlOutputFile().get()), htmlOutput.getHtml().getBytes(StandardCharsets.UTF_8)); + } catch (IOException e) { + throw new JApiCmpException(JApiCmpException.Reason.IoException, "Could not write HTML file: " + e.getMessage(), e); + } + } StdoutOutputGenerator stdoutOutputGenerator = new StdoutOutputGenerator(options, jApiClasses); String output = stdoutOutputGenerator.generate(); System.out.println(output); diff --git a/japicmp/src/main/java/japicmp/output/html/HtmlOutputGenerator.java b/japicmp/src/main/java/japicmp/output/html/HtmlOutputGenerator.java index 6e17b71ce..9d91bdff2 100644 --- a/japicmp/src/main/java/japicmp/output/html/HtmlOutputGenerator.java +++ b/japicmp/src/main/java/japicmp/output/html/HtmlOutputGenerator.java @@ -11,10 +11,7 @@ import java.io.FileNotFoundException; import java.io.InputStream; import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.stream.Collectors; import static japicmp.util.StringHelper.filtersAsString; @@ -57,22 +54,70 @@ private void classes(StringBuilder sb) { .map(jApiClass -> loadAndFillTemplate("/html/class-entry.html", mapOf( "fullyQualifiedName", jApiClass.getFullyQualifiedName(), "outputChangeStatus", outputChangeStatus(jApiClass), - "javaObjectSerializationCompatibleClass", javaObjectSerializationCompatibleClass(jApiClass), "javaObjectSerializationCompatible", javaObjectSerializationCompatible(jApiClass), "modifiers", modifiers(jApiClass), "classType", classType(jApiClass), - "compatibilityChanges", compatibilityChanges(jApiClass), + "compatibilityChanges", compatibilityChanges(jApiClass, false), "classFileFormatVersion", classFileFormatVersion(jApiClass), "genericTemplates", genericTemplates(jApiClass), "superclass", superclass(jApiClass), "interfaces", interfaces(jApiClass), "serialVersionUid", serialVersionUid(jApiClass), "fields", fields(jApiClass), - "constructors", constructors(jApiClass) + "constructors", constructors(jApiClass), + "methods", methods(jApiClass), + "annotations", annotations(jApiClass.getAnnotations()) ))) .collect(Collectors.joining())); } + private String methods(JApiClass jApiClass) { + if (!jApiClass.getMethods().isEmpty()) { + return loadAndFillTemplate("/html/methods.html", mapOf( + "tbody", methodsTBody(jApiClass.getMethods()) + )); + } + return ""; + } + + private String methodsTBody(List methods) { + return methods.stream() + .sorted(Comparator.comparing(JApiMethod::getName)) + .map(method -> "\n" + + "" + outputChangeStatus(method) + "\n" + + "" + modifiers(method) + "\n" + + "" + genericTemplates(method) + "\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( + "oldLineNumber", method.getOldLineNumberAsString(), + "newLineNumber", method.getNewLineNumberAsString())) + "\n" + + "\n") + .collect(Collectors.joining()); + } + + private String returnType(JApiMethod method) { + return "" + + returnTypeValue(method.getReturnType()) + + ""; + } + + private String returnTypeValue(JApiReturnType returnType) { + switch (returnType.getChangeStatus()) { + case NEW: + case UNCHANGED: + return returnType.getNewReturnType() + genericParameterTypes(returnType); + case REMOVED: + return returnType.getOldReturnType() + genericParameterTypes(returnType); + case MODIFIED: + return returnType.getNewReturnType() + " (<- " + returnType.getOldReturnType() + genericParameterTypes(returnType); + } + return ""; + } + private String constructors(JApiClass jApiClass) { if (!jApiClass.getConstructors().isEmpty()) { return loadAndFillTemplate("/html/constructors.html", mapOf( @@ -90,7 +135,7 @@ private String constructors(List constructors) { "" + genericTemplates(constructor) + "\n" + "" + constructor.getName() + "(" + parameters(constructor) + ")" + annotations(constructor.getAnnotations()) + "\n" + "" + exceptions(constructor) + "\n" + - "" + compatibilityChanges(constructor) + "\n" + + "" + compatibilityChanges(constructor, true) + "\n" + "" + loadAndFillTemplate("/html/line-numbers.html", mapOf( "oldLineNumber", constructor.getOldLineNumberAsString(), @@ -99,10 +144,10 @@ private String constructors(List constructors) { .collect(Collectors.joining()); } - private String exceptions(JApiConstructor constructor) { - if (!constructor.getExceptions().isEmpty()) { + private String exceptions(JApiBehavior jApiBehavior) { + if (!jApiBehavior.getExceptions().isEmpty()) { return loadAndFillTemplate("/html/exceptions.html", mapOf( - "tbody", exceptionsTBody(constructor.getExceptions()) + "tbody", exceptionsTBody(jApiBehavior.getExceptions()) )); } return ""; @@ -117,8 +162,8 @@ private String exceptionsTBody(List exceptions) { .collect(Collectors.joining()); } - private String parameters(JApiConstructor constructor) { - return constructor.getParameters().stream() + private String parameters(JApiBehavior jApiBehavior) { + return jApiBehavior.getParameters().stream() .map(parameter -> "" + parameter.getType() + genericParameterTypes(parameter) + @@ -138,12 +183,13 @@ private String fields(JApiClass jApiClass) { private String fields(List fields) { return fields.stream() + .sorted(Comparator.comparing(JApiField::getName)) .map(field -> "\n" + "" + outputChangeStatus(field) + "\n" + "" + modifiers(field) + "\n" + "" + type(field) + "\n" + "" + field.getName() + annotations(field.getAnnotations()) + "\n" + - "" + compatibilityChanges(field) + "\n" + + "" + compatibilityChanges(field, true) + "\n" + "\n") .collect(Collectors.joining()); } @@ -179,6 +225,7 @@ private String annotations(List annotations) { private String annotationsTBody(List annotations) { return annotations.stream() + .sorted(Comparator.comparing(JApiAnnotation::getFullyQualifiedName)) .map(annotation -> "\n" + "" + outputChangeStatus(annotation) + "\n" + "" + annotation.getFullyQualifiedName() + "\n" + @@ -204,11 +251,11 @@ private String annotationElements(List elements) { "" + element.getName() + "\n" + "" + element.getOldElementValues().stream() .map(this::valueToString) - .collect(Collectors.joining()) + + .collect(Collectors.joining(",")) + "\n" + "" + element.getNewElementValues().stream() .map(this::valueToString) - .collect(Collectors.joining()) + + .collect(Collectors.joining(",")) + "\n" + "\n") .collect(Collectors.joining()); @@ -271,7 +318,7 @@ private String interfacesTBody(List interfaces) { .map(interfaze -> "\n" + "" + outputChangeStatus(interfaze) + "\n" + "" + interfaze.getFullyQualifiedName() + "\n" + - "" + compatibilityChanges(interfaze) + "\n" + + "" + compatibilityChanges(interfaze, true) + "\n" + "\n") .collect(Collectors.joining()); } @@ -296,7 +343,7 @@ private String superclassTBody(JApiSuperclass superclass) { return "\n" + "" + outputChangeStatus(superclass) + "\n" + "" + superclassName(superclass) + "\n" + - "" + compatibilityChanges(superclass) + "\n" + + "" + compatibilityChanges(superclass, true) + "\n" + "\n"; } @@ -424,7 +471,7 @@ private String classFileFormatVersionString(int majorVersion, int minorVersion) return "n.a."; } - private String compatibilityChanges(JApiCompatibility jApiClass) { + private String compatibilityChanges(JApiCompatibility jApiClass, boolean withNA) { if (!jApiClass.getCompatibilityChanges().isEmpty()) { return loadAndFillTemplate("/html/compatibility-changes.html", mapOf( "tbody", jApiClass.getCompatibilityChanges().stream() @@ -432,7 +479,7 @@ private String compatibilityChanges(JApiCompatibility jApiClass) { .collect(Collectors.joining()) )); } - return ""; + return withNA ? "n.a." : ""; } private String compatibilityChange(JApiCompatibilityChange jApiCompatibilityChange) { @@ -483,23 +530,13 @@ private String modifier(JApiModifier>> jApiModi return ""; } - private String javaObjectSerializationCompatibleClass(JApiClass jApiClass) { - if (jApiClass.getJavaObjectSerializationCompatible() == JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.NOT_SERIALIZABLE) { - return ""; - } else if (jApiClass.getJavaObjectSerializationCompatible() == JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_COMPATIBLE) { - return "new"; - } else { - return "removed"; - } - } - private String javaObjectSerializationCompatible(JApiClass jApiClass) { if (jApiClass.getJavaObjectSerializationCompatible() == JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.NOT_SERIALIZABLE) { return ""; } else if (jApiClass.getJavaObjectSerializationCompatible() == JApiJavaObjectSerializationCompatibility.JApiJavaObjectSerializationChangeStatus.SERIALIZABLE_COMPATIBLE) { - return " (Serializable compatible) "; + return " (Serializable compatible) "; } else { - return " (Serializable incompatible(!): " + jApiClass.getJavaObjectSerializationCompatibleAsString() + ") "; + return " (Serializable incompatible(!): " + jApiClass.getJavaObjectSerializationCompatibleAsString() + ") "; } } diff --git a/japicmp/src/main/java/japicmp/output/xml/XmlOutputGenerator.java b/japicmp/src/main/java/japicmp/output/xml/XmlOutputGenerator.java index 5a765d4d6..49e578464 100644 --- a/japicmp/src/main/java/japicmp/output/xml/XmlOutputGenerator.java +++ b/japicmp/src/main/java/japicmp/output/xml/XmlOutputGenerator.java @@ -6,26 +6,20 @@ import japicmp.model.JApiClass; import japicmp.output.OutputFilter; import japicmp.output.OutputGenerator; -import japicmp.output.extapi.jpa.JpaAnalyzer; -import japicmp.output.extapi.jpa.model.JpaTable; import japicmp.output.xml.model.JApiCmpXmlRoot; import japicmp.util.Optional; -import japicmp.util.Streams; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Marshaller; import javax.xml.bind.SchemaOutputResolver; -import javax.xml.transform.*; +import javax.xml.transform.Result; import javax.xml.transform.stream.StreamResult; -import javax.xml.transform.stream.StreamSource; -import java.io.*; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.nio.file.StandardOpenOption; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -54,7 +48,6 @@ public XmlOutputGenerator(List jApiClasses, Options options, XmlOutpu @Override public XmlOutput generate() { JApiCmpXmlRoot jApiCmpXmlRoot = createRootElement(jApiClasses, options); - //analyzeJpaAnnotations(jApiCmpXmlRoot, jApiClasses); filterClasses(jApiClasses, options); return createXmlDocumentAndSchema(options, jApiCmpXmlRoot); } @@ -72,16 +65,6 @@ public static List writeToFiles(Options options, XmlOutput xmlOutput) { throw new JApiCmpException(JApiCmpException.Reason.IoException, "Failed to write XML file '" + xmlFile.getAbsolutePath() + "': " + e.getMessage(), e); } } - if (xmlOutput.getHtmlOutputStream().isPresent() && options.getHtmlOutputFile().isPresent()) { - File htmlFile = new File(options.getHtmlOutputFile().get()); - try (FileOutputStream fos = new FileOutputStream(htmlFile)) { - ByteArrayOutputStream outputStream = xmlOutput.getHtmlOutputStream().get(); - outputStream.writeTo(fos); - filesWritten.add(htmlFile); - } catch (IOException e) { - throw new JApiCmpException(JApiCmpException.Reason.IoException, "Failed to write HTML file '" + htmlFile.getAbsolutePath() + "': " + e.getMessage(), e); - } - } } finally { try { xmlOutput.close(); @@ -92,18 +75,10 @@ public static List writeToFiles(Options options, XmlOutput xmlOutput) { return filesWritten; } - private void analyzeJpaAnnotations(JApiCmpXmlRoot jApiCmpXmlRoot, List jApiClasses) { - JpaAnalyzer jpaAnalyzer = new JpaAnalyzer(); - List jpaEntities = jpaAnalyzer.analyze(jApiClasses); - //jApiCmpXmlRoot.setJpaTables(jpaEntities); - } - private XmlOutput createXmlDocumentAndSchema(Options options, JApiCmpXmlRoot jApiCmpXmlRoot) { XmlOutput xmlOutput = new XmlOutput(); xmlOutput.setJApiCmpXmlRoot(jApiCmpXmlRoot); - ByteArrayOutputStream xmlBaos = null; - InputStream styleSheetAsInputStream = null; - InputStream xsltAsInputStream = null; + ByteArrayOutputStream xmlBaos; try { JAXBContext jaxbContext = JAXBContext.newInstance(JApiCmpXmlRoot.class); Marshaller marshaller = jaxbContext.createMarshaller(); @@ -118,7 +93,7 @@ private XmlOutput createXmlDocumentAndSchema(Options options, JApiCmpXmlRoot jAp final File xmlFile = new File(options.getXmlOutputFile().get()); SchemaOutputResolver outputResolver = new SchemaOutputResolver() { @Override - public Result createOutput(String namespaceUri, String suggestedFileName) throws IOException { + public Result createOutput(String namespaceUri, String suggestedFileName) { File schemaFile = xmlFile.getParentFile(); if (schemaFile == null) { LOGGER.warning(String.format("File '%s' has no parent file. Using instead: '%s'.", xmlFile.getAbsolutePath(), XSD_FILENAME)); @@ -134,64 +109,14 @@ public Result createOutput(String namespaceUri, String suggestedFileName) throws jaxbContext.generateSchema(outputResolver); } } - if (options.getHtmlOutputFile().isPresent()) { - TransformerFactory transformerFactory = TransformerFactory.newInstance(); - xsltAsInputStream = XmlOutputGenerator.class.getResourceAsStream("/html.xslt"); - if (xsltAsInputStream == null) { - throw new JApiCmpException(Reason.XsltError, "Failed to load XSLT."); - } - if (options.getHtmlStylesheet().isPresent()) { - styleSheetAsInputStream = new FileInputStream(options.getHtmlStylesheet().get()); - } else { - styleSheetAsInputStream = XmlOutputGenerator.class.getResourceAsStream("/style.css"); - if (styleSheetAsInputStream == null) { - throw new JApiCmpException(Reason.XsltError, "Failed to load stylesheet."); - } - } - String xsltAsString = integrateStylesheetIntoXslt(xsltAsInputStream, styleSheetAsInputStream); - Transformer transformer = transformerFactory.newTransformer(new StreamSource(new StringReader(xsltAsString))); - ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(xmlBaos.toByteArray()); - ByteArrayOutputStream htmlOutputStream = new ByteArrayOutputStream(); - transformer.transform(new StreamSource(byteArrayInputStream), new StreamResult(htmlOutputStream)); - xmlOutput.setHtmlOutputStream(Optional.of(htmlOutputStream)); - } } catch (JAXBException e) { throw new JApiCmpException(Reason.JaxbException, String.format("Marshalling of XML document failed: %s", e.getMessage()), e); } catch (IOException e) { throw new JApiCmpException(Reason.IoException, String.format("Marshalling of XML document failed: %s", e.getMessage()), e); - } catch (TransformerConfigurationException e) { - throw new JApiCmpException(Reason.XsltError, String.format("Configuration of XSLT transformer failed: %s", e.getMessage()), e); - } catch (TransformerException e) { - throw new JApiCmpException(Reason.XsltError, String.format("XSLT transformation failed: %s", e.getMessage()), e); - } finally { - try { - if (styleSheetAsInputStream != null) { - styleSheetAsInputStream.close(); - } - if (xsltAsInputStream != null) { - xsltAsInputStream.close(); - } - } catch (IOException e) { - LOGGER.log(Level.FINE, "Failed to close CSS and/or XSLT file: " + e.getLocalizedMessage(), e); - } } return xmlOutput; } - private String integrateStylesheetIntoXslt(InputStream xsltAsInputStream, InputStream styleSheetAsInputStream) { - String xsltAsString = Streams.asString(xsltAsInputStream); - String styleSheetAsString = Streams.asString(styleSheetAsInputStream); - xsltAsString = xsltAsString.replace("", ""); - if (System.getProperty("japicmp.dump.xslt") != null) { - try { - Files.write(Paths.get(System.getProperty("japicmp.dump.xslt")), Collections.singletonList(xsltAsString), Charset.forName("UTF-8"), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - } catch (IOException e) { - LOGGER.log(Level.WARNING, "Could not dump XSLT file: " + e.getMessage(), e); - } - } - return xsltAsString; - } - private void filterClasses(List jApiClasses, Options options) { OutputFilter outputFilter = new OutputFilter(options); outputFilter.filter(jApiClasses); @@ -228,6 +153,4 @@ private String regExAsString(List ignoreMissingClassRegularExpression) } return sb.toString(); } - - } diff --git a/japicmp/src/main/resources/html/class-entry.html b/japicmp/src/main/resources/html/class-entry.html index d32806fe6..b24be319e 100644 --- a/japicmp/src/main/resources/html/class-entry.html +++ b/japicmp/src/main/resources/html/class-entry.html @@ -1,7 +1,7 @@
- + ${outputChangeStatus} ${javaObjectSerializationCompatible} @@ -29,29 +29,8 @@ ${constructors}
- - Methods: - - - - - - - - - - - - - - - - - - -
StatusModifierGeneric TemplatesTypeMethodExceptionsCompatibility Changes:Line Number
-
+ ${methods}
- + ${annotations}
diff --git a/japicmp/src/main/resources/html/methods.html b/japicmp/src/main/resources/html/methods.html new file mode 100644 index 000000000..04ca943aa --- /dev/null +++ b/japicmp/src/main/resources/html/methods.html @@ -0,0 +1,18 @@ +Methods: + + + + + + + + + + + + + + + ${tbody} + +
StatusModifierGeneric TemplatesTypeMethodExceptionsCompatibility Changes:Line Number
diff --git a/japicmp/src/test/java/japicmp/output/html/HtmlOutputGeneratorTest.java b/japicmp/src/test/java/japicmp/output/html/HtmlOutputGeneratorTest.java index 3267313e7..87509501d 100644 --- a/japicmp/src/test/java/japicmp/output/html/HtmlOutputGeneratorTest.java +++ b/japicmp/src/test/java/japicmp/output/html/HtmlOutputGeneratorTest.java @@ -4,6 +4,9 @@ import japicmp.cmp.JarArchiveComparatorOptions; import japicmp.config.Options; import japicmp.model.JApiClass; +import japicmp.output.xml.XmlOutput; +import japicmp.output.xml.XmlOutputGenerator; +import japicmp.output.xml.XmlOutputGeneratorOptions; import japicmp.util.*; import javassist.CannotCompileException; import javassist.ClassPool; @@ -34,7 +37,7 @@ public void testHtmlReport() throws Exception { @Override public List createOldClasses(ClassPool classPool) throws CannotCompileException { CtClass ctClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool); - CtMethodBuilder.create().name("toBeRemoved").returnType(CtClass.booleanType).addToClass(ctClass); + CtMethodBuilder.create().publicAccess().name("toBeRemoved").returnType(CtClass.booleanType).addToClass(ctClass); return Collections.singletonList(ctClass); } @@ -43,7 +46,7 @@ public List createNewClasses(ClassPool classPool) throws CannotCompileE CtClass newInterface = CtInterfaceBuilder.create().name("NewInterface").addToClassPool(classPool); CtClass superclass = CtClassBuilder.create().name("japicmp.Superclass").addToClassPool(classPool); CtClass ctClass = CtClassBuilder.create().name("japicmp.Test").withSuperclass(superclass).implementsInterface(newInterface).addToClassPool(classPool); - CtMethodBuilder.create().name("newMethod").returnType(CtClass.booleanType).addToClass(ctClass); + CtMethodBuilder.create().publicAccess().name("newMethod").returnType(CtClass.booleanType).addToClass(ctClass); CtFieldBuilder.create().type(CtClass.booleanType).name("bField").addToClass(ctClass); CtConstructorBuilder.create().publicAccess().parameters(new CtClass[] {CtClass.intType, CtClass.booleanType}).exceptions(new CtClass[] {classPool.get("java.lang.Exception")}).addToClass(ctClass); return Arrays.asList(ctClass, superclass); @@ -60,4 +63,37 @@ public List createNewClasses(ClassPool classPool) throws CannotCompileE assertThat(document.select("#meta-accessmodifier-value").text(), is("PROTECTED")); assertThat(document.select("#warning-missingclasses").text(), containsString("WARNING")); } + + @Test + public void testXmlReport() throws Exception { + JarArchiveComparatorOptions options = new JarArchiveComparatorOptions(); + options.setIncludeSynthetic(true); + List jApiClasses = ClassesHelper.compareClasses(options, new ClassesHelper.ClassesGenerator() { + @Override + public List createOldClasses(ClassPool classPool) throws CannotCompileException { + CtClass ctClass = CtClassBuilder.create().name("japicmp.Test").addToClassPool(classPool); + CtMethodBuilder.create().publicAccess().name("toBeRemoved").returnType(CtClass.booleanType).addToClass(ctClass); + return Collections.singletonList(ctClass); + } + + @Override + public List createNewClasses(ClassPool classPool) throws CannotCompileException, NotFoundException { + CtClass newInterface = CtInterfaceBuilder.create().name("NewInterface").addToClassPool(classPool); + CtClass superclass = CtClassBuilder.create().name("japicmp.Superclass").addToClassPool(classPool); + CtClass ctClass = CtClassBuilder.create().name("japicmp.Test").withSuperclass(superclass).implementsInterface(newInterface).addToClassPool(classPool); + CtMethodBuilder.create().publicAccess().name("newMethod").returnType(CtClass.booleanType).addToClass(ctClass); + CtFieldBuilder.create().type(CtClass.booleanType).name("bField").addToClass(ctClass); + CtConstructorBuilder.create().publicAccess().parameters(new CtClass[] {CtClass.intType, CtClass.booleanType}).exceptions(new CtClass[] {classPool.get("java.lang.Exception")}).addToClass(ctClass); + return Arrays.asList(ctClass, superclass); + } + }); + Options reportOptions = Options.newDefault(); + reportOptions.setIgnoreMissingClasses(true); + reportOptions.setXmlOutputFile(Optional.of(Paths.get(System.getProperty("user.dir"), "target", "report.xml").toString())); + XmlOutputGenerator generator = new XmlOutputGenerator(jApiClasses, reportOptions, new XmlOutputGeneratorOptions()); + + XmlOutput xmlOutput = generator.generate(); + + Files.write(Paths.get(System.getProperty("user.dir"), "target", "report.xml"), xmlOutput.getXmlOutputStream().get().toString().getBytes(StandardCharsets.UTF_8)); + } }