Database Structure

On this page

Still need help?

The Atlassian Community is here for you.

Ask the community

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.api.registry.MethodInfo;
import com.atlassian.clover.api.registry.StatementInfo;
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",                            // class name
                new FixedSourceRegion(10, 1),                // start row:column
                Modifiers.createFrom(Modifier.PUBLIC, null), // modifiers and annotations
                false, false, false);                        // is interface, is enum, is annotation

        // 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
        MethodInfo methodInfo = session.enterMethod(new ContextSet(),
                new FixedSourceRegion(12, 1),  // start row:column
                methodSignature,  // signature
                false,            // is test method
                null,             // test name (if different from the method name)
                false,            // is lambda function
                5,                // cyclomatic complexity
                LanguageConstruct.Builtin.METHOD);
        int methodIndex = methodInfo.getDataIndex(); // use this index in your coverage recorder

        // example: add a statement in the helloWorld method
        StatementInfo statementInfo = session.addStatement(new ContextSet(),
                new FixedSourceRegion(13, 1, 13, 44),  // start row:column, end row:column
                3,                                     // cyclomatic complexity
                LanguageConstruct.Builtin.STATEMENT);
        int statementIndex = statementInfo.getDataIndex(); // use this index in your coverage recorder

        // 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);
        }
    }
}

 

Creating a global coverage recording file

In addition to the Clover database, which holds information about code structure, you need also to generate a global coverage recording file. It keeps information about hit counts for every code element registered in a database (i.e. how many times given method/statement/branch was executed).

import com.atlassian.clover.api.CloverException;
import com.atlassian.clover.recorder.FileBasedGlobalCoverageRecording;
import com.atlassian.clover.registry.Clover2Registry;
import com_atlassian_clover.Clover;

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

public class GlobalCoverageFileGenerator {

    private static void convertCoverage(final Clover2Registry registry) throws IOException {
        // TODO fill this array with hit counts, e.g. from another coverage tool
        int[] elements = { 1, 2, 3, 4, 5 };
        // note: should be >= from registry.getVersion() otherwise will be discarded as outdated
        long coverageTS = registry.getVersion();
        // generate global recording file name (<clover.db><randomhash>_<timestamp>)
        String recname = Clover.getRecordingName(12345, registry.getRegistryFile().getAbsolutePath(), coverageTS);

        // create global coverage file
        flushToDisk(recname, registry.getVersion(), coverageTS, elements);
    }

    /**
     * @param dbname name of the coverage recording file
     * @param dbversion database timestamp
     * @param writeTS coverage file timestamp
     * @param elements an array with hit counts
     */
    private static void flushToDisk(final String dbname, long dbversion, long writeTS, int[] elements) throws IOException {
        new FileBasedGlobalCoverageRecording(dbname, dbversion, writeTS, new int[][] {elements}, elements.length).write();
    }

    public static void main(String[] args) throws IOException, CloverException {
        final Clover2Registry registry = Clover2Registry.fromFile(new File("clover.db"));
        convertCoverage(registry);
    }
}

You can find these code samples on https://bitbucket.org/atlassian/clover-examples.

Last modified on Jun 17, 2016

Was this helpful?

Yes
No
Provide feedback about this article
Powered by Confluence and Scroll Viewport.