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