package com.ikokoon.serenity.process.aggregator; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import com.ikokoon.serenity.model.Class; import com.ikokoon.serenity.model.Composite; import com.ikokoon.serenity.model.Line; import com.ikokoon.serenity.model.Method; import com.ikokoon.serenity.model.Package; import com.ikokoon.serenity.persistence.IDataBase; import com.ikokoon.toolkit.Toolkit; /** * @author Michael Couck * @since 07.03.10 * @version 01.00 */ public abstract class AAggregator implements IAggregator { private static final int PRECISION = 2; protected Logger logger = Logger.getLogger(this.getClass()); protected IDataBase dataBase; private Map<Object, List<?>> lines = new HashMap<Object, List<?>>(); private Map<Object, List<?>> methods = new HashMap<Object, List<?>>(); public AAggregator(IDataBase dataBase) { this.dataBase = dataBase; } /** * Returns a list of lines in the packages, i.e. all the lines in the packages. * * @param pakkages * @return */ @SuppressWarnings("unchecked") protected List<Line<?, ?>> getLines(List<Package> pakkages) { List<Line<?, ?>> projectLines = new ArrayList<Line<?, ?>>(); for (Package<?, ?> pakkage : pakkages) { List<Line<?, ?>> lines = (List<Line<?, ?>>) this.lines.get(pakkage); if (lines == null) { lines = getLines(pakkage); } projectLines.addAll(lines); } return projectLines; } /** * Returns a list of lines in the package. * * @param pakkage * @return */ @SuppressWarnings("unchecked") protected List<Line<?, ?>> getLines(Package<?, ?> pakkage) { List<Line<?, ?>> packageLines = new ArrayList<Line<?, ?>>(); for (Class<?, ?> klass : pakkage.getChildren()) { List<Line<?, ?>> lines = (List<Line<?, ?>>) this.lines.get(klass); if (lines == null) { lines = getLines(klass, new ArrayList<Line<?, ?>>()); } packageLines.addAll(lines); } return packageLines; } /** * Returns a list of lines in the class. * * @param klass * @param lines * @return */ @SuppressWarnings("unchecked") protected List<Line<?, ?>> getLines(Class<?, ?> klass, List<Line<?, ?>> lines) { for (Class<?, ?> innerKlass : klass.getInnerClasses()) { getLines(innerKlass, lines); } List<Line<?, ?>> setLines = (List<Line<?, ?>>) this.lines.get(klass); if (setLines == null) { for (Method<?, ?> method : klass.getChildren()) { for (Line<?, ?> line : method.getChildren()) { if (!containsLine(lines, line)) { lines.add(line); } } } } else { lines.addAll(setLines); } return lines; } /** * Returns true if the specified set contains the line. * * @param lines * @param line * @return */ private boolean containsLine(List<Line<?, ?>> lines, Line<?, ?> line) { for (Line<?, ?> setLine : lines) { if (setLine.getNumber() == line.getNumber()) { return true; } } return false; } /** * Returns a list of methods that are in the packages, i.e. all the methods in the package. * * @param pakkages * @return */ @SuppressWarnings("unchecked") protected List<Method<?, ?>> getMethods(Collection<Package> pakkages) { List<Method<?, ?>> projectMethods = new ArrayList<Method<?, ?>>(); for (Package<?, ?> pakkage : pakkages) { List<Method<?, ?>> methods = (List<Method<?, ?>>) this.methods.get(pakkage); if (methods == null) { methods = getMethods(pakkage); } projectMethods.addAll(methods); } return projectMethods; } @SuppressWarnings("unchecked") protected List<Method<?, ?>> getMethods(Package<?, ?> pakkage) { List<Method<?, ?>> packageMethods = (List<Method<?, ?>>) methods.get(pakkage); if (packageMethods == null) { packageMethods = new ArrayList<Method<?, ?>>(); for (Class<?, ?> klass : pakkage.getChildren()) { List<Method<?, ?>> methods = new ArrayList<Method<?, ?>>(); getMethods(klass, methods); packageMethods.addAll(methods); } } return packageMethods; } protected List<Method<?, ?>> getMethods(Class<?, ?> klass, List<Method<?, ?>> methods) { for (Class<?, ?> innerKlass : klass.getInnerClasses()) { getMethods(innerKlass, methods); } for (Method<?, ?> method : klass.getChildren()) { methods.add(method); } return methods; } protected void setPrecision(Composite<?, ?> composite) { Field[] fields = composite.getClass().getDeclaredFields(); for (Field field : fields) { if (double.class.isAssignableFrom(field.getType()) || Double.class.isAssignableFrom(field.getDeclaringClass())) { try { field.setAccessible(true); double value = field.getDouble(composite); value = Toolkit.format(value, PRECISION); field.setDouble(composite, value); } catch (Exception e) { logger.error("Exception accessing the field : " + field, e); } } } } /** * Distance from the Main Sequence (D): The perpendicular distance of a package from the idealised line A + I = 1. This metric is an indicator of * the package's balance between abstractness and stability. A package squarely on the main sequence is optimally balanced with respect to its * abstractness and stability. Ideal packages are either completely abstract and stable (x=0, y=1) or completely concrete and unstable (x=1, y=0). * The range for this metric is 0 to 1, with D=0 indicating a package that is coincident with the main sequence and D=1 indicating a package that * is as far from the main sequence as possible. * * 1) u = (x3 - x1)(x2 - x1) + (y3 - y1)(y2 - y1) / ||p2 - p1||² <br> * 2) y = mx + c, 0 = ax + by + c, d = |am + bn + c| / sqrt(a² + b²) : d= |-stability + -abstractness + 1| / sqrt(-1² + -1²) * * @param stability * @param abstractness * @return */ protected double getDistance(double stability, double abstractness) { double a = -1, b = -1; double distance = Math.abs(-stability + -abstractness + 1) / Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2)); return distance; } /** * Abstractness (A): The ratio of the number of abstract classes (and interfaces) in the analyzed package to the total number of classes in the * analyzed package. The range for this metric is 0 to 1, with A=0 indicating a completely concrete package and A=1 indicating a completely * abstract package. * * @param interfaces * @param implementations * @return */ protected double getAbstractness(double interfaces, double implementations) { double abstractness = (interfaces + implementations) > 0 ? interfaces / (interfaces + implementations) : 1d; return abstractness; } /** * Instability (I): The ratio of efferent coupling (Ce) to total coupling (Ce + Ca) such that I = Ce / (Ce + Ca). This metric is an indicator of * the package's resilience to change. The range for this metric is 0 to 1, with I=0 indicating a completely stable package and I=1 indicating a * completely instable package. * * @param efferent * @param afferent * @return */ protected double getStability(double efferent, double afferent) { double stability = (efferent + afferent) > 0 ? efferent / (efferent + afferent) : 1d; return stability; } /** * Calculates the complexity for a class. * * @param methods * @param totalComplexity * @return */ protected double getComplexity(double methods, double totalComplexity) { double complexity = methods > 0 ? totalComplexity / methods : 1; return Math.max(1, complexity); } /** * Calculates the coverage for a method, class or package. * * @param lines * @param executed * @return */ protected double getCoverage(double lines, double executed) { double coverage = lines > 0 ? (executed / lines) * 100d : 0; return coverage; } }