Basic usage
JacoDB API has two levels:
- the one provides you with information represented in filesystem, i.e., with
bytecode
andclasses
; - the other one provides you with information appearing at runtime, i.e., with
types
.
bytecode
and classes
represent data from .class
files: class with methods, fields, etc.
types
represent types that can be nullable, parameterized, etc.
Classes
diagram
Types
diagram
Both levels are connected to JcClasspath
. You can't modify classes retrieved from pure bytecode.
types
may be constructed manually by generic substitution.
JcClasspath
is an entry point for both classes and types.
JcClassOrInterface
represents a class file. An array or a primitive gets no JcClassOrInterface
.
JcType
represents a type from JVM runtime.
JcClassType#methods
contains:
- all the public/protected/private methods of an enclosing class
- all the ancestor methods visible at compile time
- only constructor methods from the declaring class
JcClassType#fields
contains:
- all the public/protected/private fields of an enclosing class
- all the ancestor fields visible at compile time
Get declared methods
class Example { public static MethodNode findNormalDistribution() throws Exception { File commonsMath32 = new File("commons-math3-3.2.jar"); File commonsMath36 = new File("commons-math3-3.6.1.jar"); File buildDir = new File("my-project/build/classes/java/main"); JcDatabase database = JacoDB.async( new JcSettings() .useProcessJavaRuntime() .persistent("/tmp/compilation-db/" + System.currentTimeMillis()) // persist data ).get(); // Let's load these three bytecode locations database.asyncLoad(Arrays.asList(commonsMath32, commonsMath36, buildDir)); // This method just refreshes the libraries inside the database. If there are any changes in libs then // the database updates data with the new results. database.asyncLoad(Collections.singletonList(buildDir)); // Let's assume that we want to get bytecode info only for "commons-math3" version 3.2. JcClassOrInterface jcClass = database.asyncClasspath(Arrays.asList(commonsMath32, buildDir)) .get().findClassOrNull("org.apache.commons.math3.distribution.NormalDistribution"); System.out.println(jcClass.getDeclaredMethods().size()); System.out.println(jcClass.getAnnotations().size()); System.out.println(JcClasses.getConstructors(jcClass).size()); // At this point the database read the method bytecode and return the result. return jcClass.getDeclaredMethods().get(0).body(); } }
Note: the body
method returns null
if the to-be-processed JAR-file is changed or removed. If the class
environment is incomplete (i.e., a superclass, an interface, a return type or a parameter of a method is not found
in classpath), then the API throws NoClassInClasspathException
at runtime.
Watch for file system changes
The database can watch for file system changes in the background and refresh the JAR-files explicitly:
public static void watchFileSystem() throws Exception { JcDatabase database = JacoDB.async(new JcSettings() .watchFileSystem() .useProcessJavaRuntime() .loadByteCode(Arrays.asList(lib1, buildDir)) .persistent("", false)).get(); } // A user rebuilds the buildDir folder. // The database re-reads the rebuilt directory in the background.
Get type information
The represented types
include
- primitives,
- classes,
- arrays,
- bounded and unbounded wildcards.
The types
level represents runtime behavior according to parameter substitution in the given generic type.
public static class A<T> { T x = null; } public static class B extends A<String> { } public static void typesSubstitution() { JcClassType b = (JcClassType)classpath.findTypeOrNull("org.jacodb.examples.JavaReadMeExamples.B"); JcType xType = b.getFields() .stream() .filter(it -> "x".equals(it.getName())) .findFirst().get().getFieldType(); JcClassType stringType = (JcClassType) classpath.findTypeOrNull("java.lang.String"); System.out.println(xType.equals(stringType)); // will print "true" }
Multithreading
The instances of JcClassOrInterface
, JcMethod
, and JcClasspath
are thread-safe and immutable.
JcClasspath
represents an independent snapshot of classes, which cannot be modified since it is created.
Removing or modifying library files does not affect JcClasspath
instance structure. The JcClasspath#close
method releases
all snapshots and cleans up the persisted data if some libraries are outdated.
public static void refresh() throws Exception { JcDatabase database = JacoDB.async( new JcSettings() .watchFileSystem() .useProcessJavaRuntime() .loadByteCode(Arrays.asList(lib1, buildDir)) .persistent("...") ).get(); JcClasspath cp = database.asyncClasspath(Collections.singletonList(buildDir)).get(); database.asyncRefresh().get(); // does not affect cp classes JcClasspath cp1 = database.asyncClasspath(Collections.singletonList(buildDir)).get(); // will use new version of compiled results in buildDir }
If a request for a JcClasspath
instance contains the libraries, which haven't been indexed yet,
the indexing process is triggered and the new instance of the JcClasspath
set is returned.
public static void autoProcessing() throws Exception { JcDatabase database = JacoDB.async( new JcSettings() .loadByteCode(Arrays.asList(lib1)) .persistent("...") ).get(); JcClasspath cp = database.asyncClasspath(Collections.singletonList(buildDir)).get(); // database will automatically process buildDir }
JacoDB
is thread-safe. If one requests JcClasspath
instance while loading JAR-files from another thread,
JcClasspath
can represent only a consistent state of the JAR-files being loaded. It is the completely loaded
JAR-file that appears in JcClasspath
. Please note: it is not guaranteed that all the JAR-files, submitted for loading,
will be actually loaded.
class Example { public static void main(String[] args) { val db = JacoDB.async(new JcSettings()).get(); new Thread(() -> db.asyncLoad(Arrays.asList(lib1, lib2)).get()).start(); new Thread(() -> { // maybe created when lib2 or both are not loaded into database // but buildDir will be loaded anyway var cp = db.asyncClasspath(buildDir).get(); }).start(); } }
Bytecode loading
Bytecode loading consists of two steps:
- retrieving information about the class names from the JAR-files or build directories,
- reading
classes
bytecode from the JAR-files or build directories and processing it (persisting data, setting upJcFeature
implementations, etc.).
JacoDB
or JcClasspath
instances are returned right after the first step is performed. You retrieve the final representation
of classes
during the second step. The .class
files may undergo changes at some moment between
these two steps, and the classes
representation is affected accordingly.