Skip to content

Commit

Permalink
Merge branch 'master' of https://github.com/yegor256/jpeek into 139
Browse files Browse the repository at this point in the history
  • Loading branch information
llorllale committed Feb 27, 2018
2 parents 1a3d918 + f2c555f commit 9f94179
Show file tree
Hide file tree
Showing 8 changed files with 320 additions and 12 deletions.
16 changes: 15 additions & 1 deletion README.md
Expand Up @@ -168,6 +168,20 @@ Cohesion and Reuse in an Object-Oriented System,<br/>
Department of Computer Science, Colorado State University, 1995,
[PDF](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.53.2683).

[`dallal11.pdf`]
Transitive Lack of Cohesion in Methods (**TLCOM**).<br/>
Jehad Al Dallal,<br/>
Transitive-based object-oriented lack-of-cohesion metric,<br/>
Department of Information Science, Kuwait University, 2011,
[PDF](https://www.researchgate.net/publication/220307725_Transitive-based_object-oriented_lack-of-cohesion_metric).

[`hitz95`]
Lack of Cohesion in Methods 4 (**LCOM4**).<br/>
Martin Hitz et al.,<br/>
Measuring Coupling and Cohesion In Object-Oriented Systems,<br/>
Institute of Applied Computer Science and Systems Analysis, University of Vienna, 1995,
[PDF](http://www.isys.uni-klu.ac.at/PDF/1995-0043-MHBM.pdf).

## How it works?

First, `Skeleton` parses Java bytecode using Javaassit and ASM, in order to produce
Expand Down Expand Up @@ -222,7 +236,7 @@ that XSL is much more suitable for manipulations with data than Java.

## Known Limitations

* The java compiler is known to inline constant variables as per [JLS 13.1](https://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html#jls-13.1). This affects the results calculated by some metrics when a method accesses `final` attributes (the access to the constants are not be included in the calculation).
* The java compiler is known to inline constant variables as per [JLS 13.1](https://docs.oracle.com/javase/specs/jls/se8/html/jls-13.html#jls-13.1). This affects the results calculated by metrics that take into account access to class attributes if these are `final` constants. For instance, all LCOM* and *COM metrics are affected.

## How to contribute?

Expand Down
Binary file added papers/dallal11.pdf
Binary file not shown.
Binary file added papers/hitz95.pdf
Binary file not shown.
11 changes: 7 additions & 4 deletions src/main/resources/org/jpeek/metrics/LCC.xsl
Expand Up @@ -58,10 +58,13 @@ SOFTWARE.
</xsl:for-each>
</xsl:variable>
<!--
@todo #9:30min `indirectly-related-pairs` are pairs of methods, which are through other directly connected methods.
https://pdfs.semanticscholar.org/672e/de6e3e600eafd84036a0b983b88e481ac626.pdf
Right now it is stubbed because information about method-method communication is yet to be provided in skeleton.xml
(issue #106).
@todo #119:30min `indirectly-related-pairs` are pairs of methods, which are connected through `directly-related-pairs`.
See section 3.1 of https://pdfs.semanticscholar.org/672e/de6e3e600eafd84036a0b983b88e481ac626.pdf
The key sentence is "The indirect connection relation is the **transitive closure** of direct connection relation".
See https://en.wikipedia.org/wiki/Transitive_closure for more info on what is a Transitive Closure.
We need to apply a Transitive Closure Algorithm on a graph of `directly-related-pairs`.
A simpliest Transitive Closure algorithms has O(V³) time complexity and involves a mutable V×V table (V is amount of vertices).
See https://www.geeksforgeeks.org/transitive-closure-of-a-graph/ for an example.
-->
<!--<xsl:variable name="indirectly-related-pairs">
</xsl:variable>
Expand Down
110 changes: 110 additions & 0 deletions src/main/resources/org/jpeek/metrics/LCOM4.xsl
@@ -0,0 +1,110 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
The MIT License (MIT)
Copyright (c) 2017-2018 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.
-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="skeleton">
<metric>
<xsl:apply-templates select="@*"/>
<title>LCOM4</title>
<description>
<xsl:text>'LCOM4' is a 1995 revision by Martin Hitz and Behzad Montazeri,
that further improves upon the revised versions LCOM3 proposed by
W. Li and S. Henry in "Maintenance Metrics for the Object Oriented Paradigm".
LCOM4 establishes connections between methods not just on shared attributes
of the class, but also on whether one method calls the other. That is,
methods A and B are said to be related if either both use at least one
instance variable in common, OR A calls B, OR B calls A.
LCOM4 tweaks the formula for method connections in order to eliminate
the distorting effects that 'getters' may have on earlier LCOM variants,
and also methods that purely rely on the reuse of other methods without
accessing attributes directly (think method overloads where the ones
with the least args reuse the ones with the most args).
</xsl:text>
</description>
<xsl:apply-templates select="node()"/>
</metric>
</xsl:template>
<xsl:template match="class">
<xsl:variable name="class_fqn" select="replace(string-join(../@id | @id, '.'), '^\.', '')"/>
<xsl:variable name="A" select="attributes/attribute[@static = 'false']/text()"/>
<xsl:variable name="a" select="count($A)"/>
<xsl:variable name="M" select="methods/method"/>
<xsl:variable name="m" select="count($M)"/>
<!--
@todo #8:30min LCOM4: #156 needs to be fixed in order to avoid confusing access to
fields belonging to other classes as if they actually belong to this class.
Once #156 is fixed, refactor xpaths 'this_attrs' and 'other_attrs' accordingly.
-->
<!--
@todo #8:30min LCOM4: need to finish implementing the rest of the test cases. Only test case
"MethodMethodCalls" was implemented in this 30min ticket.
-->
<xsl:variable name="E">
<xsl:for-each select="$M">
<xsl:variable name="this" select="."/>
<xsl:variable name="this_fullname" select="concat($class_fqn, '.', $this/@name)"/>
<xsl:variable name="this_attrs" select="$A[. = $this/ops/op[starts-with(@code, 'put') or starts-with(@code, 'get')]/text()]"/>
<xsl:variable name="this_methods" select="$this/ops/op[@code = 'call' and matches(replace(., $class_fqn, ''), '^\.[^.]+$')]"/>
<xsl:for-each select="$this/following-sibling::method">
<xsl:variable name="other" select="."/>
<xsl:variable name="other_fullname" select="concat($class_fqn, '.', $other/@name)"/>
<xsl:variable name="other_attrs" select="$A[. = $other/ops/op[starts-with(@code, 'put') or starts-with(@code, 'get')]/text()]"/>
<xsl:variable name="other_methods" select="$other/ops/op[@code = 'call' and matches(replace(., $class_fqn, ''), '^\.[^.]+$')]"/>
<xsl:if test="exists($this_attrs[.= $other_attrs]) or exists($this_methods[.= $other_fullname]) or exists($other_methods[.= $this_fullname])">
<pair/>
</xsl:if>
</xsl:for-each>
</xsl:for-each>
</xsl:variable>
<xsl:copy>
<xsl:attribute name="value">
<xsl:choose>
<xsl:when test="$m &lt; 2 or $a = 0">
<xsl:text>0</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:value-of select="format-number((((count($E/pair) div $a) - $m) div (1 - $m)), '0.####')"/>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates select="@*"/>
<vars>
<var id="methods">
<xsl:value-of select="$m"/>
</var>
<var id="attributes">
<xsl:value-of select="$a"/>
</var>
<var id="pairs">
<xsl:value-of select="count($E/pair)"/>
</var>
</vars>
</xsl:copy>
</xsl:template>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
147 changes: 147 additions & 0 deletions src/main/resources/org/jpeek/metrics/TLCOM.xsl
@@ -0,0 +1,147 @@
<?xml version="1.0"?>
<!--
The MIT License (MIT)
Copyright (c) 2017-2018 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.
-->
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">
<xsl:template match="skeleton">
<metric>
<xsl:apply-templates select="@*"/>
<title>TLCOM</title>
<description>
<xsl:text>
"Transitive Lack of Cohesion in Methods" (TLCOM) expands upon
the original definition for LCOM by extending the criteria for
estabilishing relations between method pairs by also including cases
when a m1 calls m2 and m2 happens to use attribute a2 of the class. This
then means that m1 transitively uses a2 via m2.
The chain of calls can have more than one hop: if m1 calls m2 which calls
m3 which calls m4, and m4 uses attribute a4, then m1 is said to transitively
use a4 as well.
The final formula is the same as the original LCOM:
TLCOM = |P| - |Q|, if |P| &gt; |Q|, otherwise it's 0 (zero). 'P' is
the set of method-pairs that are not connected according to the
definition above, and 'Q' is the set of method-pairs that are connected.
</xsl:text>
</description>
<xsl:apply-templates select="node()"/>
</metric>
</xsl:template>
<xsl:template match="class">
<!--
@todo #65:30min TLCOM: we are including access to ALL fields here. After #156 is fixed,
come back and refactor xpath for $Ms_that_use_attrs, $left_attrs, and $right_attrs
in order to avoid including access to fields belonging to other classes.
-->
<xsl:variable name="class_fqn" select="replace(string-join(../@id | @id, '.'), '^\.', '')"/>
<xsl:variable name="A" select="attributes/attribute"/>
<xsl:variable name="M" select="methods/method"/>
<xsl:variable name="Ms_that_use_attrs">
<xsl:for-each select="$M">
<xsl:if test="./ops/op[@code != 'call'][. = $A]">
<method>
<xsl:value-of select="concat($class_fqn, '.', @name)"/>
</method>
</xsl:if>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="pairs">
<xsl:for-each select="$M">
<xsl:variable name="left" select="."/>
<xsl:variable name="left_name" select="concat($class_fqn, '.', $left/@name)"/>
<xsl:variable name="left_attrs" select="$left/ops/op[@code != 'call' and exists(. = $A)]"/>
<xsl:variable name="left_calls" select="$left/ops/op[@code = 'call' and matches(., concat('^', $class_fqn, '\.[^\.]+$'))]"/>
<xsl:for-each select="$left/following-sibling::method">
<xsl:variable name="right" select="."/>
<xsl:variable name="right_name" select="concat($class_fqn, '.', $right/@name)"/>
<xsl:variable name="right_attrs" select="$right/ops/op[@code != 'call' and exists(. = $A)]"/>
<xsl:variable name="right_calls" select="$right/ops/op[@code = 'call' and matches(., concat('^', $class_fqn, '\.[^\.]+$'))]"/>
<!--
@todo #65:30min TLCOM: need to establish transitive connections between methods with arbitrarily long
chains of method calls between them. Right now, this code can establish transitive connections to a
max depth of 3 levels, ie up to something like this: m1 calls m2 calls m3 which uses a3 therefore m1 is
connected to m3. It will miss connections in longer chains. After fixing this, maybe we can add it to
the standard metrics in App.
-->
<!--
@todo #65:15min TLCOM: add test for OneMethodCreatesLambda after #171 is fixed. The extra synthetic
method introduced by the compiler distorts this metric's result.
-->
<xsl:choose>
<xsl:when test="$left_attrs[. = $right_attrs]">
<Q/>
</xsl:when>
<xsl:when test="exists($left_calls[matches(., $right_name)]) and exists($Ms_that_use_attrs/method[. = $right_name])">
<Q/>
</xsl:when>
<xsl:when test="exists($right_calls[matches(., $left_name)]) and exists($Ms_that_use_attrs/method[. = $left_name])">
<Q/>
</xsl:when>
<xsl:when test="exists($left_calls[. = $right_calls]) and exists($right_calls[. = $Ms_that_use_attrs/method])">
<Q/>
</xsl:when>
<xsl:when test="exists($right_calls[. = $left_calls]) and exists($left_calls[. = $Ms_that_use_attrs/method])">
<Q/>
</xsl:when>
<xsl:otherwise>
<P/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="P" select="count($pairs/P)"/>
<xsl:variable name="Q" select="count($pairs/Q)"/>
<xsl:copy>
<xsl:attribute name="value">
<xsl:choose>
<xsl:when test="$P &gt; $Q">
<xsl:value-of select="$P - $Q"/>
</xsl:when>
<xsl:otherwise>
<xsl:text>0</xsl:text>
</xsl:otherwise>
</xsl:choose>
</xsl:attribute>
<xsl:apply-templates select="@*"/>
<vars>
<var id="methods">
<xsl:value-of select="count($M)"/>
</var>
<var id="attributes">
<xsl:value-of select="count($A)"/>
</var>
<var id="|P|">
<xsl:value-of select="$P"/>
</var>
<var id="|Q|">
<xsl:value-of select="$Q"/>
</var>
</vars>
</xsl:copy>
</xsl:template>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
25 changes: 18 additions & 7 deletions src/test/java/org/jpeek/MetricsTest.java
Expand Up @@ -36,9 +36,6 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

// @todo #18:30min Impediment: #103 must be fixed before LCOM5 is tested
// against: NoMethods, OneVoidMethodWithoutParams, WithoutAttributes,
// OneMethodCreatesLambda. The LCOM5 value for these will be "NaN".
// @todo #93:30min NHD needs to be tested against the following after #103 is
// fixed: NoMethods, OneVoidMethodWithoutParams, WithoutAttributes,
// OneMethodCreatesLambda. NHD score for all these is "NaN".
Expand All @@ -49,9 +46,6 @@
// be tested in MetricsTest when the resulting value is "NaN". Affected
// tests are: NoMethods, OneVoidMethodWithoutParams, WithoutAttributes,
// OneMethodCreatesLambda.
// @todo #68:30min SCOM has an impediment on issue #114: cannot currently
// be tested against "Bar" because the skeleton is incorrectly excluding
// some attributes from some methods that are using them.
// @todo #103:30min NaN-based assertions introduced in #103 made complexity
// of `testsTarget` higher. Potentially, if more possible invariants will be
// introduced, enlarging complexity may become real problem for this method.
Expand Down Expand Up @@ -119,13 +113,21 @@ public static Collection<Object[]> targets() {
new Object[] {"MethodsWithDiffParamTypes", "LCOM5", 0.6667d},
new Object[] {"OverloadMethods", "LCOM5", 0.25d},
new Object[] {"TwoCommonAttributes", "LCOM5", 1.0d},
new Object[] {"NoMethods", "LCOM5", Double.NaN},
new Object[] {"WithoutAttributes", "LCOM5", Double.NaN},
new Object[] {"OneVoidMethodWithoutParams", "LCOM5", 1.0d},
new Object[] {"OneMethodCreatesLambda", "LCOM5", 1.5d},
new Object[] {"Bar", "NHD", 0.4d},
new Object[] {"Foo", "NHD", 0.3333d},
new Object[] {"MethodsWithDiffParamTypes", "NHD", 0.7143d},
new Object[] {"OverloadMethods", "NHD", 0.5333d},
new Object[] {"TwoCommonAttributes", "NHD", 0.3333d},
new Object[] {"MethodsWithDiffParamTypes", "CCM", 0.0476d},
new Object[] {"TwoCommonAttributes", "SCOM", 0.0d},
new Object[] {"NoMethods", "SCOM", Double.NaN},
new Object[] {"OneVoidMethodWithoutParams", "SCOM", 0.0d},
new Object[] {"WithoutAttributes", "SCOM", Double.NaN},
new Object[] {"OneMethodCreatesLambda", "SCOM", 0.0d},
new Object[] {"Foo", "LCOM2", 0.3333d},
new Object[] {"MethodsWithDiffParamTypes", "LCOM2", 0.5714d},
new Object[] {"NoMethods", "LCOM2", 1.0d},
Expand All @@ -144,7 +146,16 @@ public static Collection<Object[]> targets() {
new Object[] {"MethodsWithDiffParamTypes", "PCC", 0.3333d},
new Object[] {"Foo", "OCC", 0.5d},
new Object[] {"Foo", "TCC", 1.0d},
new Object[] {"MethodsWithDiffParamTypes", "TCC", 0.2d}
new Object[] {"MethodsWithDiffParamTypes", "TCC", 0.2d},
new Object[] {"Foo", "TLCOM", 1.0d},
new Object[] {"MethodsWithDiffParamTypes", "TLCOM", 15.0d},
new Object[] {"NoMethods", "TLCOM", 0.0d},
new Object[] {"OneVoidMethodWithoutParams", "TLCOM", 1.0d},
new Object[] {"OnlyOneMethodWithParams", "TLCOM", 0.0d},
new Object[] {"OverloadMethods", "TLCOM", 0.0d},
new Object[] {"TwoCommonAttributes", "TLCOM", 4.0d},
new Object[] {"WithoutAttributes", "TLCOM", 1.0d},
new Object[] {"MethodMethodCalls", "LCOM4", 0.6d}
);
}

Expand Down
23 changes: 23 additions & 0 deletions src/test/resources/org/jpeek/samples/MethodMethodCalls.java
@@ -0,0 +1,23 @@
public final class MethodMethodCalls {
private int num;

public int methodOne() {
return num++;
}

public int methodTwo() {
return this.methodOne();
}

public int methodThree() {
return num--;
}

public int methodFour() {
return this.methodTwo();
}

public int methodFive() {
throw new UnsupportedOperationException();
}
}

0 comments on commit 9f94179

Please sign in to comment.