This documentation is for Clover 4.1.x. Earlier versions are available here.

Skip to end of metadata
Go to start of metadata

Model

Class overview

Possible entity nesting

entity below can be nested in entitybranchstatementmethodclassfilepackage
branch
statement N N
method N N
class N N 1
file
package 2

Comments

N - new in Clover 3.2.0
1 - there are helper methods PackageInfo.getClasses() / getAllClasses() which returns classes from a package
2 - there are helper methods PackageInfo.getClassesIncludingSubPackages() / getAllClassesIncludingSubPackages() searching for classes in nested packages


From a logical perspective a branch should be nested inside a statement, e.g. "if (a > 5) .." has one statement with true and false branches in it. However, due to performance reasons, branches are kept aside statements, directly under a method. It's planned to add branches also under a class and a file in one of future Clover releases.

 

Since Clover 3.2 it's possible to nest classes inside classes. This can be used to model an inner class such as:

class A {
  class B { }
}

Clover does not keep inner classes this way, however. All inner classes are kept directly under a file. One of the reasons for such approach is a separation of code metrics, i.e. a complexity of an inner class B does not count to the complexity of a parent class A.

 

Clover does not keep anonymous inline classes as a class entity in the model. Instead of this, methods of an anonymous class are being added to the parent class. This is a legacy issue.

Note that Clover 3.2 keeps lambda functions as classes declared under a method. Due to fact that lambda functions can be converted to a functional interface and vice versa, we plan to fix it and make it consistent in a future Clover release. Therefore, anonymous inline classes will have their own entity in a database model and will be kept under an enclosing method.

Java API

Interfaces describing the database structure are located in the com.atlassian.clover.api.registry package (JavaDoc).

They can be grouped into few categories:

  • basic entities stored in a database are represented by ProjectInfo, PackageInfo, FileInfo, ClassInfo, MethodInfo, StatementInfo and BranchInfo
  • these entities implement HasPackages, HasFiles, HasClasses, HasMethods, HasStatements or HasBranches interfaces which allow to navigate to their children
  • HasParent, EntityContainer and EntityVisitor allows to get to the parent entity (note that some entities might have different parent types)
  • HasMetrics, HasAggregatedMetrics returns information about code metrics
  • helper interfaces describing data structures such as MethodSignatureInfo, Annotation, AnnotationValue etc

 

Reading from a Clover database

An example how to read a content of a database.

If you'd like to read a database without coverage, then replace "CloverDatabase.loadWithCoverage(..)"  by "new CloverDatabase(initstring)"

import com.atlassian.clover.CloverDatabase;
import com.atlassian.clover.CoverageDataSpec;
import com.atlassian.clover.api.registry.ClassInfo;
import com.atlassian.clover.api.registry.FileInfo;
import com.atlassian.clover.api.registry.MethodInfo;
import com.atlassian.clover.api.registry.PackageInfo;
import com.atlassian.clover.api.registry.ProjectInfo;

import java.io.PrintStream;

public class SimpleRegistryDumper {
    public static void main(String[] args) throws Exception {
        if (args.length != 1) {
            System.err.println("Usage:");
            System.err.println("java " + SimpleRegistryDumper.class.getName() + " database");
        } else {
            // read clover database together with coverage recording files, use time span=0 (latest build)
            CloverDatabase db = CloverDatabase.loadWithCoverage(args[0], new CoverageDataSpec());
            ProjectInfo projectInfo = db.getRegistry().getProject();
            // print some project details
            printProject(projectInfo, System.out);
        }
    }
    private static void printProject(ProjectInfo db, PrintStream out) {
       for (PackageInfo packageInfo : db.getAllPackages()) {
           out.println("package: " + packageInfo.getName());
           for (FileInfo fileInfo : packageInfo.getFiles()) {
               out.println("\tfile: " + fileInfo.getName());
               for (ClassInfo classInfo : fileInfo.getClasses()) {
                   out.println("\t\tclass: " + classInfo.getName());
                   for (MethodInfo methodInfo : classInfo.getMethods()) {
                       out.println("\t\t\tmethod: " + methodInfo.getName());
                   }
               }
           }
       }
    }
}

