Japan - Java Package Analyser 0.2.4

Japan is an Ant task and IntelliJ plugin for checking you haven't broken any of the package dependency rules within your Java project. For example, you might want to allow the 'client' and 'server' packages to see 'remote', but not allow 'remote' to see 'client' or 'server'. Japan works on Java source files and the dependency rules are defined in an XML configuration file. Japan is hosted on SourceForge here.

Why?

On most of the large Java projects I've worked on, compilation was used to enforce dependency rules, e.g. 'remote' was compiled first, then added to the classpath when 'client' and 'server' were compiled. For that to work, the sources for each package had to be isolated when they were compiled, so the compiler couldn't pick up the other packages' sources. That meant either having separate source paths for 'client', 'server' and 'remote', or copying packages out to a clean location for each compile. For large projects, using multiple source paths becomes unmanageable and copying source files around takes time, not to mention the complexity of the build system.

Japan aims to solve this by letting you check the dependencies independently from compilation. This means you can have one source path, compile everything in one go and check dependencies at any time, without the overhead of the compiler, e.g. from within your IDE whilst you're moving classes around between packages.

Installation

Japan is distributed as a single jar file named japan-<version>.jar, although it does have a dependency on jdom.jar. To use Japan in Ant, you don't need to put the jar file anywhere special - just refer to it from the <taskdef> for Japan in your build script (more on this below). To use Japan in IntelliJ, copy the jar file to the IntelliJ plugins directory and restart IntelliJ.

Configuration file

Using the Japan project itself as an example, here is a graphical representation of the allowed package dependencies within the project:

And here is the config file used to define the above dependencies for Japan. This file is normally named japan-config.xml:

<japan-config base-package="net.sourceforge.japan" ignore-indicator="// JAPAN:IGNORE">

    <dependency-set name="module" package-depth="4" transitive="true">
        <dependency from="util" to=""/>
        <dependency from="fileutils" to=""/>
        <dependency from="japanconfig" to="util"/>
        <dependency from="analyser" to="fileutils,util"/>
        <dependency from="checker" to="japanconfig,analyser"/>
        <dependency from="gui" to="checker" additional-dependencies="javax.swing"/>
        <dependency from="intellij" to="gui"/>
        <dependency from="standalonegui" to="gui"/>
        <dependency from="cmdline" to="checker"/>
        <dependency from="ant" to="checker"/>
    </dependency-set>

</japan-config>

The first thing you define is the base package. It is assumed that most projects have two or three packages that don't contain any classes, but prefix the whole code base, such as com.mycompany.myproject. This is important for Japan because it is the string that is searched for in the code base.

Next, the 'ignore indicator' is set. This is a string that can be used in a source file to get around a broken dependency. The broken dependency might be caused by an innocent comment in the code, or it might be a broken dependency that you're prepared to live with for a while. To use the 'ignore indicator', put it on the same line as the offending code, e.g. at the end of the line.

Now for the dependency set. This defines the dependencies you want to allow between packages at a particular depth in the package tree. Depth is the number of packages down from the default package, e.g. 'net' is at package depth 1, 'sourceforge' is at depth 2, and so on. In the above example, the dependencies are between packages at depth 4 - 'net.sourceforge.japan.XXX'. You can only define dependencies between packages at the same depth.

You can have more than one dependency set, so you can check dependencies at other depths too. For example, you might organise your code in two dimensions - different business domains at depth 4 and different tiers at depth 5 like this: com.mycompany.myproject.domain1.client, com.mycompany.myproject.domain1.server, com.mycompany.myproject.domain2.client, etc.

The dependency set has a transitive attribute, which controls whether the dependency rules within that set are transitive, e.g. in Japan, classes in the standalonegui package can use classes in the core package because standalonegui depends on gui, which in turn depends on core. If you didn't use transitive rules, you would have finer control over exactly which package depends on which and you could introduce layering of packages.

Finally, the dependencies. Each dependency is from one package to zero or more other packages at that depth. If there is more than one target package, they can be specified in a comma separated list, e.g.

        <dependency from="client" to="remote,util"/>

or as individual lines, e.g.

        <dependency from="client" to="remote"/>
        <dependency from="client" to="util"/>

You can also specify additional dependencies to packages outside of the project, such as to javax.swing. Note that the transitive setting applies to these too, so in the example above, the intellij and standalonegui packages can also see javax.swing.

