InheritanceGraphService

The InheritanceGraphService allows you to create InheritanceGraph instances, which model the parent/child relations between classes and interfaces in the workspace.

Getting an InheritanceGraph instance

The InheritanceGraph type can be created for any arbitrary Workspace. This service will always keep a shared copy of the inheritance graph for the current workspace.

// You can make a new graph from any workspace.
InheritanceGraph graph = callGraphService.newInheritanceGraph(workspace);

// Or get the current (shared) graph for the current workspace if one is open in the workspace manager.
graph = callGraphService.getCurrentWorkspaceGraph(); // Can be 'null' if no workspace is open.

Parents and children

We will use the following classes for the following examples:

interface Edible {}
interface Red {}
class Apple implements Edible, Red {}
class AppleWithWorm extends Apple {}
class Grape implements Edible {}
classDiagram

class Edible {
	<<interface>>
}
class Red {
	<<interface>>
}
class Apple {
	<<class>>
}
class AppleWithWorm {
	<<class>>
}
class Grape {
	<<class>>
}

Edible <|-- Grape
Edible <|-- Apple
Apple *-- AppleWithWorm
Red <|-- Apple

Accessing parent types

You can access direct parents with getParents() which returns a Set<InheritanceVertex>, or parents() which returns a Stream<InheritanceVertex>. Direct parents include the class's super-type and any interfaces implemented directly by the class. For example AppleWithWorm will implement Edible and Red but these are not direct parents since those are not declared on the class definition.

You can access all parents with getAllParents() which returns a Set<InheritanceVertex>, or allParents() which returns a Stream<InheritanceVertex>.

InheritanceVertex apple = graph.getVertex("Apple");
InheritanceVertex wormApple = graph.getVertex("AppleWithWorm");
InheritanceVertex red = graph.getVertex("Red");

// Get children as a set of graph vertices
//   The set 'appleParents' will have 2 elements: Edible, Red
//   The set 'wormAppleParents' will have 1 element: Apple
//   The set 'wormAppleAllParents' will have 3 element: Apple, Edible, Red
//   The set 'redParents' will be empty
Set<InheritanceVertex> appleParents = apple.getParents();
Set<InheritanceVertex> wormAppleParents = wormApple.getParents();
Set<InheritanceVertex> wormAppleAllParents = wormApple.getAllParents();
Set<InheritanceVertex> redParents = red.getParents();

// Alternative: Stream<InheritanceVertex>
wormApple.parents();
wormApple.allParents();

Accessing child types

You can access direct children with getChildren() which returns a Set<InheritanceVertex>, or children() which returns a Stream<InheritanceVertex>. Direct children are just the reverse order of direct parents as described above.

You can access all children with getAllChildren() which returns a Set<InheritanceVertex>, or allChildren() which returns a Stream<InheritanceVertex>.

InheritanceVertex apple = graph.getVertex("Apple");
InheritanceVertex wormApple = graph.getVertex("AppleWithWorm");
InheritanceVertex red = graph.getVertex("Red");

// Get children as a set of graph vertices
//   The set 'appleChildren' will have 1 element: AppleWithWorm
//   The set 'wormChildren' will be empty
//   The set 'redChildren' will have 1 element: Apple
//   The set 'redAllChildren' will have 2 elements: Apple, AppleWithWorm
Set<InheritanceVertex> appleChildren = apple.getChildren();
Set<InheritanceVertex> wormChildren = wormApple.getChildren();
Set<InheritanceVertex> redChildren = red.getChildren();
Set<InheritanceVertex> redAllChildren = red.getAllChildren();

// Alternative: Stream<InheritanceVertex>
apple.children();
apple.allChildren();

Accessing complete type hierarchy (parents and children)

You can access direct children & parents with getAllDirectVertices() which combines the results of getChildren() and getParents().

You can access all related vertices with getFamily(boolean includeObject) which will is an recursive calling of getAllDirectVerticies(). If you pass true it will include all types that are not edge-cases described below in the edge-case section. You will probably only ever pass false to getFamily(...).

// Direct will contain: Edible, Red, AppleWithWorm
Set<InheritanceVertex> appleDirects = apple.getAllDirectVertices();

// Family will contain: Edible, Red, AppleWithWorm, Apple (itself), Grape
//  - Grape will be included because of the shared parent Edible
Set<InheritanceVertex> appleFamily = apple.getFamily(false);

Edge case: Classes without super-types

All classes must define a super-type. Each time you define a new class it will implictly extend java/lang/Object unless it is an enum which then it will extend java/lang/Enum which extends java/lang/Object. There are only a few exceptions to these rules.

Module classes, denoted by their name module-info do not define super-types. Their super-type index in the class file points to index 0 which is an edge case treated as null in this situation.

The Object class also has no super-type, for obvious enough reasons.

The inheritance graph accommodates for these edge cases. It may be useful information for you to know regardless.

Edge case: Cyclic inheritance from obfuscators

Some obfuscators may create classes that are unused in the application logic, but exist solely to screw with analysis tools. Consider the following example:

class A extends B {}
class B extends A {}
classDiagram
A *-- B
B *-- A

This code will not compile, but there is nothing stopping an obfuscator from creating these classes. If an analysis tool naively tries to find all parents of A it will look at B then A again, then B and you have yourself an infinite loop.

The inheritance graph tracks what types in a hierarchy have already been visited and short-circuits hierarchy searches in paths where it finds cycles.

Getting the common type of two classes

You can get the common type of any two classes by passing their names to InheritanceGraph's getCommon(String a, String b) method.

// common will be 'Edible'
String common = graph.getCommon("Apple", "Grape");
classDiagram
class Edible {
	<<interface>>
}
class Apple {
	<<class>>
}
class Grape {
	<<class>>
}

note for Edible "Common parent of Apple and Grape"

Edible <|-- Apple
Edible <|-- Grape

Checking if a type is assignable from another

Java's java.lang.Class has an isAssignableFrom(Class) method which the inheritance graph mirrors.

// Will return true since both Apple/Grape implement Edible
graph.isAssignableFrom("Edible", "Apple");
graph.isAssignableFrom("Edible", "Grape");

// The inverse Will return false
graph.isAssignableFrom("Apple", "Edible");
graph.isAssignableFrom("Grape", "Edible");