Writing to a Clover database

 

import com.atlassian.clover.api.CloverException;
import com.atlassian.clover.api.instrumentation.InstrumentationSession;
import com.atlassian.clover.api.registry.FileInfo;
import com.atlassian.clover.context.ContextSet;
import com.atlassian.clover.registry.Clover2Registry;
import com.atlassian.clover.registry.FixedSourceRegion;
import com.atlassian.clover.registry.entities.MethodSignature;
import com.atlassian.clover.registry.entities.Modifier;
import com.atlassian.clover.registry.entities.Modifiers;
import com.atlassian.clover.registry.entities.Parameter;
import com.atlassian.clover.spi.lang.LanguageConstruct;

import java.io.File;
import java.io.IOException;

public class SimpleCodeInstrumenter {
    private Clover2Registry registry;
    private InstrumentationSession session;

    public SimpleCodeInstrumenter(String initString, String projectName) throws CloverException {
        try {
            final File dbFile = new File(initString);
            registry = Clover2Registry.createOrLoad(dbFile, projectName);
            if (registry == null) {
                throw new CloverException("Unable to create or load clover registry located at: " + dbFile);
            }
        } catch (IOException e) {
            throw new CloverException(e);
        }
    }

    public void startInstrumentation(String encoding) throws CloverException {
        session = registry.startInstr(encoding);
    }

    public Clover2Registry endInstrumentation(boolean append) throws CloverException {
        try {
            session.close();
            if (append) {
                registry.saveAndAppendToFile();
            } else {
                registry.saveAndOverwriteFile();
            }
            return registry;
        } catch (IOException e) {
            throw new CloverException(e);
        }
    }

    /**
     * This method should perform the actual instrumentation. On every code construct you find in your
     * source file(s) being instrumented (such as file, class, method, statement, branch) you shall call
     * proper handler from InstrumentationSession class in order to record data for a given code entity
     * in the Clover database.
     */
    public void instrument() {
        // note: there is no need to call session.enterPackage(packageName), it will be called from
        // session.enterFile(); the same applies to session.exitPackage()

        // example: register a file with attributes such as enclosing package, number of lines, time stamp, checksum
        String packageName = "com.acme.my.package";
        File sourceFile =  new File("com/acme/my/package/Foo.java");
        FileInfo fileInfo = session.enterFile(packageName, sourceFile,
                200, 100, sourceFile.lastModified(), sourceFile.length(), 3423452);

        // example: register a class (in current file)
        session.enterClass("Foo", new FixedSourceRegion(10, 1), false, false, false);

        // example: add a method to the Foo class
        MethodSignature methodSignature = new MethodSignature("helloWorld", null,  // method name and generic type
                "void",                                                            // return type
                new Parameter[] { new Parameter("String", "name") },               // formal parameters
                null,                                                              // throws
                Modifiers.createFrom(Modifier.PROTECTED | Modifier.STATIC, null)); // modifiers
        int methodIndex           // use this index in your coverage recorder
                = session.enterMethod(new ContextSet(), new FixedSourceRegion(12, 1),          // start row:column
                        methodSignature, false, false, 5, LanguageConstruct.Builtin.METHOD); // other attributes

        // example: add a statement in the helloWorld method
        int stmtIndex             // use this index in your coverage recorder
                = session.addStatement(new ContextSet(), new FixedSourceRegion(13, 1, 13, 44),
                        3, LanguageConstruct.Builtin.STATEMENT);

        // end method, class and a file
        session.exitMethod(14, 1);  // end row:column
        session.exitClass(30, 2);   // end row:column
        session.exitFile();
    }

    public static void main(String[] args) throws CloverException {
        if (args.length != 1) {
            System.err.println("Usage:");
            System.err.println("java " + SimpleCodeInstrumenter.class.getName() + " database");
        } else {
            SimpleCodeInstrumenter instrumenter = new SimpleCodeInstrumenter(args[0], "MyProject");
            instrumenter.startInstrumentation("UTF-8");
            instrumenter.instrument();
            instrumenter.endInstrumentation(true);
        }
    }
}

 

 

  • No labels