Skip to content

Commit

Permalink
cqfn#19 OCC
Browse files Browse the repository at this point in the history
  • Loading branch information
vssekorin committed Oct 24, 2017
1 parent 8d0435c commit 7b3f3b1
Show file tree
Hide file tree
Showing 4 changed files with 411 additions and 0 deletions.
276 changes: 276 additions & 0 deletions src/main/java/org/jpeek/metrics/cohesion/OCC.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
/**
* 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.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import javassist.CannotCompileException;
import javassist.CtClass;
import javassist.CtField;
import javassist.CtMethod;
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;

/**
* Optimistic Class Cohesion (OCC).
*
* <p>There is no thread-safety guarantee.
*
* @author Vseslav Sekorin (vssekorin@gmail.com)
* @version $Id$
* @see <a href="https://www.researchgate.net/publication/268046583_A_Proposal_of_Class_Cohesion_Metrics_Using_Sizes_of_Cohesive_Parts">A Proposal of Class Cohesion Metrics Using Sizes of Cohesive Parts</a>
* @since 0.2
* @checkstyle AbbreviationAsWordInNameCheck (5 lines)
* @checkstyle ParameterNumberCheck (500 lines)
*/
public final class OCC implements Metric {

/**
* The base.
*/
private final Base base;

/**
* Ctor.
* @param bse The base
*/
public OCC(final Base bse) {
this.base = bse;
}

@Override
public Iterable<Directive> xembly() throws IOException {
return new JavassistClasses(
this.base,
OCC::cohesion,
// @checkstyle MagicNumberCheck (1 line)
new Colors(0.35d, 0.75d, true)
).xembly();
}

/**
* Calculate OCC metric for a single Java class.
*
* @param ctc The .class file
* @return Metrics
*/
private static double cohesion(final CtClass ctc) {
final List<String> ctmethods = Arrays.stream(ctc.getDeclaredMethods())
.map(CtMethod::getName)
.collect(Collectors.toList());
final int number = ctmethods.size();
final double result;
if (number == 1) {
result = 0;
} else {
result = maxRw(ctc, ctmethods) / (number - 1);
}
return result;
}

/**
* Maximum number of methods which are reachable.
*
* @param ctc The .class file
* @param ctmethods Methods
* @return The number
*/
@SuppressWarnings({"PMD.UseVarargs", "PMD.CyclomaticComplexity",
"PMD.StdCyclomaticComplexity", "PMD.ModifiedCyclomaticComplexity",
"PMD.UseObjectForClearerAPI"})
private static double maxRw(
final CtClass ctc, final List<String> ctmethods) {
final ClassReader reader;
try {
reader = new ClassReader(ctc.toBytecode());
} catch (final IOException | CannotCompileException ex) {
throw new IllegalStateException(ex);
}
final Map<String, Set<String>> vars = new HashMap<>();
final Map<String, Set<String>> methods = new HashMap<>();
final List<String> ctfields = Arrays.stream(ctc.getDeclaredFields())
.map(CtField::getName)
.collect(Collectors.toList());
reader.accept(
//@checkstyle AnonInnerLengthCheck (50 lines)
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 Set<String> attrs = new HashSet<>();
final Set<String> names = new HashSet<>();
vars.put(mtd, attrs);
methods.put(mtd, names);
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);
if (ctfields.contains(attr)) {
attrs.add(attr);
}
}
@Override
public void visitMethodInsn(
final int opcode, final String owner,
final String name, final String desc,
final boolean itf) {
super.visitMethodInsn(
opcode, owner, name, desc, itf
);
if (!name.equals(mtd) && ctmethods.contains(name)) {
names.add(name);
}
}
};
}
},
0
);
methods.forEach(
(key, value) -> value.forEach(
item -> vars.get(key).addAll(vars.get(item))
)
);
final int number = ctmethods.size();
final boolean[][] reachability = new boolean[number][number];
boolean[][] result = new boolean[number][number];
boolean[][] current = new boolean[number][number];
for (int row = 0; row < number; ++row) {
for (int col = 0; col < number; ++col) {
reachability[row][col] = hasWeakConnection(
vars.get(ctmethods.get(row)),
vars.get(ctmethods.get(col))
);
current[row][col] = reachability[row][col];
result[row][col] = reachability[row][col];
}
}
for (int ind = 2; ind <= number; ++ind) {
current = multiply(current, reachability, number);
result = disjunction(current, result, number);
}
return maxWays(result, number);
}

