entity below can be nested in entity | branch | statement | method | class | file | package |
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.
Interfaces describing the database structure are located in the com.atlassian.clover.api.registry package (JavaDoc).
They can be grouped into few categories:
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()); } } } } } }
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 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 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); } } }