diff --git a/src/main/java/org/jpeek/metrics/cohesion/LCOM2.java b/src/main/java/org/jpeek/metrics/cohesion/LCOM2.java new file mode 100644 index 00000000..da9005ab --- /dev/null +++ b/src/main/java/org/jpeek/metrics/cohesion/LCOM2.java @@ -0,0 +1,142 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2017 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.cohesion; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import javassist.CannotCompileException; +import javassist.CtClass; +import org.jpeek.Base; +import org.jpeek.Metric; +import org.jpeek.metrics.Colors; +import org.jpeek.metrics.JavassistClasses; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.xembly.Directive; + +/** + * Lack of Cohesion in Methods 2 (LCOM2). + * + *

Consider a class C with methods M1, M2,….., Mn. Let {Ii} = set of + * instance variables used by method Mi. There are n such sets, i.e., {1i}, + * {I2},….., {In}. Let P = { (Ii, Ij ) | Ii  Ij = } and Q = {(Ii, Ij ) | + * Ii  Ij  }. If all n sets {1i}, {I2},….., {In} are  then let P = .

+ * + *

There is no thread-safety guarantee.

+ * + * @author Mehmet Yildirim (memoyil@gmail.com) + * @version $Id$ + * @see A metrics suite for object oriented design + * @since 0.2 + * @checkstyle AbbreviationAsWordInNameCheck (5 lines) + * @checkstyle ClassDataAbstractionCouplingCheck (500 lines) + */ +public final class LCOM2 implements Metric { + + /** + * The base. + */ + private final Base base; + + /** + * Ctor. + * + * @param bse The base + */ + public LCOM2(final Base bse) { + this.base = bse; + } + + @Override + public Iterable xembly() throws IOException { + return new JavassistClasses( + this.base, LCOM2::cohesion, + // @checkstyle MagicNumberCheck (1 line) + new Colors(1.0d, 0.0d) + ).xembly(); + } + + /** + * Calculate LCOM2 metric for a single Java class. + * + * @param ctc The .class file + * @return Metrics + * @checkstyle ParameterNumberCheck (100 lines) + * @checkstyle AnonInnerLengthCheck (25 lines) + */ + @SuppressWarnings({"PMD.UseObjectForClearerAPI", "PMD.UseVarargs"}) + private static double cohesion(final CtClass ctc) { + final ClassReader reader; + try { + reader = new ClassReader(ctc.toBytecode()); + } catch (final IOException | CannotCompileException ex) { + throw new IllegalStateException(ex); + } + final List> methods = new LinkedList<>(); + final Set attrs = new HashSet<>(); + reader.accept( + new ClassVisitor(Opcodes.ASM6) { + @Override + public MethodVisitor visitMethod(final int access, final + String mtd, final String desc, final String signature, final + String[] exceptions) { + super.visitMethod(access, mtd, desc, signature, exceptions); + final Collection methodattrs = new HashSet<>(0); + methods.add(methodattrs); + return new MethodVisitor(Opcodes.ASM6) { + @Override + public void visitFieldInsn(final int opcode, + final String owner, final String attr, + final String details) { + super.visitFieldInsn(opcode, owner, attr, details); + attrs.add(attr); + methodattrs.add(attr); + } + }; + } + }, + 0 + ); + int sum = 0; + int result = 0; + for (final String attr : attrs) { + for (final Collection methodattrs : methods) { + if (methodattrs.contains(attr)) { + ++sum; + } + } + } + if (!attrs.isEmpty() && !methods.isEmpty()) { + result = 1 - sum / (attrs.size() * methods.size()); + } + return result; + } + +} diff --git a/src/main/java/org/jpeek/metrics/cohesion/LCOM3.java b/src/main/java/org/jpeek/metrics/cohesion/LCOM3.java new file mode 100644 index 00000000..e5fbca91 --- /dev/null +++ b/src/main/java/org/jpeek/metrics/cohesion/LCOM3.java @@ -0,0 +1,140 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2017 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.cohesion; + +import java.io.IOException; +import java.util.Collection; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import javassist.CannotCompileException; +import javassist.CtClass; +import org.jpeek.Base; +import org.jpeek.Metric; +import org.jpeek.metrics.Colors; +import org.jpeek.metrics.JavassistClasses; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.xembly.Directive; + +/** + * Lack of Cohesion in Methods 2 (LCOM2). + * + *

Consider an undirected graph G where the vertices are the methods of a + * class, and there is an edge between two vertices if the corresponding + * methods share at least one instance variable.

+ * + *

There is no thread-safety guarantee.

+ * + * @author Mehmet Yildirim (memoyil@gmail.com) + * @version $Id$ + * @see A metrics suite for object oriented design + * @since 0.2 + * @checkstyle AbbreviationAsWordInNameCheck (5 lines) + * @checkstyle ClassDataAbstractionCouplingCheck (500 lines) + */ +public final class LCOM3 implements Metric { + + /** + * The base. + */ + private final Base base; + + /** + * Ctor. + * @param bse The base + */ + public LCOM3(final Base bse) { + this.base = bse; + } + + @Override + public Iterable xembly() throws IOException { + return new JavassistClasses( + this.base, LCOM3::cohesion, + // @checkstyle MagicNumberCheck (1 line) + new Colors(0.35d, 0.0d) + ).xembly(); + } + + /** + * Calculate LCOM2 metric for a single Java class. + * @param ctc The .class file + * @return Metrics + * @checkstyle ParameterNumberCheck (100 lines) + * @checkstyle AnonInnerLengthCheck (25 lines) + */ + @SuppressWarnings({"PMD.UseObjectForClearerAPI", "PMD.UseVarargs"}) + private static double cohesion(final CtClass ctc) { + final ClassReader reader; + try { + reader = new ClassReader(ctc.toBytecode()); + } catch (final IOException | CannotCompileException ex) { + throw new IllegalStateException(ex); + } + final List> methods = new LinkedList<>(); + final Set attrs = new HashSet<>(); + reader.accept( + new ClassVisitor(Opcodes.ASM6) { + @Override + public MethodVisitor visitMethod(final int access, final + String mtd, final String desc, final String signature, + final String[] exceptions) { + super.visitMethod(access, mtd, desc, signature, exceptions); + final Collection methodattrs = new HashSet<>(0); + methods.add(methodattrs); + return new MethodVisitor(Opcodes.ASM6) { + @Override + public void visitFieldInsn(final int opcode, + final String owner, final String attr, + final String details) { + super.visitFieldInsn(opcode, owner, attr, details); + attrs.add(attr); + methodattrs.add(attr); + } + }; + } + }, + 0 + ); + int sum = 0; + int result = 0; + for (final String attr : attrs) { + for (final Collection methodattrs : methods) { + if (methodattrs.contains(attr)) { + ++sum; + } + } + } + if (!attrs.isEmpty() && methods.size() != 1) { + final int methodsize = methods.size(); + result = (methodsize - (sum / attrs.size())) / (methodsize - 1); + } + return result; + } + +} diff --git a/src/test/java/org/jpeek/metrics/cohesion/LCOM2Test.java b/src/test/java/org/jpeek/metrics/cohesion/LCOM2Test.java new file mode 100644 index 00000000..cdec640a --- /dev/null +++ b/src/test/java/org/jpeek/metrics/cohesion/LCOM2Test.java @@ -0,0 +1,79 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2017 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.cohesion; + +import com.jcabi.matchers.XhtmlMatchers; +import java.io.IOException; +import java.nio.file.Paths; +import org.hamcrest.MatcherAssert; +import org.jpeek.DefaultBase; +import org.jpeek.metrics.FakeBase; +import org.junit.Test; +import org.xembly.Xembler; + +/** + * Test case for {@link LCOM2}. + * @author Mehmet Yildirim (memoyil@gmail.com) + * @version $Id$ + * @since 0.2 + * @checkstyle AbbreviationAsWordInNameCheck (5 lines) + * @checkstyle JavadocMethodCheck (500 lines) + */ +public final class LCOM2Test { + + @Test + public void createsBigXmlReport() throws IOException { + MatcherAssert.assertThat( + XhtmlMatchers.xhtml( + new Xembler( + new LCOM2( + new DefaultBase(Paths.get(".")) + ).xembly() + ).xmlQuietly() + ), + XhtmlMatchers.hasXPaths( + "/metric/app/package/class[@id='LCOM2Test']", + "//class[@id='DefaultBase' and @value='0.0000']" + ) + ); + } + + @Test + public void createsXmlReportForFixtureClassA() throws IOException { + MatcherAssert.assertThat( + XhtmlMatchers.xhtml( + new Xembler( + new LCOM2( + new FakeBase("Foo") + ).xembly() + ).xmlQuietly() + ), + XhtmlMatchers.hasXPaths( + "/metric/app/package/class[@id='Foo']", + "//class[@id='Foo' and @value='1.0000']", + "//class[@id='Foo' and @color='yellow']" + ) + ); + } +} diff --git a/src/test/java/org/jpeek/metrics/cohesion/LCOM3Test.java b/src/test/java/org/jpeek/metrics/cohesion/LCOM3Test.java new file mode 100644 index 00000000..6c8f87c6 --- /dev/null +++ b/src/test/java/org/jpeek/metrics/cohesion/LCOM3Test.java @@ -0,0 +1,79 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2017 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.cohesion; + +import com.jcabi.matchers.XhtmlMatchers; +import java.io.IOException; +import java.nio.file.Paths; +import org.hamcrest.MatcherAssert; +import org.jpeek.DefaultBase; +import org.jpeek.metrics.FakeBase; +import org.junit.Test; +import org.xembly.Xembler; + +/** + * Test case for {@link LCOM3}. + * @author Mehmet Yildirim (memoyil@gmail.com) + * @version $Id$ + * @since 0.2 + * @checkstyle AbbreviationAsWordInNameCheck (5 lines) + * @checkstyle JavadocMethodCheck (500 lines) + */ +public final class LCOM3Test { + + @Test + public void createsBigXmlReport() throws IOException { + MatcherAssert.assertThat( + XhtmlMatchers.xhtml( + new Xembler( + new LCOM3( + new DefaultBase(Paths.get(".")) + ).xembly() + ).xmlQuietly() + ), + XhtmlMatchers.hasXPaths( + "/metric/app/package/class[@id='LCOM3Test']", + "//class[@id='DefaultBase' and @value='0.0000']" + ) + ); + } + + @Test + public void createsXmlReportForFixtureClassA() throws IOException { + MatcherAssert.assertThat( + XhtmlMatchers.xhtml( + new Xembler( + new LCOM3( + new FakeBase("Foo") + ).xembly() + ).xmlQuietly() + ), + XhtmlMatchers.hasXPaths( + "/metric/app/package/class[@id='Foo']", + "//class[@id='Foo' and @value='0.0000']", + "//class[@id='Foo' and @color='yellow']" + ) + ); + } +}