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;

  1. A java parser that runs on your machine
  2. The https://packagemap.coopen in new window site, where you can explore the 'maps' of your code.

How it works

Map basics

Nodes types and colours

There are two different types of nodes in a packagemap:

  1. Box nodes for types and classes.
  2. Oval nodes for methods.

There are 4 different colors of nodes in a packagemap based on their access modifier and visibility:

AccessColor
PublicGreen
Package PrivateGold
ProtectedBlue
PrivateBlack

Node typesClick 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.coopen in new window.

The API keys are available on the Account pageopen in new window.

Github Actions

The java parser is available on the Github Actions Marketplaceopen in new window

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 pageopen in new window

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/open in new window 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 like String and Integer, 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 and Integer and Object.)

  • --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:

  1. Understand the structure of the code. How the program is laid out.
  2. 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:

  1. 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.
  2. 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.

Coupling and cohesion

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:

Node typesClick to expand

Filter: *Caller$
  • * matches the package name varmethodcaller.
  • 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: Node types filtered

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. Wildcard filter

Changing the filter to include the incoming traversal operator ->*.internalMethodCall, we can see the code paths that lead to this internal method. Traversal filter

Filters example

Here's an example map from the java-design-patterns projectopen in new window. 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 expandFiltered map

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 expandFiltered map

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 expandMethod filtered map