diff --git a/src/main/java/org/jpeek/App.java b/src/main/java/org/jpeek/App.java index 3d9e24b6..e9729c3e 100644 --- a/src/main/java/org/jpeek/App.java +++ b/src/main/java/org/jpeek/App.java @@ -70,16 +70,13 @@ * and in `MetricsTest`). * Once they are resolved add it to reports list. */ -@SuppressWarnings - ( - { - "PMD.AvoidDuplicateLiterals", - "PMD.NPathComplexity", - "PMD.CyclomaticComplexity", - "PMD.StdCyclomaticComplexity", - "PMD.ModifiedCyclomaticComplexity" - } - ) +@SuppressWarnings({ + "PMD.AvoidDuplicateLiterals", + "PMD.NPathComplexity", + "PMD.CyclomaticComplexity", + "PMD.StdCyclomaticComplexity", + "PMD.ModifiedCyclomaticComplexity" +}) public final class App { /** @@ -172,11 +169,12 @@ public void analyze() throws IOException { final XSL chain = new XSLChain(layers); this.save(skeleton.toString(), "skeleton.xml"); final Collection reports = new LinkedList<>(); + final Calculus xsl = new XslCalculus(); if (this.params.containsKey("LCOM")) { reports.add( new XslReport( chain.transform(skeleton), - "LCOM", this.params, 10.0d, -5.0d + "LCOM", this.params, xsl, 10.0d, -5.0d ) ); } @@ -184,7 +182,7 @@ public void analyze() throws IOException { reports.add( new XslReport( chain.transform(skeleton), - "CAMC", this.params + "CAMC", this.params, xsl ) ); } @@ -192,7 +190,7 @@ public void analyze() throws IOException { reports.add( new XslReport( chain.transform(skeleton), - "MMAC", this.params, 0.5d, 0.1d + "MMAC", this.params, xsl, 0.5d, 0.1d ) ); } @@ -200,7 +198,7 @@ public void analyze() throws IOException { reports.add( new XslReport( chain.transform(skeleton), - "LCOM5", this.params, 0.5d, -0.1d + "LCOM5", this.params, xsl, 0.5d, -0.1d ) ); } @@ -208,7 +206,7 @@ public void analyze() throws IOException { reports.add( new XslReport( chain.transform(skeleton), - "NHD" + "NHD", xsl ) ); } @@ -216,7 +214,7 @@ public void analyze() throws IOException { reports.add( new XslReport( chain.transform(skeleton), - "LCOM2", this.params + "LCOM2", this.params, xsl ) ); } @@ -224,7 +222,7 @@ public void analyze() throws IOException { reports.add( new XslReport( chain.transform(skeleton), - "LCOM3", this.params + "LCOM3", this.params, xsl ) ); } @@ -232,7 +230,7 @@ public void analyze() throws IOException { reports.add( new XslReport( chain.transform(skeleton), - "SCOM", this.params + "SCOM", this.params, xsl ) ); } @@ -240,7 +238,7 @@ public void analyze() throws IOException { reports.add( new XslReport( chain.transform(skeleton), - "OCC", this.params + "OCC", this.params, xsl ) ); } @@ -248,7 +246,7 @@ public void analyze() throws IOException { reports.add( new XslReport( chain.transform(skeleton), - "PCC" + "PCC", xsl ) ); } @@ -256,7 +254,7 @@ public void analyze() throws IOException { reports.add( new XslReport( chain.transform(skeleton), - "TCC" + "TCC", xsl ) ); } @@ -264,7 +262,7 @@ public void analyze() throws IOException { reports.add( new XslReport( chain.transform(skeleton), - "LCC" + "LCC", xsl ) ); } @@ -272,7 +270,7 @@ public void analyze() throws IOException { reports.add( new XslReport( chain.transform(skeleton), - "CCM" + "CCM", xsl ) ); } @@ -280,7 +278,7 @@ public void analyze() throws IOException { reports.add( new XslReport( chain.transform(skeleton), - "MWE" + "MWE", xsl ) ); } diff --git a/src/main/java/org/jpeek/Calculus.java b/src/main/java/org/jpeek/Calculus.java new file mode 100644 index 00000000..bc37e832 --- /dev/null +++ b/src/main/java/org/jpeek/Calculus.java @@ -0,0 +1,51 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017-2019 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.jpeek; + +import com.jcabi.xml.XML; +import java.io.IOException; +import java.util.Map; + +/** + * Metrics calculus interface. + * @since 0.30.9 + * @todo #390:30min We have a separate interface to calculate XML metrics. + * We should continue to the goal defined in #296 where we should have a java + * implementation to calculate metrics. The motivation is to be able to make + * calculations impossible or too difficult to implement in xsl, like LCOM4. + */ +public interface Calculus { + + /** + * Produces {@link XML} representing metrics values. + * @param metric Desired metric to calculate + * @param params Params + * @param skeleton Package input + * @return XML document giving metrics values for classes + * @throws IOException If fails + */ + XML node(String metric, Map params, XML skeleton) + throws IOException; + +} diff --git a/src/main/java/org/jpeek/Report.java b/src/main/java/org/jpeek/Report.java index 0385812e..eff295f7 100644 --- a/src/main/java/org/jpeek/Report.java +++ b/src/main/java/org/jpeek/Report.java @@ -29,10 +29,6 @@ /** * Report interface. * @since 0.1 - * @todo #296:30min Now that we have the infrastructure to implement - * some metrics in other means than XSL transformations. We should try - * to implement some metrics with Java. We could start with LCOM4 metric that - * implies a graph traversal algorithm. */ public interface Report { diff --git a/src/main/java/org/jpeek/XslCalculus.java b/src/main/java/org/jpeek/XslCalculus.java new file mode 100644 index 00000000..184d3202 --- /dev/null +++ b/src/main/java/org/jpeek/XslCalculus.java @@ -0,0 +1,56 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017-2019 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.jpeek; + +import com.jcabi.xml.Sources; +import com.jcabi.xml.XML; +import com.jcabi.xml.XSLDocument; +import java.io.IOException; +import java.util.Map; +import org.cactoos.io.ResourceOf; +import org.cactoos.text.FormattedText; +import org.cactoos.text.TextOf; + +/** + * Metrics xsl calculus. Use an xsl sheet to transform the input skeleton into + * the xml containing the calculation. + * @since 0.30.9 + */ +public final class XslCalculus implements Calculus { + + @Override + public XML node(final String metric, final Map params, + final XML skeleton) throws IOException { + return new XSLDocument( + new TextOf( + new ResourceOf( + new FormattedText("org/jpeek/metrics/%s.xsl", metric) + ) + ).asString(), + Sources.DUMMY, + params + ).transform(skeleton); + } + +} diff --git a/src/main/java/org/jpeek/XslReport.java b/src/main/java/org/jpeek/XslReport.java index 13edf20d..5dea6d2a 100644 --- a/src/main/java/org/jpeek/XslReport.java +++ b/src/main/java/org/jpeek/XslReport.java @@ -25,7 +25,6 @@ import com.jcabi.log.Logger; import com.jcabi.xml.ClasspathSources; -import com.jcabi.xml.Sources; import com.jcabi.xml.StrictXML; import com.jcabi.xml.XML; import com.jcabi.xml.XMLDocument; @@ -39,11 +38,8 @@ import java.util.HashMap; import java.util.Map; import org.cactoos.collection.CollectionOf; -import org.cactoos.io.ResourceOf; import org.cactoos.io.TeeInput; import org.cactoos.scalar.LengthOf; -import org.cactoos.text.FormattedText; -import org.cactoos.text.TextOf; import org.xembly.Directives; import org.xembly.Xembler; @@ -95,6 +91,11 @@ final class XslReport implements Report { */ private final String metric; + /** + * Calculus. + */ + private final Calculus calculus; + /** * Post processing XSLs. */ @@ -109,10 +110,11 @@ final class XslReport implements Report { * Ctor. * @param xml Skeleton * @param name Name of the metric + * @param calc Calculus */ - XslReport(final XML xml, final String name) { + XslReport(final XML xml, final String name, final Calculus calc) { this( - xml, name, new HashMap<>(0), + xml, name, new HashMap<>(0), calc, XslReport.DEFAULT_MEAN, XslReport.DEFAULT_SIGMA ); } @@ -122,10 +124,13 @@ final class XslReport implements Report { * @param xml Skeleton * @param name Name of metric * @param args Params for XSL + * @param calc Calculus + * @checkstyle ParameterNumberCheck (5 lines) */ - XslReport(final XML xml, final String name, final Map args) { + XslReport(final XML xml, final String name, final Map args, + final Calculus calc) { this( - xml, name, args, + xml, name, args, calc, XslReport.DEFAULT_MEAN, XslReport.DEFAULT_SIGMA ); } @@ -135,16 +140,24 @@ final class XslReport implements Report { * @param xml Skeleton * @param name Name of the metric * @param args Params for XSL + * @param calc Calculus * @param mean Mean * @param sigma Sigma + * @todo #390:30min this constructor now has too many arguments. We should find a way + * to refactor the constructor or the class to have fewer parameters. + * We could start by analyzing the usage of this.params (args in this + * constructor) and get rid of it if it is not used. + * Another idea could be to have a data class contaning reporting params: + * name, args, mean, sigma. * @checkstyle ParameterNumberCheck (10 lines) */ XslReport(final XML xml, final String name, - final Map args, + final Map args, final Calculus calc, final double mean, final double sigma) { this.skeleton = xml; this.metric = name; this.params = args; + this.calculus = calc; this.post = new XSLChain( new CollectionOf<>( new XSLDocument( @@ -227,18 +240,9 @@ private XML xml() throws IOException { XslReport.SCHEMA_FILE ) ).applyQuietly( - new XSLDocument( - new TextOf( - new ResourceOf( - new FormattedText( - "org/jpeek/metrics/%s.xsl", - this.metric - ) - ) - ).asString(), - Sources.DUMMY, - this.params - ).transform(this.skeleton).node() + this.calculus.node( + this.metric, this.params, this.skeleton + ).node() ) ); } diff --git a/src/test/java/org/jpeek/MetricsTest.java b/src/test/java/org/jpeek/MetricsTest.java index b2dbccfa..9008c801 100644 --- a/src/test/java/org/jpeek/MetricsTest.java +++ b/src/test/java/org/jpeek/MetricsTest.java @@ -58,6 +58,7 @@ * @todo #323:30min This test is fully written against JUnit 4 API. * Migrate this parametrized test to junit 5, so it won't import any classes from junit 4 anymore. * @checkstyle JavadocTagsCheck (500 lines) + * @checkstyle ClassDataAbstractionCouplingCheck (500 lines) */ @RunWith(Parameterized.class) @SuppressWarnings({ @@ -249,7 +250,7 @@ public void testsTarget() throws Exception { final Path output = Files.createTempDirectory(""); new XslReport( new Skeleton(new FakeBase(this.target)).xml(), - this.metric + this.metric, new XslCalculus() ).save(output); final String xpath; if (Double.isNaN(this.value)) { diff --git a/src/test/java/org/jpeek/XslCalculusTest.java b/src/test/java/org/jpeek/XslCalculusTest.java new file mode 100644 index 00000000..d240dcc3 --- /dev/null +++ b/src/test/java/org/jpeek/XslCalculusTest.java @@ -0,0 +1,75 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2017-2019 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.jpeek; + +import com.jcabi.matchers.XhtmlMatchers; +import com.jcabi.xml.XML; +import java.io.IOException; +import java.util.HashMap; +import org.jpeek.skeleton.Skeleton; +import org.junit.jupiter.api.Test; +import org.llorllale.cactoos.matchers.Assertion; + +/** + * Test case for {@link XslCalculus}. + * @since 0.30.9 + */ +public final class XslCalculusTest { + + @Test + public void createsXmlCalculusWithXpaths() throws IOException { + final XML result = new XslCalculus().node( + "LCOM", new HashMap<>(0), new Skeleton( + new FakeBase( + "NoMethods", "Bar", "OverloadMethods", + "OnlyOneMethodWithParams", "WithoutAttributes" + ) + ).xml() + ); + new Assertion<>( + "Must create LCOM report", + result.toString(), + XhtmlMatchers.hasXPaths( + "/metric/app/package/class/vars", + "/metric/app/package/class/vars/var", + "/metric/app/package/class[@value]" + ) + ).affirm(); + } + + @Test + public void createsXmlCalculusWithEmptyProject() throws IOException { + final XML result = new XslCalculus().node( + "LCOM2", new HashMap<>(0), new Skeleton(new FakeBase()).xml() + ); + new Assertion<>( + "Report for empty project created", + result.toString(), + XhtmlMatchers.hasXPaths( + "/metric[title='LCOM2']/app[@id]" + ) + ).affirm(); + } + +} diff --git a/src/test/java/org/jpeek/XslReportTest.java b/src/test/java/org/jpeek/XslReportTest.java index 5867ca06..9c8991a1 100644 --- a/src/test/java/org/jpeek/XslReportTest.java +++ b/src/test/java/org/jpeek/XslReportTest.java @@ -48,7 +48,9 @@ public final class XslReportTest { @Test public void createsXmlReport() throws IOException { final Path output = Files.createTempDirectory(""); - new XslReport(new Skeleton(new FakeBase()).xml(), "LCOM").save(output); + new XslReport( + new Skeleton(new FakeBase()).xml(), "LCOM", new XslCalculus() + ).save(output); new Assertion<>( "Must LCOM.xml file exists", Files.exists(output.resolve("LCOM.xml")), @@ -71,7 +73,7 @@ public void createsXmlReportWithXpaths() throws IOException { "OnlyOneMethodWithParams", "WithoutAttributes" ) ).xml(), - "LCOM" + "LCOM", new XslCalculus() ).save(output); new Assertion<>( "Must create LCOM report", @@ -79,7 +81,6 @@ public void createsXmlReportWithXpaths() throws IOException { new TextOf(output.resolve("LCOM.xml")).asString() ), XhtmlMatchers.hasXPaths( - "/metric/app/package/class/vars", "/metric/statistics/mean", "/metric/bars/bar[@x='0' and .='0' and @color='yellow']" ) @@ -89,7 +90,9 @@ public void createsXmlReportWithXpaths() throws IOException { @Test public void createsXmlReportWithEmptyProject() throws IOException { final Path output = Files.createTempDirectory(""); - new XslReport(new Skeleton(new FakeBase()).xml(), "LCOM").save(output); + new XslReport( + new Skeleton(new FakeBase()).xml(), "LCOM", new XslCalculus() + ).save(output); new Assertion<>( "Report for empty project created", XhtmlMatchers.xhtml( @@ -119,7 +122,7 @@ public void createsFullXmlReport() throws IOException { .add("class").attr("id", "E").attr("value", "NaN").up() ).xmlQuietly() ), - "LCOM" + "LCOM", new XslCalculus() ).save(output); new Assertion<>( "Must create full report", @@ -141,7 +144,9 @@ public void createsFullXmlReport() throws IOException { @Test public void setsCorrectSchemaLocation() throws IOException { final Path output = Files.createTempDirectory(""); - new XslReport(new Skeleton(new FakeBase()).xml(), "LCOM").save(output); + new XslReport( + new Skeleton(new FakeBase()).xml(), "LCOM", new XslCalculus() + ).save(output); new Assertion<>( "Must have correct schema location", XhtmlMatchers.xhtml(