package com.ikokoon.serenity; import java.io.File; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.log4j.Logger; import com.ikokoon.serenity.hudson.source.CoverageSourceCode; import com.ikokoon.serenity.hudson.source.ISourceCode; import com.ikokoon.serenity.model.Afferent; import com.ikokoon.serenity.model.Class; import com.ikokoon.serenity.model.Composite; import com.ikokoon.serenity.model.Efferent; import com.ikokoon.serenity.model.Line; import com.ikokoon.serenity.model.Method; import com.ikokoon.serenity.model.Package; import com.ikokoon.serenity.model.Project; import com.ikokoon.serenity.persistence.IDataBase; import com.ikokoon.toolkit.Toolkit; /** * TODO - make this class non static? Is this a better option? More OO? Better performance? Will it be easier to understand? In the case of * distributing the collector class by putting it in the constant pool of the classes and then calling the instance variable from inside the classes, * will this be more difficult to understand? * * In this static class all the real collection logic is in one place and is called statically. The generation of the instructions to call this class * is simple and seemingly not much less performant than an instance variable. * * This class collects the data from the processing. It adds the metrics to the packages, classes, methods and lines and persists the data in the * database. This is the central collection class for the coverage and dependency functionality. * * @author Michael Couck * @since 12.07.09 * @version 01.00 */ public class Collector implements IConstants { /** The logger. */ private static final Logger LOGGER = Logger.getLogger(Collector.class); /** The database/persistence object. */ private static IDataBase dataBase; /** These are the profiler methods. */ /** * Initialises the profiler snapshot taker. * * @param dataBase * the database to use for taking snapshots. */ public static void initialize(final IDataBase dataBase) { Collector.dataBase = dataBase; } /** * This class is called by the byte code injection to increment the allocations of classes on the heap, i.e. when their constructors are called. * * @param className * the name of the class being instantiated * @param methodName * the name of the method, typically this will be 'init' * @param methodDescription * the byte code description of the method */ public static final void collectAllocation(String className, String methodName, String methodDescription) { Class<Package<?, ?>, Method<?, ?>> klass = getClass(className); double allocations = klass.getAllocations(); allocations++; klass.setAllocations(allocations); } /** * This method is called by the byte code injection at the start of a method, i.e. when a thread enters a method. * * @param className * the name of the class where the thread is entering the method * @param methodName * the name of the method in the class that is being executed * @param methodDescription * the byte code description of the method */ public static final void collectStart(String className, String methodName, String methodDescription) { Method<?, ?> method = getMethod(className, methodName, methodDescription); method.setInvocations(method.getInvocations() + 1); method.setStartTime(System.nanoTime()); } /** * This method is called by the byte code injection at the end of a method, i.e. when a thread returns from a method. This can happen in a return, * or when an exception is thrown. * * @param className * the name of the class where the thread is entering the method * @param methodName * the name of the method in the class that is being executed * @param methodDescription * the byte code description of the method */ public static final void collectEnd(String className, String methodName, String methodDescription) { Method<?, ?> method = getMethod(className, methodName, methodDescription); method.setEndTime(System.nanoTime()); long executionTime = method.getEndTime() - method.getStartTime(); long totalTime = method.getTotalTime() + executionTime; method.setTotalTime(totalTime); } /** * This method is called by the byte code injection when there is wait, join, sleep or yield called in a method. * * @param className * the name of the class where the thread is entering the method * @param methodName * the name of the method in the class that is being executed * @param methodDescription * the byte code description of the method */ public static final void collectStartWait(String className, String methodName, String methodDescription) { Method<?, ?> method = getMethod(className, methodName, methodDescription); method.setStartWait(System.nanoTime()); } /** * This method is called by the byte code injection when there is wait, join, sleep or yield that exits in a method. * * @param className * the name of the class where the thread is entering the method * @param methodName * the name of the method in the class that is being executed * @param methodDescription * the byte code description of the method */ public static final void collectEndWait(String className, String methodName, String methodDescription) { Method<?, ?> method = getMethod(className, methodName, methodDescription); method.setEndWait(System.nanoTime()); long waitTime = (method.getEndWait() - method.getStartWait()) + method.getWaitTime(); method.setWaitTime(waitTime); } /** * This method accumulates the number of times a thread goes through each line in a method. * * @param className * the name of the class that is calling this method * @param methodName * the name of the method that the line is in * @param methodDescription * the description of the method * @param lineNumber * the line number of the line that is calling this method */ public static final void collectCoverage(String className, String methodName, String methodDescription, int lineNumber) { Line<?, ?> line = getLine(className, methodName, methodDescription, lineNumber); line.increment(); } /** * This method just collect the line specified in the parameter list. * * @param className * the name of the class that is calling this method * @param lineNumber * the line number of the line that is calling this method * @param methodName * the name of the method that the line is in * @param methodDescription * the description of the method */ public static final void collectLine(String className, String methodName, String methodDescription, Integer lineNumber) { getLine(className, methodName, methodDescription, lineNumber); } /** * This method collects the Java source for the class. * * @param className * the name of the class * @param source * the source for the class */ public static final void collectSource(String className, String source) { Class<Package<?, ?>, Method<?, ?>> klass = getClass(className); File file = new File(IConstants.SERENITY_SOURCE, className + ".html"); if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } if (!file.exists()) { if (!Toolkit.createFile(file)) { LOGGER.info("Couldn't create new source file : " + file); } } if (file.exists()) { ISourceCode sourceCode = new CoverageSourceCode(klass, source); String htmlSource = sourceCode.getSource(); Toolkit.setContents(file, htmlSource.getBytes()); } } /** * This method is called after each jumps in the method graph. Every time there is a jump the complexity goes up one point. Jumps include if else * statements, or just if, throws statements, switch and so on. * * @param className * the name of the class the method is in * @param methodName * the name of the method * @param methodDescription * the methodDescriptionription of the method * @param complexity * the complexity of the method */ public static final void collectComplexity(String className, String methodName, String methodDescription, double complexity) { Method<?, ?> method = getMethod(className, methodName, methodDescription); method.setComplexity(complexity); } /** * Collects the packages that the class references and adds them to the document. * * @param className * the name of the classes * @param targetClassNames * the referenced class names */ public static final void collectEfferentAndAfferent(String className, String... targetClassNames) { String packageName = Toolkit.classNameToPackageName(className); for (String targetClassName : targetClassNames) { // Is the target name outside the package for this class String targetPackageName = Toolkit.classNameToPackageName(targetClassName); if (targetPackageName.trim().equals("")) { continue; } // Is the target and the source the same package name if (targetPackageName.equals(packageName)) { continue; } // Exclude java.lang classes and packages if (Configuration.getConfiguration().excluded(packageName) || Configuration.getConfiguration().excluded(targetPackageName)) { continue; } // Add the target package name to the afferent packages for this package Class<Package<?, ?>, Method<?, ?>> klass = getClass(className); Afferent afferent = getAfferent(klass, targetPackageName); if (!klass.getAfferent().contains(afferent)) { klass.getAfferent().add(afferent); } // Add this package to the efferent packages of the target Class<Package<?, ?>, Method<?, ?>> targetClass = getClass(targetClassName); Efferent efferent = getEfferent(targetClass, packageName); if (!targetClass.getEfferent().contains(efferent)) { targetClass.getEfferent().add(efferent); } } } /** * Adds the access attribute to the method object. * * @param className * the name of the class * @param methodName * the name of the method * @param methodDescription * the description of the method * @param access * the access opcode associated with the method */ public static final void collectAccess(String className, String methodName, String methodDescription, Integer access) { Method<?, ?> method = getMethod(className, methodName, methodDescription); method.setAccess(access); } /** * Adds the access attribute to the class object. * * @param className * the name of the class * @param access * the access opcode associated with the class */ public static final void collectAccess(String className, Integer access) { Class<Package<?, ?>, Method<?, ?>> klass = getClass(className); if (access.intValue() == 1537) { klass.setInterfaze(true); } klass.setAccess(access); } /** * Collects the inner class for a class. * * @param innerName * the name of the inner class * @param outerName * the name of the outer class */ public static final void collectInnerClass(String innerName, String outerName) { Class<?, ?> innerClass = getClass(innerName); Class<?, ?> outerClass = getClass(outerName); if (innerClass.getOuterClass() == null) { innerClass.setOuterClass(outerClass); } if (!outerClass.getInnerClasses().contains(innerClass)) { outerClass.getInnerClasses().add(innerClass); } } /** * Collects the outer class of an inner class. * * @param innerName * the name of the inner class * @param outerName * the name of the outer class * @param outerMethodName * the method name in the case this is an in-method class definition * @param outerMethodDescription * the description of the method for anonymous and inline inner classes */ public static final void collectOuterClass(String innerName, String outerName, String outerMethodName, String outerMethodDescription) { Class<?, ?> innerClass = getClass(innerName); Class<?, ?> outerClass = getClass(outerName); if (innerClass.getOuterClass() == null) { innerClass.setOuterClass(outerClass); } if (!outerClass.getInnerClasses().contains(innerClass)) { outerClass.getInnerClasses().add(innerClass); } if (innerClass.getOuterMethod() == null) { if (outerMethodName != null) { Method<?, ?> outerMethod = getMethod(outerName, outerMethodName, outerMethodDescription); innerClass.setOuterMethod(outerMethod); } } } @SuppressWarnings("unchecked") private static final Package<Project<?, ?>, Class<?, ?>> getPackage(String className) { String packageName = Toolkit.classNameToPackageName(className); long id = Toolkit.hash(packageName); Package<Project<?, ?>, Class<?, ?>> pakkage = (Package<Project<?, ?>, Class<?, ?>>) dataBase.find(Package.class, id); if (pakkage == null) { pakkage = new Package<Project<?, ?>, Class<?, ?>>(); pakkage.setName(packageName); pakkage.setComplexity(1d); pakkage.setCoverage(0d); pakkage.setAbstractness(0d); pakkage.setStability(0d); pakkage.setDistance(0d); pakkage.setInterfaces(0d); pakkage.setImplementations(0d); pakkage = (Package<Project<?, ?>, Class<?, ?>>) dataBase.persist(pakkage); LOGGER.debug("Added package : " + pakkage); } return pakkage; } @SuppressWarnings("unchecked") private static final Class<Package<?, ?>, Method<?, ?>> getClass(String className) { long id = Toolkit.hash(className); Class klass = (Class) dataBase.find(Class.class, id); if (klass == null) { klass = new Class(); klass.setName(className); klass.setComplexity(1d); klass.setCoverage(0d); klass.setStability(0d); klass.setEfference(0d); klass.setAfference(0d); klass.setInterfaze(false); Package<Project<?, ?>, Class<?, ?>> pakkage = getPackage(className); pakkage.getChildren().add(klass); klass.setParent(pakkage); klass = (Class<?, ?>) dataBase.persist(klass); LOGGER.debug("Added class : " + klass); } return klass; } @SuppressWarnings("unchecked") private static final Method<?, ?> getMethod(String className, String methodName, String methodDescription) { methodName = methodName.replace('<', ' ').replace('>', ' ').trim(); long id = Toolkit.hash(className, methodName, methodDescription); Method method = (Method) dataBase.find(Method.class, id); if (method == null) { method = new Method(); method.setName(methodName); method.setClassName(className); method.setDescription(methodDescription); method.setComplexity(0d); method.setCoverage(0d); Class klass = getClass(className); method.setParent(klass); if (klass.getChildren() == null) { List<Method> children = new ArrayList<Method>(); klass.setChildren(children); } klass.getChildren().add(method); dataBase.persist(method); } return method; } @SuppressWarnings("unchecked") protected static final Line<?, ?> getLine(String className, String methodName, String methodDescription, double lineNumber) { long id = Toolkit.hash(className, methodName, lineNumber); Line line = (Line) dataBase.find(Line.class, id); if (line == null) { line = new Line(); line.setNumber(lineNumber); line.setCounter(0d); line.setClassName(className); line.setMethodName(methodName); Method method = getMethod(className, methodName, methodDescription); Collection<Composite> lines = method.getChildren(); line.setParent(method); lines.add(line); dataBase.persist(line); } return line; } private static final Efferent getEfferent(Class<?, ?> klass, String packageName) { StringBuilder builder = new StringBuilder("<e:"); builder.append(packageName); builder.append(">"); String name = builder.toString(); long id = Toolkit.hash(name); Efferent efferent = (Efferent) dataBase.find(Efferent.class, id); if (efferent == null) { efferent = new Efferent(); efferent.setName(name); klass.getEfferent().add(efferent); dataBase.persist(efferent); } return efferent; } private static final Afferent getAfferent(Class<?, ?> klass, String packageName) { StringBuilder builder = new StringBuilder("<a:"); builder.append(packageName); builder.append(">"); String name = builder.toString(); long id = Toolkit.hash(name); Afferent afferent = (Afferent) dataBase.find(Afferent.class, id); if (afferent == null) { afferent = new Afferent(); afferent.setName(name); klass.getAfferent().add(afferent); dataBase.persist(afferent); } return afferent; } }