How to use in Ant

As an example of using Japan from an Ant build script, here is how I run Japan on Japan itself:

    <taskdef classname="net.sourceforge.japan.ant.JapanTask" name="japan" classpath="build/dist/japan-0.1.jar:lib/compile/jdom.jar"/>

    <target name="japan" >
        <japan sourcePath="src/java;src/test" japanConfigFile="japan-config.xml" />
    </target>

The taskdef is the standard way of plugging new tasks into Ant. You will need to adjust the classpath attribute to point to wherever your japan and jdom jars are.

The japan task requires a source path and the location of the configuration file that contains your dependency rules. The source path can be specified using the sourcePath attribute with a semicolon delimited list of source dirs (as above), or as a srcpathRef attribute with a reference to a path defined elsewhere, e.g.

        <japan srcpathRef="src.path" japanConfigFile="japan-config.xml" />

When you run the Ant with the japan target, you should see output like this:

Buildfile: build.xml

japan:
Java Package Analyser - Checking dependencies defined in japan-config.xml
0 violations

BUILD SUCCESSFUL
Total time: 2 seconds

And if you're unlucky and have broken some dependencies, you should see something like this:

Buildfile: build.xml

japan:
Java Package Analyser - Checking dependencies defined in japan-config.xml
1) net.sourceforge.japan.core.Rule(20,30): // dummy dependency to swing: javax.swing
2) net.sourceforge.japan.core.StringUtil(6,36): // dummy dependency to gui package: net.sourceforge.japan.gui
3) net.sourceforge.japan.intellij.ConfigurationController(14,7): import net.sourceforge.japan.ant.JapanTask;
3 violations

BUILD FAILED
/home/chris/dev/japan/build.xml:460: 3 dependency violations

Total time: 2 seconds

How to use in IntelliJ

After copying the Japan jar file to the IntelliJ plugins directory and restarting, the source path and Japan configuration file for your project should be specified in the Project Settings dialog.

Project Settings Find Dialog Search Results

There are a couple of magic values you can use here to pick up settings from IntelliJ itself. For the source path, $SRC_PATH$ can be used to pick up the source path from the project. For the Japan configuration file, $PROJECT_PATH$ can be used as a prefix, which will map to the project root.

Once the project settings are done, click the Japan window at the bottom of IntelliJ (Alt-J). Hit the green arrow to begin (F5) and you should see the Find dialog. The default values are calculated from the details in the config file, so pressing OK will find all the dependency violations. After searching for all dependencies and working out which ones violate the dependency rules, the results should be displayed in the Japan window. Violations show up in red. You can use the red exclamation button on the Japan toolbar (F6) to toggle display of all dependencies or just the violations. Double clicking on a dependency opens the class in the editor window and navgiates to the appropriate line.

Note that you can use Japan to investigate dependencies between arbitrary packages in your project by changing the default values in the Find dialog. For example, you could enter 'net.sourceforge.japan.gui' for the 'from package' and 'net.sourceforge.japan.core' for the 'to package' to find the dependencies from gui to core.

How to use from the command line

Japan can be invoked from the command line as a text based console app or as a Swing gui. The functionality in the console app is probably a little dated now as I have been concentrating on the Ant and IntelliJ interfaces - look at the source in the cmdline package for more details. The standalone Swing gui is similar to the IntelliJ plugin. Look at the source in the standalonegui package for more details (I might restructure this to bring it more in line with the IntelliJ plugin).

How it works

Japan uses simple string searching to find all occurrences of the base package in each file, e.g. searching for net.sourceforget.japan. For each result, the string is expanded to find a full package name, e.g. net.sourceforce.japan.core. That string is then stored as a dependency from the package that the source file is in to the package represented by the string. Any additional dependencies specified in the config file, such as javax.swing are searched for similarly.

What might break it

Note that Japan isn't foolproof as it doesn't understand the structure of Java files. If you have broken convention and used a capital letter at the start of a package name, Japan will incorrectly think it's a class name and won't include that package as part of its analysis. Conversely, if you start a class name with a lower case letter, it will be assumed to be a package name. Also, the contents of strings are treated no differently than other text, so a debug message with the text "I should have been initialised by com.mycompany.myproject.app.Main" in a class that shouldn't be able to refer to the app package will fail. This has the interesting benefit that code using introspection to get around the dependency rules will be caught!

Last updated: 27-July-2005, Chris Smith