Docs
How it works
PackageMap builds a directed graph of the types in your code, to show you how they work with each other. We call these directed graphs 'maps'. So PackageMap builds a 'map' of your code's packages.
The app is made of two parts;
- A java parser that runs on your machine
- The https://packagemap.co site, where you can explore the 'maps' of your code.
Map basics
Nodes types and colours
There are two different types of nodes in a packagemap:
- Box nodes for types and classes.
- Oval nodes for methods.
There are 4 different colors of nodes in a packagemap based on their access modifier and visibility:
Access | Color |
---|---|
Public | Green |
Package Private | Gold |
Protected | Blue |
Private | Black |
Click to expand
Edge types and colours
Edges and links between nodes types can be either:
- Black: representing a usage within the same package.
- Red: representing a usage or import that crosses a package boundary.
Getting started
Register a PackageMap account to get API keys
The maps you render will be stored in your account. You account also has an API key that you can use with the java-parser.
You can sign up at https://packagemap.co.
The API keys are available on the Account page.
Github Actions
The java parser is available on the Github Actions Marketplace
Add this to your workflow:
- name: PackageMap java parser Github action
uses: packagemap/packagemap-java-[email protected]
with:
src_dir: 'src/main/java'
base: 'com.mycompany'
api_key: ${{ secrets.PACKAGEMAP_KEY }}
Docker image
docker run -v $(pwd):/project \
packagemap/packagemap-java-parser \
--key <access-key>:<secret-key> \
--base 'com.mycompany' \
/project/src/main/java
This command mounts the current directory into the docker image under /project
and then parses java code from the host directory ./src/main/java
.
Jar file
Download the jar file from the github releases page
java -jar packagemap-java-parser.jar \
--key <access-key>:<secret-key> \
--base com.mycompany \
./src/main/java/
The parser is built using java 18, and you'll need this to run it.
Given the parser is for parsing java code, we recon you probably already have this installed. If not, checkout https://whichjdk.com/ to see which jdk version you should install and how.
Parser config and flags
The parser has a few flags that you'll need to set:
--key <access-key>:<secret-key>
You need to pass the Access Key and Secret Key from your PackageMap account. You pass them as a single field, separated by a colon
:
.--base some.package.here
The base package is a filter that is applied to all the types found in your code.
TIP
Use the
--base
flag to exclude the built-in java types likeString
andInteger
, and external dependencies from your map.Only types matching this base package prefix will be included in the map. If you don't pass a base package, your map will include all the external dependencies and built-in java types (Like
String
andInteger
andObject
.)--git <sha or branch name>
You can automatically filter to just the types that have changed against some git base. If you want to see a map of the changes against your git repo's
main
branch you could pass--git main
or--git origin/main
. We've found this is useful in code review, or in CI when automatically generating a map.[source dirs]
The parsers trailing args are a space separated list of directories to parse for java code. The parser will recurse down the directory tree, and parse all java files it finds. The java parser uses method-bindings in it's AST to make sure it finds all the types used in your code -- not just the types that were imported.
TIP
Set the source dirs to the directory just above your package structure
For a tree structure like this:
└── src └── main └── java └── com └── acme └── package └── Main.java
You should target
src/main/java
and set the--base com.acme
The java parser will print out the URL to the map. But you can also find it on the maps page in your account:
Where to use PackageMap
PackageMap helps you to understand the structure of your code.
In code review
In code review, it's common to review only the content of the files, and not how the types fit together.
TIP
Use PackageMap to understand the structure of your code, fast.
When reviewing code that you didn't write, or might not be familiar with you have to:
- Understand the structure of the code. How the program is laid out.
- Understand the content of the code. How the algorithms work.
Code review is much faster if you first understand the structure of the code.
To use PackageMap to make code review faster:
- Checkout the branch you are reviewing, and run the java parser with the
--git origin/main
flag to create a map of the types that have changed. The map will then show you all the types in files that have changed since the HEAD of the main branch. - You can then use prefix and wildcard filters to dig deeper into the code.
Coupling and cohesion
Coupling describes the extent to which two packages or modules are connected to each other.
Cohesion describes the extent to which the code in a single package is connected to other code in that package.
TIP
Code should have high cohesion and low coupling.
You can use PackageMap to visualise the coupling and cohesion in your code. PackageMap renders red lines that cross package boundaries, and black lines within a package. You should aim to minimise the cross-package imports.
But; you can't minimise the imports if you can't see them. PackageMap shows them clearly to you.
TIP
Use PackageMap to see the coupling and cohesion in your code.
Developing new features
When writing code for new features, you can use PackageMap as you go to understand where you should put a class or type, and how it will interact with the other types it uses.
Write better structured code by understand how your code fits into the overall application.
Filter and explore the map
PackageMap supports two types of filter; prefix and wildcard. Wildcard and prefix filters include the node matching the filter, and any direct links.
Prefix filters
Prefix filters match you type name's prefix. Including a filter of com.acme.package
will only show types with this package prefix in the map page.
Filter: com.acme.package
Wildcard filters
Star syntax
Prefix filters can be useful, but you'll often find yourself typing out the same filter prefix multiple times to target multiple types. Instead, you can use a wildcard filter. Wildcard filters are any filter string with a *
character in it.
Internally the *
is replaced with the regex .*
, and the wildcard filter is regex matched against the type names. Even though we use regex internally, the .
in the filter will be matched literally.
Filter: *.package
Line terminators
Wildcard filters can include a regex line terminator: $
. This can be useful to match just classes, or to remove the prefix matching behaviour from a filter.
For example, this image can be filtered to just the Caller
using a line terminator and wildcard:
Click to expand
Filter: *Caller$
*
matches the package namevarmethodcaller.
- match the
Caller
class name $
matches the end of line, no method calls will be included.
Filter the map to just the types that are referenced by the Caller
class:
Traversal filters
Wildcard and prefix filters include the node matching the filter, and any direct links. Traversal filters include all the indirect links in or out of the node.
The traversal operator is: ->
The operator can be used before or after a prefix or regex filter.
- Before / Incoming: use the
->
operator to point towards another filter to include all the indirect incoming links. This will include every code path that leads to this node. - After / Outgoing: use the
->
operator to point away from another filter to include all the indirect outgoing links. This will include every code that that leaves this node.
Match all nodes that lead to the Caller
type.
Filter: ->*Caller
Match all nodes that are used by the Caller
type.
Filter: *Caller->
The traversal operator can be used for incoming and outgoing at the same time:
Fitler: ->*Caller->
This image shows the node types example with a filter for *.internalMethodCall
. This is a wildcard filter that matches the internalMethodCall
method and it's direct links.
Changing the filter to include the incoming traversal operator ->*.internalMethodCall
, we can see the code paths that lead to this internal method.
Filters example
Here's an example map from the java-design-patterns project. The code illustrates how the Visitor Pattern works.
In the code, there are a lot of types, and each of the types uses many others. It's hard to see how the Visitor Pattern actually works.
It's hard to see how the code fits together.
Filter by types
Click to expand
But, using PackageMap filters, we can apply the wildcard filter: *UnitVisitor
to see only the interactions with the class;
com.iluwatar.visitor.UnitVisitor
Using a wildcard filter means we don't have to type the full package prefix.
Click to expand
In this filtered example it's much easier to see how the other classes interact with the UnitVisitor
. We can see that the UnitVisitor
is the class that links the various visitors and the various units (Soldier, Commander, Sergeant).
Filter by methods
We can also filter on the visit
method, to see how they methods interact across classes:
Click to expand