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}