/**
* Check weak-connection of two method.
*
* @param first First method's vars
* @param second Second method's vars
* @return Result
*/
private static boolean hasWeakConnection(
final Set<?> first, final Set<?> second) {
boolean result = false;
for (final Object item : first) {
if (second.contains(item)) {
result = true;
break;
}
}
return result;
}

/**
* Multiply of two boolean matrix.
*
* @param first The first matrix
* @param second The second matrix
* @param size Matrix size
* @return Result matrix
*/
private static boolean[][] multiply(
final boolean[][] first, final boolean[][] second, final int size) {
final boolean[][] result = new boolean[size][size];
//@checkstyle NestedForDepthCheck (10 lines)
for (int row = 0; row < size; ++row) {
for (int col = 0; col < size; ++col) {
for (int inner = 0; inner < size; ++inner) {
result[row][col] = result[row][col]
|| first[row][inner] && second[inner][col];
}
}
}
return result;
}

/**
* Disjunction of two boolean matrix.
*
* @param first The first matrix
* @param second The second matrix
* @param size Matrix size
* @return Result matrix
*/
private static boolean[][] disjunction(
final boolean[][] first, final boolean[][] second, final int size) {
final boolean[][] result = new boolean[size][size];
for (int row = 0; row < size; ++row) {
for (int col = 0; col < size; ++col) {
result[row][col] = first[row][col] || second[row][col];
}
}
return result;
}

/**
* The maximum number of paths in the reachability matrix.
*
* @param array Matrix
* @param size Size of matrix
* @return The number
*/
private static int maxWays(final boolean[][] array, final int size) {
int result = 0;
for (int row = 0; row < size; ++row) {
int current = 0;
for (int col = 0; col < size; ++col) {
if (array[row][col] && row != col) {
++current;
}
}
if (result < current) {
result = current;
}
}
return result;
}
}
98 changes: 98 additions & 0 deletions src/test/java/org/jpeek/metrics/cohesion/OCCTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* 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 OCC}.
*
* @author Vseslav Sekorin (vssekorin@gmail.com)
* @version $Id$
* @since 0.2
* @checkstyle AbbreviationAsWordInNameCheck (5 lines)
* @checkstyle JavadocMethodCheck (500 lines)
*/
public final class OCCTest {

@Test
public void createsBigXmlReport() throws IOException {
MatcherAssert.assertThat(
XhtmlMatchers.xhtml(
new Xembler(
new OCC(
new DefaultBase(Paths.get("."))
).xembly()
).xmlQuietly()
),
XhtmlMatchers.hasXPaths(
"/app/package/class[@id='OCCTest']",
"//class[@id='Base' and @value='0.0000']"
)
);
}

@Test
public void createsXmlReportForYellowClass() throws IOException {
MatcherAssert.assertThat(
XhtmlMatchers.xhtml(
new Xembler(
new OCC(
new FakeBase("Test")
).xembly()
).xmlQuietly()
),
XhtmlMatchers.hasXPaths(
"/app/package/class[@id='Test']",
"//class[@id='Test' and @value='0.5000']",
"//class[@id='Test' and @color='yellow']"
)
);
}

@Test
public void createsXmlReportForRedClass() throws IOException {
MatcherAssert.assertThat(
XhtmlMatchers.xhtml(
new Xembler(
new OCC(
new FakeBase("C")
).xembly()
).xmlQuietly()
),
XhtmlMatchers.hasXPaths(
"/app/package/class[@id='C']",
"//class[@id='C' and @value='1.0000']",
"//class[@id='C' and @color='red']"
)
);
}
}
19 changes: 19 additions & 0 deletions src/test/resources/org/jpeek/C.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
public class C {
private int a1, a2;

public void m1(int x) {
this.a1 = x;
}

public void m2(int y) {
this.a2 = y;
}

public int m3() {
return this.a1 + this.a2;
}

public void m4() {
this.m1(0);
}
}
Loading

0 comments on commit 7b3f3b1

Please sign in to comment.