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    }