Skip to content

Commit

Permalink
ncc calculation using java
Browse files Browse the repository at this point in the history
  • Loading branch information
starkda committed Apr 1, 2024
1 parent d26f4e7 commit 47254cc
Show file tree
Hide file tree
Showing 5 changed files with 204 additions and 123 deletions.
8 changes: 7 additions & 1 deletion src/main/java/org/jpeek/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
import org.cactoos.scalar.IoChecked;
import org.cactoos.scalar.LengthOf;
import org.jpeek.calculus.Calculus;
import org.jpeek.calculus.java.Ccm;
import org.jpeek.calculus.xsl.XslCalculus;
import org.jpeek.skeleton.Skeleton;
import org.xembly.Directives;
Expand Down Expand Up @@ -250,14 +251,19 @@ private void buildReport(final Collection<XSL> layers, final Collection<Report>
final Base base = new DefaultBase(this.input);
final XML skeleton = new Skeleton(base).xml();
final XSL chain = new XSLChain(layers);
final Calculus xsl = new XslCalculus();
this.save(skeleton.toString(), "skeleton.xml");
Arrays.stream(Metrics.values())
.filter(
metric -> this.params.containsKey(metric.name())
)
.forEach(
metric -> {
final Calculus xsl;
if (metric == Metrics.CCM) {
xsl = new Ccm();
} else {
xsl = new XslCalculus();
}
if (Objects.nonNull(metric.getSigma())) {
reports.add(
new XslReport(
Expand Down
197 changes: 156 additions & 41 deletions src/main/java/org/jpeek/calculus/java/Ccm.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,26 @@
package org.jpeek.calculus.java;

import com.jcabi.xml.XML;
import com.jcabi.xml.XMLDocument;
import com.jcabi.xml.XSLDocument;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.cactoos.io.ResourceOf;
import org.cactoos.io.UncheckedInput;
import org.cactoos.text.FormattedText;
import org.cactoos.text.Joined;
import org.jpeek.calculus.Calculus;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
* CCM metric Java calculus.
* This class implements the Calculus interface to provide functionality
* for computing the CCM metric for Java code.
* @since 0.30.25
*/
public final class Ccm implements Calculus {
Expand All @@ -52,57 +61,163 @@ public XML node(
).toString()
);
}
return Ccm.withFixedNcc(
new XSLDocument(
new UncheckedInput(
new ResourceOf("org/jpeek/metrics/CCM.xsl")
).stream()
).transform(skeleton),
skeleton
final XSLDocument doc = new XSLDocument(
new UncheckedInput(
new ResourceOf("org/jpeek/metrics/CCM.xsl")
).stream()
);
final XML meta = addMetaInformation(skeleton, params);
return doc.transform(meta);
}

/**
* Updates the transformed xml with proper NCC value.
* @param transformed The transformed XML skeleton.
* @param skeleton XML Skeleton
* @return XML with fixed NCC.
* Adds meta information to the skeleton XML document.
* This method modifies the skeleton XML document by adding meta information
* about the computed CCM metric.
* @param skeleton The skeleton XML document representing the code structure.
* @param params Parameters for the computation.
* @return The modified XML document containing meta information.
*/
private static XML withFixedNcc(final XML transformed, final XML skeleton) {
final List<XML> packages = transformed.nodes("//package");
for (final XML elt : packages) {
final String pack = elt.xpath("/@id").get(0);
final List<XML> classes = elt.nodes("//class");
for (final XML clazz : classes) {
Ccm.updateNcc(skeleton, pack, clazz);
private static XML addMetaInformation(final XML skeleton, final Map<String, Object> params) {
try {
final Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
.newDocument();
final Element meta = doc.createElement("meta");
final List<XML> packages = skeleton.nodes("//package");
for (final XML pack : packages) {
final Element tag = doc.createElement("package");
tag.setAttribute(
"id", pack.node().getAttributes().getNamedItem("id").getNodeValue()
);
final List<XML> classes = pack.nodes("class");
for (final XML clazz: classes) {
final Element sub = doc.createElement("class");
sub.appendChild(addNccTag(doc, clazz, params));
sub.setAttribute(
"id",
clazz.node().getAttributes().getNamedItem("id").getNodeValue()
);
tag.appendChild(sub);
}
meta.appendChild(tag);
}
final Node repr = skeleton.node();
final Node text = repr.getFirstChild().getOwnerDocument()
.importNode(doc.createTextNode("\n"), true);
final Node node = repr.getFirstChild().getOwnerDocument()
.importNode(meta, true);
repr.getFirstChild().appendChild(text);
repr.getFirstChild().appendChild(node);
return new XMLDocument(repr);
} catch (final ParserConfigurationException ex) {
throw new IllegalStateException(ex);
}
return transformed;
}

/**
* Updates the xml node of the class with proper NCC value.
* @param skeleton XML Skeleton
* @param pack Package name
* @param clazz Class node in the resulting xml
* @todo #449:30min Implement NCC calculation with `XmlGraph` and use this
* class to fix CCM metric (see issue #449). To do this, this class, once
* it works correctly, should be integrated with XSL based calculuses in
* `XslReport` (see `todo #449` in Calculus). Write a test to make sure
* the metric is calculated correctly. Also, decide whether the
* whole CCM metric should be implemented in Java, or only the NCC part.
* Update this `todo` accordingly.
* Adds NCC (Number of Component Connections) tag to the XML document.
* This method calculates the NCC for a given class and adds it as a tag to the XML document.
* @param doc The XML document to which the NCC tag will be added.
* @param clazz The XML representation of the class.
* @param params Parameters for the computation (unused).
* @return The NCC node.
*/
private static void updateNcc(
final XML skeleton, final String pack, final XML clazz
private static Node addNccTag(final Document doc, final XML clazz,
final Map<String, Object> params
) {
throw new UnsupportedOperationException(
new Joined(
"",
skeleton.toString(),
pack,
clazz.toString()
).toString()
);
final Element ncc = doc.createElement("ncc");
ncc.appendChild(doc.createTextNode(calculateComponents(clazz, params).toString()));
return ncc;
}

/**
* Calculates the number of components for a given class.
* This method calculates the number of components for a class using the Union-Find algorithm.
* @param clazz The XML representation of the class.
* @param params Parameters for the computation.
* @return The number of components.
*/
private static Integer calculateComponents(final XML clazz, final Map<String, Object> params) {
final Map<String, List<String>> connections = new HashMap<>();
final Map<String, String> parents = new HashMap<>();
for (final XML method : clazz.nodes("methods/method")) {
if (!params.containsKey("include-static-methods")
&& method.node().getAttributes().getNamedItem("static").getNodeValue()
.equals("true")) {
continue;
}
final String name = method.node().getAttributes().getNamedItem("name").getNodeValue();
if (!params.containsKey("include-ctors") && name.equals("<init>")) {
continue;
}
parents.put(name, name);
final List<XML> ops = method.nodes("ops/op");
for (final XML operation : ops) {
final String var = operation.node().getTextContent();
if (connections.containsKey(var)) {
connections.get(var).add(name);
} else {
final List<String> init = new ArrayList<>(0);
init.add(name);
connections.put(var, init);
}
}
}
return unionFind(parents, connections);
}

/**
* Performs the Union-Find algorithm to calculate the number of components.
* This method implements the Union-Find algorithm to calculate the number of components.
* @param parents The map representing the parent relationship.
* @param connections The map representing the connections between variables and methods.
* @return The number of components.
*/
private static Integer unionFind(final Map<String, String> parents,
final Map<String, List<String>> connections
) {
int answer = parents.size();
for (final List<String> conns : connections.values()) {
final String initial = conns.get(0);
for (final String connectable : conns) {
if (!parents.get(initial).equals(parents.get(connectable))) {
answer -= 1;
}
unite(initial, connectable, parents);
}
}
return answer;
}

/**
* Gets the parent of a node using the Union-Find algorithm.
* This method retrieves the parent of a node using the Union-Find algorithm.
* @param node The node whose parent is to be found.
* @param parents The map representing the parent relationship.
* @return The parent of the node.
*/
private static String getParent(final String node, final Map<String, String> parents) {
String ancestor = node;
while (!parents.get(ancestor).equals(ancestor)) {
ancestor = parents.get(ancestor);
}
return ancestor;
}

/**
* Unites two nodes using the Union-Find algorithm.
* This method unites two nodes using the Union-Find algorithm.
* @param node The first node.
* @param son The second node.
* @param parents The map representing the parent relationship.
*/
private static void unite(final String node, final String son,
final Map<String, String> parents
) {
final String root = getParent(node, parents);
final String attachable = getParent(son, parents);
if (!root.equals(attachable)) {
parents.put(attachable, root);
}
}
}
69 changes: 6 additions & 63 deletions src/main/resources/org/jpeek/metrics/CCM.xsl
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ 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/meta"/>
<xsl:template match="skeleton">
<metric>
<xsl:apply-templates select="@*"/>
Expand All @@ -40,7 +41,11 @@ SOFTWARE.
<xsl:apply-templates select="node()"/>
</metric>
</xsl:template>
<xsl:variable name="met" select="/skeleton/meta"/>
<xsl:template match="class">
<xsl:variable name="currentClassId" select="@id"/>
<xsl:variable name="currentPackageId" select="../@id"/>
<xsl:variable name="currentClassNcc" select="$met/package[@id = $currentPackageId]/class[@id = $currentClassId]/ncc"/>
<xsl:variable name="methods" select="methods/method[@ctor='false']"/>
<xsl:variable name="edges">
<xsl:for-each select="$methods">
Expand Down Expand Up @@ -68,52 +73,9 @@ SOFTWARE.
</xsl:for-each>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="graph">
<xsl:for-each select="$methods">
<xsl:variable name="method" select="."/>
<node id="{$method/@name}">
<xsl:for-each select="$edges/edge[method[1]/text() = $method/@name]">
<edge>
<xsl:value-of select="method[2]/text()"/>
</edge>
</xsl:for-each>
</node>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="result">
<xsl:for-each select="$graph/node">
<xsl:variable name="root" select="."/>
<xsl:variable name="id" select="$root/@id"/>
<xsl:element name="group">
<xsl:attribute name="id">
<xsl:value-of select="$id"/>
</xsl:attribute>
<xsl:call-template name="group">
<xsl:with-param name="x" select="$root"/>
<xsl:with-param name="seen" select="concat($id, ',')"/>
<xsl:with-param name="graph" select="$graph"/>
</xsl:call-template>
<xsl:element name="edge">
<xsl:value-of select="$id"/>
</xsl:element>
</xsl:element>
</xsl:for-each>
</xsl:variable>
<xsl:variable name="counts">
<xsl:for-each-group select="$result/group" group-by="@id">
<xsl:variable name="minValue">
<xsl:perform-sort select="current-group()/edge">
<xsl:sort select="./text()"/>
</xsl:perform-sort>
</xsl:variable>
<minValue>
<xsl:value-of select="$minValue/edge[1]/text()"/>
</minValue>
</xsl:for-each-group>
</xsl:variable>
<xsl:copy>
<xsl:variable name="nc" select="count($edges/edge) div 2"/>
<xsl:variable name="ncc" select="count(distinct-values($counts/minValue/text()))"/>
<xsl:variable name="ncc" select="$currentClassNcc"/>
<xsl:variable name="nmp" select="(count($methods) * (count($methods) - 1)) div 2"/>
<xsl:attribute name="value">
<xsl:choose>
Expand Down Expand Up @@ -147,23 +109,4 @@ SOFTWARE.
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template name="group">
<xsl:param name="x" as="element()"/>
<xsl:param name="seen"/>
<xsl:param name="graph"/>
<xsl:variable name="used" select="concat($seen, concat($x/@id, ','))"/>
<xsl:for-each select="$x/edge">
<xsl:variable name="r" select="."/>
<xsl:if test="not(contains($used, concat($r/text(), ',')))">
<xsl:element name="edge">
<xsl:value-of select="$r"/>
</xsl:element>
<xsl:call-template name="group">
<xsl:with-param name="seen" select="$used"/>
<xsl:with-param name="x" select="$graph/node[@id=$r][1]"/>
<xsl:with-param name="graph" select="$graph"/>
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
Loading

0 comments on commit 47254cc

Please sign in to comment.