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 */ 020package org.sonar.java.bytecode.visitor; 021 022import java.util.*; 023 024import org.sonar.java.bytecode.asm.AsmClass; 025import org.sonar.java.bytecode.asm.AsmEdge; 026import org.sonar.java.bytecode.asm.AsmField; 027import org.sonar.java.bytecode.asm.AsmMethod; 028import org.sonar.java.bytecode.asm.AsmResource; 029import org.sonar.java.squid.JavaSquidConfiguration; 030import org.sonar.squid.api.SourceCodeEdgeUsage; 031import org.sonar.squid.measures.Metric; 032 033public 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 @Override 044 public void visitClass(AsmClass asmClass) { 045 this.asmClass = asmClass; 046 unrelatedBlocks = new ArrayList<Set<AsmResource>>(); 047 } 048 049 @Override 050 public void visitMethod(AsmMethod asmMethod) { 051 if (isMethodElligibleForLCOM4Computation(asmMethod)) { 052 ensureBlockIsCreated(asmMethod); 053 for (AsmEdge edge : asmMethod.getOutgoingEdges()) { 054 if (isCallToInternalFieldOrMethod(edge) && isNotCallToExcludedFieldFromLcom4Calculation(edge.getTo())) { 055 AsmResource toResource = getAccessedFieldOrMethod(edge.getTo()); 056 linkAsmResources(asmMethod, toResource); 057 } 058 } 059 } 060 } 061 062 private AsmResource getAccessedFieldOrMethod(AsmResource resource) { 063 if (resource instanceof AsmMethod && ((AsmMethod)resource).isAccessor()) { 064 return ((AsmMethod)resource).getAccessedField(); 065 } else { 066 return resource; 067 } 068 } 069 070 private boolean isNotCallToExcludedFieldFromLcom4Calculation(AsmResource to) { 071 if (to instanceof AsmField) { 072 AsmField field = (AsmField)to; 073 return !fieldsToExcludeFromLcom4Calculation.contains(field.getName()); 074 } 075 return true; 076 } 077 078 private boolean isMethodElligibleForLCOM4Computation(AsmMethod asmMethod) { 079 return !asmMethod.isAbstract() && !asmMethod.isStatic() && !asmMethod.isConstructor() && !asmMethod.isEmpty() 080 && !asmMethod.isAccessor() && asmMethod.isBodyLoaded(); 081 } 082 083 private void removeIsolatedMethodBlocks() { 084 Iterator<Set<AsmResource>> iterator = unrelatedBlocks.iterator(); 085 086 while (iterator.hasNext()) { 087 Set<AsmResource> block = iterator.next(); 088 if (block.size() == 1) { 089 iterator.remove(); 090 } 091 } 092 093 } 094 095 @Override 096 public void leaveClass(AsmClass asmClass) { 097 removeIsolatedMethodBlocks(); 098 099 int lcom4 = unrelatedBlocks.size(); 100 if (lcom4 == 0) { 101 lcom4 = 1; 102 } 103 104 getSourceClass(asmClass).add(Metric.LCOM4, lcom4); 105 getSourceClass(asmClass).addData(Metric.LCOM4_BLOCKS, unrelatedBlocks); 106 107 if (isMainPublicClassInFile(asmClass)) { 108 getSourceFile(asmClass).add(Metric.LCOM4, lcom4); 109 getSourceFile(asmClass).addData(Metric.LCOM4_BLOCKS, unrelatedBlocks); 110 } 111 } 112 113 private void ensureBlockIsCreated(AsmResource resource) { 114 getOrCreateResourceBlock(resource); 115 } 116 117 private void linkAsmResources(AsmResource resourceA, AsmResource resourceB) { 118 Set<AsmResource> blockA = getOrCreateResourceBlock(resourceA); 119 Set<AsmResource> blockB = getOrCreateResourceBlock(resourceB); 120 121 // getOrCreateResourceBlock() returns the same block instance if resourceA and resourceB are identical or already in the same block 122 // TODO: Avoid this violation by using a Disjoint Union Set which is also more efficient performance-wise 123 // See: http://en.wikipedia.org/wiki/Disjoint-set_data_structure 124 if (blockA == blockB) { // NOSONAR false-positive Compare Objects With Equals 125 return; 126 } 127 128 blockA.addAll(blockB); 129 unrelatedBlocks.remove(blockB); 130 } 131 132 private boolean isCallToInternalFieldOrMethod(AsmEdge edge) { 133 return edge.getTargetAsmClass() == asmClass && (edge.getUsage() == SourceCodeEdgeUsage.CALLS_FIELD || edge.getUsage() == SourceCodeEdgeUsage.CALLS_METHOD); 134 } 135 136 private Set<AsmResource> getOrCreateResourceBlock(AsmResource resource) { 137 for (Set<AsmResource> block: unrelatedBlocks) { 138 if (block.contains(resource)) { 139 return block; 140 } 141 } 142 143 Set<AsmResource> block = new HashSet<AsmResource>(); 144 block.add(resource); 145 unrelatedBlocks.add(block); 146 return block; 147 } 148}