/*
* Copyright 2005, Sixth and Red River Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sixrr.stockmetrics.classCalculators;
import com.intellij.psi.*;
import org.jetbrains.annotations.NonNls;
import java.util.*;
public class LackOfCohesionOfMethodsClassCalculator extends ClassCalculator {
private static final @NonNls
Set<String> boilerplateMethods = new HashSet<String>();
static {
//noinspection HardCodedStringLiteral
Collections.addAll(boilerplateMethods, "toString", "equals", "hashCode", "finalize", "clone", "readObject",
"writeObject");
}
@Override
protected PsiElementVisitor createVisitor() {
return new Visitor();
}
private class Visitor extends JavaRecursiveElementVisitor {
@Override
public void visitClass(PsiClass aClass) {
super.visitClass(aClass);
if (isConcreteClass(aClass)) {
final PsiMethod[] methods = aClass.getMethods();
final Set<PsiMethod> applicableMethods = new HashSet<PsiMethod>();
for (PsiMethod method : methods) {
final String methodName = method.getName();
if (!method.isConstructor() && !boilerplateMethods.contains(methodName)) {
applicableMethods.add(method);
}
}
final Map<PsiMethod, Set<PsiField>> fieldsPerMethod = calculateFieldUsage(applicableMethods);
final Map<PsiMethod, Set<PsiMethod>> linkedMethods = calculateMethodLinkage(applicableMethods);
final Set<Set<PsiMethod>> components = calculateComponents(applicableMethods, fieldsPerMethod,
linkedMethods);
final int numComponents = components.size();
postMetric(aClass, numComponents);
}
}
}
private static Set<Set<PsiMethod>> calculateComponents(Set<PsiMethod> applicableMethods,
Map<PsiMethod, Set<PsiField>> fieldsPerMethod,
Map<PsiMethod, Set<PsiMethod>> linkedMethods) {
final Set<Set<PsiMethod>> components = new HashSet<Set<PsiMethod>>();
while (applicableMethods.size() > 0) {
final Set<PsiMethod> component = new HashSet<PsiMethod>();
final Set<PsiField> fieldsUsed = new HashSet<PsiField>();
final PsiMethod testMethod = applicableMethods.iterator().next();
applicableMethods.remove(testMethod);
component.add(testMethod);
fieldsUsed.addAll(fieldsPerMethod.get(testMethod));
while (true) {
final Set<PsiMethod> methodsToAdd = new HashSet<PsiMethod>();
for (PsiMethod method : applicableMethods) {
if (overlaps(fieldsPerMethod.get(method), fieldsUsed) ||
overlaps(linkedMethods.get(method), component)) {
methodsToAdd.add(method);
fieldsUsed.addAll(fieldsPerMethod.get(method));
}
}
if (methodsToAdd.size() == 0) {
break;
}
applicableMethods.removeAll(methodsToAdd);
component.addAll(methodsToAdd);
}
components.add(component);
}
return components;
}
private static boolean overlaps(Set<?> set1, Set<?> set2) {
for (Object element : set1) {
if (set2.contains(element)) {
return true;
}
}
return false;
}
private static Map<PsiMethod, Set<PsiField>> calculateFieldUsage(Set<PsiMethod> applicableMethods) {
final Map<PsiMethod, Set<PsiField>> fieldsPerMethod = new HashMap<PsiMethod, Set<PsiField>>();
for (PsiMethod method : applicableMethods) {
final Set<PsiField> fields = calculateUsedFields(method);
fieldsPerMethod.put(method, fields);
}
return fieldsPerMethod;
}
private static Set<PsiField> calculateUsedFields(PsiMethod method) {
final FieldsUsedVisitor visitor = new FieldsUsedVisitor();
method.accept(visitor);
return visitor.getFieldsUsed();
}
private static class FieldsUsedVisitor extends JavaRecursiveElementVisitor {
private final Set<PsiField> fieldsUsed = new HashSet<PsiField>();
FieldsUsedVisitor() {
}
@Override
public void visitReferenceExpression(PsiReferenceExpression referenceExpression) {
super.visitReferenceExpression(referenceExpression);
final PsiElement referent = referenceExpression.resolve();
if (!(referent instanceof PsiField)) {
return;
}
final PsiField field = (PsiField) referent;
fieldsUsed.add(field);
}
@SuppressWarnings({"ReturnOfCollectionOrArrayField"})
public Set<PsiField> getFieldsUsed() {
return fieldsUsed;
}
}
private static Map<PsiMethod, Set<PsiMethod>> calculateMethodLinkage(Set<PsiMethod> applicableMethods) {
final Map<PsiMethod, Set<PsiMethod>> linkages = new HashMap<PsiMethod, Set<PsiMethod>>();
for (PsiMethod method : applicableMethods) {
final Set<PsiMethod> linkedMethods = calculateLinkedMethods(method, applicableMethods);
linkages.put(method, linkedMethods);
}
//add the transpose, since linkage is undirected
for (PsiMethod method : applicableMethods) {
final Set<PsiMethod> linkedMethods = linkages.get(method);
for (PsiMethod linkedMethod : linkedMethods) {
linkages.get(linkedMethod).add(method);
}
}
return linkages;
}
private static Set<PsiMethod> calculateLinkedMethods(PsiMethod method, Set<PsiMethod> applicableMethods) {
final MethodsUsedVisitor visitor = new MethodsUsedVisitor(applicableMethods);
method.accept(visitor);
return visitor.getMethodsUsed();
}
private static class MethodsUsedVisitor extends JavaRecursiveElementVisitor {
private final Set<PsiMethod> applicableMethods;
private final Set<PsiMethod> methodsUsed = new HashSet<PsiMethod>();
MethodsUsedVisitor(Set<PsiMethod> applicableMethods) {
this.applicableMethods = applicableMethods;
}
@Override
public void visitMethodCallExpression(PsiMethodCallExpression callExpression) {
super.visitMethodCallExpression(callExpression);
final PsiMethod testMethod = callExpression.resolveMethod();
if (applicableMethods.contains(testMethod)) {
methodsUsed.add(testMethod);
}
}
@SuppressWarnings({"ReturnOfCollectionOrArrayField"})
public Set<PsiMethod> getMethodsUsed() {
return methodsUsed;
}
}
}