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.
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.
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.
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
.
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 |
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
.
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).
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.
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