diff --git a/pom.xml b/pom.xml index 9514a332..7e8cc076 100644 --- a/pom.xml +++ b/pom.xml @@ -182,6 +182,12 @@ SOFTWARE. 9.8.0-5 runtime + + org.llorllale + cactoos-matchers + 0.13 + test + diff --git a/src/main/java/org/jpeek/skeleton/OpsOf.java b/src/main/java/org/jpeek/skeleton/OpsOf.java index 9591cb25..895ccbe6 100644 --- a/src/main/java/org/jpeek/skeleton/OpsOf.java +++ b/src/main/java/org/jpeek/skeleton/OpsOf.java @@ -23,6 +23,8 @@ */ package org.jpeek.skeleton; +import org.cactoos.Text; +import org.cactoos.text.TextOf; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.xembly.Directives; @@ -63,16 +65,25 @@ public void visitFieldInsn(final int opcode, final String owner, final String attr, final String dsc) { super.visitFieldInsn(opcode, owner, attr, dsc); this.target.addIf("ops").add("op"); + final Text name; if (opcode == Opcodes.GETFIELD) { this.target.attr("code", "get"); + name = new TextOf(attr); } else if (opcode == Opcodes.PUTFIELD) { this.target.attr("code", "put"); + name = new TextOf(attr); } else if (opcode == Opcodes.GETSTATIC) { this.target.attr("code", "get_static"); + name = new QualifiedName(owner, attr); } else if (opcode == Opcodes.PUTSTATIC) { this.target.attr("code", "put_static"); + name = new QualifiedName(owner, attr); + } else { + name = new TextOf(attr); } - this.target.set(attr).up().up(); + this.target.set( + name + ).up().up(); } @Override @@ -86,5 +97,4 @@ public void visitMethodInsn(final int opcode, .set(owner.replace("/", ".").concat(".").concat(mtd)) .up().up(); } - } diff --git a/src/main/java/org/jpeek/skeleton/QualifiedName.java b/src/main/java/org/jpeek/skeleton/QualifiedName.java new file mode 100644 index 00000000..3331fa6f --- /dev/null +++ b/src/main/java/org/jpeek/skeleton/QualifiedName.java @@ -0,0 +1,55 @@ +/** + * 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.skeleton; + +import org.cactoos.text.JoinedText; +import org.cactoos.text.TextEnvelope; +import org.cactoos.text.UncheckedText; + +/** + * A fully qualified name of a field, an unambiguous name + * that specifies field without regard + * to the context of the call. + * @author Ilya Kharlamov (ilya.kharlamov@gmail.com) + * @version $Id$ + * @since 0.29 + */ +public final class QualifiedName extends TextEnvelope { + /** + * Ctor. + * @param owner The class the attribute belongs to + * @param attr The name of the field + */ + public QualifiedName(final String owner, final String attr) { + super( + new UncheckedText( + new JoinedText( + ".", + owner.replace('/', '.'), + attr + ) + ) + ); + } +} diff --git a/src/main/resources/org/jpeek/metrics/LCOM4.xsl b/src/main/resources/org/jpeek/metrics/LCOM4.xsl index 2e2ab20a..40ef65ce 100644 --- a/src/main/resources/org/jpeek/metrics/LCOM4.xsl +++ b/src/main/resources/org/jpeek/metrics/LCOM4.xsl @@ -47,16 +47,23 @@ SOFTWARE. - + + + + + + + + + + + + - + + - diff --git a/src/main/resources/org/jpeek/metrics/LCOM5.xsl b/src/main/resources/org/jpeek/metrics/LCOM5.xsl index 4b77f929..0729fffc 100644 --- a/src/main/resources/org/jpeek/metrics/LCOM5.xsl +++ b/src/main/resources/org/jpeek/metrics/LCOM5.xsl @@ -46,12 +46,24 @@ SOFTWARE. + + + + + + + + + + + + - - + + - + diff --git a/src/main/resources/org/jpeek/metrics/OCC.xsl b/src/main/resources/org/jpeek/metrics/OCC.xsl index 576acfdf..7051d2af 100644 --- a/src/main/resources/org/jpeek/metrics/OCC.xsl +++ b/src/main/resources/org/jpeek/metrics/OCC.xsl @@ -41,7 +41,18 @@ SOFTWARE. - + + + + + + + + + + + + diff --git a/src/test/java/org/jpeek/MetricsTest.java b/src/test/java/org/jpeek/MetricsTest.java index 0427fedc..64bf2561 100644 --- a/src/test/java/org/jpeek/MetricsTest.java +++ b/src/test/java/org/jpeek/MetricsTest.java @@ -184,9 +184,6 @@ public static Collection targets() { new Object[] {"OverloadMethods", "TLCOM", 0.0d}, new Object[] {"TwoCommonAttributes", "TLCOM", 4.0d}, new Object[] {"WithoutAttributes", "TLCOM", 1.0d}, - new Object[] {"MethodMethodCalls", "LCOM4", 0.6d}, - new Object[] {"OneCommonAttribute", "LCOM4", 1d}, - new Object[] {"NotCommonAttributes", "LCOM4", 1.5d}, new Object[] {"Bar", "LCC", 0.0d}, new Object[] {"Foo", "LCC", 1.0d}, new Object[] {"MethodMethodCalls", "LCC", 0.1d}, diff --git a/src/test/java/org/jpeek/metrics/Lcom4Test.java b/src/test/java/org/jpeek/metrics/Lcom4Test.java new file mode 100644 index 00000000..cf856e9f --- /dev/null +++ b/src/test/java/org/jpeek/metrics/Lcom4Test.java @@ -0,0 +1,168 @@ +/** + * 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.metrics; + +import org.cactoos.list.ListOf; +import org.junit.Ignore; +import org.junit.Test; + +/** + * Test case for LCOM4 + * LCOM4 = Logical Relatedness of Methods. + * Basically it's a graph disjoint set. + * I.e. it answers the the following question: + * "Into how many independent classes can you split this class?" + * @author Ilya Kharlamov (ilya.kharlamov@gmail.com) + * @version $Id$ + * @since 0.28 + * @checkstyle JavadocMethodCheck (500 lines) + * @checkstyle ClassDataAbstractionCouplingCheck (500 lines) + * @checkstyle MagicNumberCheck (500 lines) + */ +public final class Lcom4Test { + + /** + * Variable from the report. + */ + public static final String PAIRS = "pairs"; + + /** + * Variable from the report. + */ + public static final String ATTRIBUTES = "attributes"; + + /** + * Variable from the report. + */ + public static final String METHODS = "methods"; + + /** + * Path to XSL. + */ + private static final String XSL = "org/jpeek/metrics/LCOM4.xsl"; + + /** + * Tests the deep dependencies methodFour() -> methodTwo() -> methodOne(). + * MethodMethodCalls.java + * - methodOne uses 'num' directly + * - methodTwo uses 'num' indirectly via methodOne + * - methodThree uses 'num' directly + * - methodFour uses 'num' indirectly via methodTwo and methodOne + * - methodFive does not use 'num' (this is an orphan method, ignored) + * Therefore the number of disjoint sets (LCOM4) should be 1 + * since all the methods use the same num field. + * @todo #215:30min LCOM4: Graph algorithm to determine disjoint sets. + * Currently we can only identify the dependencies via only one graph hop + * so we can't trace methodFour that uses 'num' indirectly + * via methodTwo and methodOne. + * Calculating Disjoint sets (also known as Connected Components), + * requires a graph traversal algorithm like depth-first search. + * This one will be tricky to implement in XSLT. + * After implementing, remove the @Ignore. + */ + @Test + @Ignore + public void methodMethodCalls() throws Exception { + final MetricBase.Report report = new MetricBase( + Lcom4Test.XSL + ).transform( + "MethodMethodCalls" + ); + report.assertVariable( + Lcom4Test.METHODS, + new ListOf<>( + "methodOne()", + "methodTwo()", + "methodThree()", + "methodFour()", + "methodFive()" + ).size() + ); + report.assertVariable(Lcom4Test.ATTRIBUTES, 1); + report.assertValue(1.0f, 0.001f); + } + + /** + * In OneCommonAttribute.java. + * - methodOne uses variable 'num' + * - methodTwo uses variable 'num' + * So this class only has a single disjoint set AKA (LCOM4) + * This is an ideal LCOM4 value = 1 + */ + @Test + public void oneCommonAttribute() throws Exception { + final MetricBase.Report report = new MetricBase( + Lcom4Test.XSL + ).transform( + "OneCommonAttribute" + ); + report.assertVariable( + Lcom4Test.METHODS, + new ListOf<>( + "methodOne", + "methodTwo" + ).size() + ); + report.assertVariable(Lcom4Test.ATTRIBUTES, 1); + report.assertVariable(Lcom4Test.PAIRS, 1); + report.assertValue(1.0f, 0.001f); + } + + /** + * In NotCommonAttributes.java + * - methodOne only uses 'num' variable + * - methodTwo only uses 'anotherNum' variable + * So basically there are two separate disjoint sets, + * or two separate classes under the same umbrella. + * So the value is 2.0 + */ + @Test + public void notCommonAttributes() throws Exception { + final MetricBase.Report report = new MetricBase( + Lcom4Test.XSL + ).transform( + "NotCommonAttributes" + ); + report.assertVariable(Lcom4Test.ATTRIBUTES, 2); + report.assertVariable(Lcom4Test.PAIRS, 0); + report.assertValue(2.0f, 0.001f); + } + + /** + * Should be the same as NotCommonAttributes. + * since constructors are not methods and should be ignored + */ + @Test + public void notCommonAttributesWithAllArgsConstructor() throws Exception { + final MetricBase.Report report = new MetricBase( + Lcom4Test.XSL + ).transform( + "NotCommonAttributesWithAllArgsConstructor" + ); + report.assertVariable(Lcom4Test.ATTRIBUTES, 2); + report.assertVariable(Lcom4Test.PAIRS, 0); + report.assertValue(2.0f, 0.001f); + } +} diff --git a/src/test/java/org/jpeek/metrics/LormTest.java b/src/test/java/org/jpeek/metrics/LormTest.java index fa7d92ca..1e2c2be6 100644 --- a/src/test/java/org/jpeek/metrics/LormTest.java +++ b/src/test/java/org/jpeek/metrics/LormTest.java @@ -24,14 +24,7 @@ package org.jpeek.metrics; -import com.jcabi.xml.XML; -import com.jcabi.xml.XSLDocument; -import org.cactoos.io.ResourceOf; import org.cactoos.list.ListOf; -import org.hamcrest.MatcherAssert; -import org.hamcrest.core.IsEqual; -import org.jpeek.FakeBase; -import org.jpeek.skeleton.Skeleton; import org.junit.Test; /** @@ -44,54 +37,27 @@ * @checkstyle ClassDataAbstractionCouplingCheck (500 lines) */ public final class LormTest { - @Test public void calculatesVariables() throws Exception { - final XML xml = new XSLDocument( - new ResourceOf( - "org/jpeek/metrics/LORM.xsl" - ).stream() + final MetricBase.Report report = new MetricBase( + "org/jpeek/metrics/LORM.xsl" ).transform( - new Skeleton( - new FakeBase("TwoCommonMethods") - ).xml() + "TwoCommonMethods" ); final int methods = 6; - MatcherAssert.assertThat( - "N variable 'N' is not calculated correctly", - xml.xpath( - "//class[@id='TwoCommonMethods']/vars/var[@id='N']/text()" - ).get(0), - new IsEqual<>( - String.valueOf(methods) - ) - ); - MatcherAssert.assertThat( - "variable 'R' is not calculated correctly", - xml.xpath( - "//class[@id='TwoCommonMethods']/vars/var[@id='R']/text()" - ).get(0), - new IsEqual<>( - String.valueOf( - new ListOf<>( - "methodTwo -> methodOne", - "methodThree -> methodOne", - "methodFive -> methodFour", - "methodSix -> methodFour" - ).size() - ) - ) + report.assertVariable("N", methods); + report.assertVariable( + "R", + new ListOf<>( + "methodTwo -> methodOne", + "methodThree -> methodOne", + "methodFive -> methodFour", + "methodSix -> methodFour" + ).size() ); - MatcherAssert.assertThat( - "variable 'RN' is not calculated correctly", - xml.xpath( - "//class[@id='TwoCommonMethods']/vars/var[@id='RN']/text()" - ).get(0), - new IsEqual<>( - String.valueOf( - methods * (methods - 1) / 2 - ) - ) + report.assertVariable( + "RN", + methods * (methods - 1) / 2 ); } } diff --git a/src/test/java/org/jpeek/metrics/MetricBase.java b/src/test/java/org/jpeek/metrics/MetricBase.java new file mode 100644 index 00000000..c32229ac --- /dev/null +++ b/src/test/java/org/jpeek/metrics/MetricBase.java @@ -0,0 +1,157 @@ +/** + * 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.metrics; + +import com.jcabi.xml.XML; +import com.jcabi.xml.XSLDocument; +import org.cactoos.io.ResourceOf; +import org.cactoos.text.FormattedText; +import org.cactoos.text.TextOf; +import org.hamcrest.number.IsCloseTo; +import org.jpeek.FakeBase; +import org.jpeek.skeleton.Skeleton; +import org.llorllale.cactoos.matchers.Assertion; +import org.llorllale.cactoos.matchers.TextIs; + +/** + * Metric test helper. + * @author Ilya Kharlamov (ilya.kharlamov@gmail.com) + * @version $Id$ + * @since 0.28 + */ +public final class MetricBase { + /** + * XSL document. + */ + private final XSLDocument xsl; + + /** + * Ctor. + * @param path Path to the xsl + * @throws Exception If file not found. + */ + public MetricBase(final String path) throws Exception { + this.xsl = new XSLDocument( + new ResourceOf( + path + ).stream() + ); + } + + /** + * Transform a class to assertable xml. + * @param name File name (without an extension) of a class to transform + * @return Xml result of the transformation + */ + public Report transform(final String name) { + return new Report( + name, + this.xsl.transform( + new Skeleton( + new FakeBase(name) + ).xml() + ) + ); + } + + /** + * Assertion helper for xml. + */ + public static final class Report { + /** + * Copy of the transformation xml. + */ + private final XML xml; + + /** + * Class name. + */ + private final String name; + + /** + * Ctor. + * @param name Class name. + * @param xml Resulting xml. + */ + public Report(final String name, final XML xml) { + this.name = name; + this.xml = xml; + } + + /** + * Asserts the variable produced. + * @param variable Variable name + * @param expected Expected value + * @throws Exception String format exception + */ + public void assertVariable(final String variable, + final int expected) throws Exception { + new Assertion<>( + new FormattedText( + "Variable '%s' is not calculated correctly for class '%s'", + variable, + this.name + ).asString(), + () -> new TextOf( + this.xml.xpath( + new FormattedText( + "//class[@id='%s']/vars/var[@id='%s']/text()", + this.name, + variable + ).asString() + ).get(0) + ), + new TextIs( + String.valueOf( + expected + ) + ) + ).affirm(); + } + + /** + * Asserts the main metric value. + * @param value Expected value of the metric + * @param error Rounding tolerance since the metric is float number + * @throws Exception String format exception + */ + public void assertValue(final double value, final double error) { + new Assertion<>( + "The metric value is not calculated properly", + () -> Double.parseDouble( + this.xml.xpath( + new FormattedText( + "//class[@id='%s']/@value", + this.name + ).asString() + ).get(0) + ), + new IsCloseTo( + value, + error + ) + ).affirm(); + } + } +} diff --git a/src/test/java/org/jpeek/skeleton/SkeletonTest.java b/src/test/java/org/jpeek/skeleton/SkeletonTest.java index 2cc95427..246fc704 100644 --- a/src/test/java/org/jpeek/skeleton/SkeletonTest.java +++ b/src/test/java/org/jpeek/skeleton/SkeletonTest.java @@ -27,7 +27,6 @@ import java.io.IOException; import org.hamcrest.MatcherAssert; import org.jpeek.FakeBase; -import org.junit.Ignore; import org.junit.Test; /** @@ -60,7 +59,7 @@ public void createsXml() { "//class[@id='OverloadMethods']/methods[count(method)=5]", "//method[@name='' and @ctor='true']", "//class[@id='Bar']//method[@name='getKey']/ops[count(op)=3]", - "//class[@id='Bar']//method[@name='getKey']/ops/op[@code='put_static' and .='singleton']", + "//class[@id='Bar']//method[@name='getKey']/ops/op[@code='put_static' and .='Bar.singleton']", "//class[@id='Bar']//method[@name='getKey']/ops/op[@code='call' and .='java.lang.String.length']", "//class[@id='Bar']//method[@name='getKey']/ops/op[@code='get' and .='key']", "//class[@id='Bar']//method[@name='']/ops[count(op)=4]" @@ -124,7 +123,6 @@ public void createsOnlyOneMethodIgnoresSynthetic() { } @Test - @Ignore public void findFieldWithQualifiedName() { MatcherAssert.assertThat( XhtmlMatchers.xhtml(