001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2012 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar is free software; you can redistribute it and/or
007 * modify it under the terms of the GNU Lesser General Public
008 * License as published by the Free Software Foundation; either
009 * version 3 of the License, or (at your option) any later version.
010 *
011 * Sonar is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * You should have received a copy of the GNU Lesser General Public
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
019 */
020 package org.sonar.java.bytecode.visitor;
021
022 import java.util.*;
023
024 import org.sonar.java.bytecode.asm.AsmClass;
025 import org.sonar.java.bytecode.asm.AsmEdge;
026 import org.sonar.java.bytecode.asm.AsmField;
027 import org.sonar.java.bytecode.asm.AsmMethod;
028 import org.sonar.java.bytecode.asm.AsmResource;
029 import org.sonar.java.squid.JavaSquidConfiguration;
030 import org.sonar.squid.api.SourceCodeEdgeUsage;
031 import org.sonar.squid.measures.Metric;
032
033 public class LCOM4Visitor extends BytecodeVisitor {
034
035 private AsmClass asmClass;
036 private List<Set<AsmResource>> unrelatedBlocks = null;
037 private final Set<String> fieldsToExcludeFromLcom4Calculation;
038
039 public LCOM4Visitor(JavaSquidConfiguration conf) {
040 this.fieldsToExcludeFromLcom4Calculation = conf.getFielsToExcludeFromLcom4Calculation();
041 }
042
043 public void visitClass(AsmClass asmClass) {
044 this.asmClass = asmClass;
045 unrelatedBlocks = new ArrayList<Set<AsmResource>>();
046 }
047
048 public void visitMethod(AsmMethod asmMethod) {
049 if (isMethodElligibleForLCOM4Computation(asmMethod)) {
050 ensureBlockIsCreated(asmMethod);
051 for (AsmEdge edge : asmMethod.getOutgoingEdges()) {
052 if (isCallToInternalFieldOrMethod(edge) && isNotCallToExcludedFieldFromLcom4Calculation(edge.getTo())) {
053 AsmResource toResource = getAccessedFieldOrMethod(edge.getTo());
054 linkAsmResources(asmMethod, toResource);
055 }
056 }
057 }
058 }
059
060 private AsmResource getAccessedFieldOrMethod(AsmResource resource) {
061 if (resource instanceof AsmMethod && ((AsmMethod)resource).isAccessor()) {
062 return ((AsmMethod)resource).getAccessedField();
063 } else {
064 return resource;
065 }
066 }
067
068 private boolean isNotCallToExcludedFieldFromLcom4Calculation(AsmResource to) {
069 if (to instanceof AsmField) {
070 AsmField field = (AsmField)to;
071 return !fieldsToExcludeFromLcom4Calculation.contains(field.getName());
072 }
073 return true;
074 }
075
076 private boolean isMethodElligibleForLCOM4Computation(AsmMethod asmMethod) {
077 return !asmMethod.isAbstract() && !asmMethod.isStatic() && !asmMethod.isConstructor() && !asmMethod.isEmpty()
078 && !asmMethod.isAccessor() && asmMethod.isBodyLoaded();
079 }
080
081 private void removeIsolatedMethodBlocks() {
082 Iterator<Set<AsmResource>> iterator = unrelatedBlocks.iterator();
083
084 while (iterator.hasNext()) {
085 Set<AsmResource> block = iterator.next();
086 if (block.size() == 1) {
087 iterator.remove();
088 }
089 }
090
091 }
092
093 public void leaveClass(AsmClass asmClass) {
094 removeIsolatedMethodBlocks();
095
096 int lcom4 = unrelatedBlocks.size();
097 if (lcom4 == 0) {
098 lcom4 = 1;
099 }
100
101 getSourceClass(asmClass).add(Metric.LCOM4, lcom4);
102 getSourceClass(asmClass).addData(Metric.LCOM4_BLOCKS, unrelatedBlocks);
103
104 if (isMainPublicClassInFile(asmClass)) {
105 getSourceFile(asmClass).add(Metric.LCOM4, lcom4);
106 getSourceFile(asmClass).addData(Metric.LCOM4_BLOCKS, unrelatedBlocks);
107 }
108 }
109
110 private void ensureBlockIsCreated(AsmResource resource) {
111 getOrCreateResourceBlock(resource);
112 }
113
114 private void linkAsmResources(AsmResource resourceA, AsmResource resourceB) {
115 Set<AsmResource> blockA = getOrCreateResourceBlock(resourceA);
116 Set<AsmResource> blockB = getOrCreateResourceBlock(resourceB);
117
118 // getOrCreateResourceBlock() returns the same block instance if resourceA and resourceB are identical or already in the same block
119 // TODO: Avoid this violation by using a Disjoint Union Set which is also more efficient performance-wise
120 // See: http://en.wikipedia.org/wiki/Disjoint-set_data_structure
121 if (blockA == blockB) { // NOSONAR false-positive Compare Objects With Equals
122 return;
123 }
124
125 blockA.addAll(blockB);
126 unrelatedBlocks.remove(blockB);
127 }
128
129 private boolean isCallToInternalFieldOrMethod(AsmEdge edge) {
130 return edge.getTargetAsmClass() == asmClass && (edge.getUsage() == SourceCodeEdgeUsage.CALLS_FIELD || edge.getUsage() == SourceCodeEdgeUsage.CALLS_METHOD);
131 }
132
133 private Set<AsmResource> getOrCreateResourceBlock(AsmResource resource) {
134 for (Set<AsmResource> block: unrelatedBlocks) {
135 if (block.contains(resource)) {
136 return block;
137 }
138 }
139
140 Set<AsmResource> block = new HashSet<AsmResource>();
141 block.add(resource);
142 unrelatedBlocks.add(block);
143 return block;
144 }
145 }