001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2011 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 }