Batch Source Formatting in Eclipse Indigo

Posted by & filed under , , .

This has been bugging me lately — how to do a batch formatting using Eclipse!

If you plug things like Checkstyle (and PMD to a certain extent) in your project build, you get bugged every now and then when someone else makes a change with stuff about tabs/spaces, brackets being on the same line or not, line being too long etc etc etc. Typically what you do in this instances, you just open the file in Eclipse, and a simple Mac+Shift+F (or Alt+Shift+F on Windows) automatically formats the source and you’re good to go!

However, what do you do, when you see a Checkstyle report reporting 100’s of issues across say 10-20 different files? You could of course set off to process every file manually — if you have the time! — or look for a way to do this using Eclipse in a batch manner. I personally opted for the latter — and this is how to do this using Eclipse.

Eclipse packages a few applications into one — when you start the main Eclipse executable you run the GUI editor application, however, there are lots of other applications one can start, apart from the classic editor. Here’s a proof — type this in your command line:

eclipse -application abc

You will get an error message like this one:

Don’t dispair — it is an error message, but that’s only cause we specified some random parameters in the command line (you would have noticed hopefully the random abc in the command line above?). Now open the log file shown in the error dialog and this is where the fun begins — in my case, the log looks like this:

SESSION 2012-03-01 20:34:33.249 -----------------------------------------------
eclipse.buildId=I20110613-1736
java.version=1.6.0_29
java.vendor=Apple Inc.
BootLoader constants: OS=macosx, ARCH=x86_64, WS=cocoa, NL=en_US
Framework arguments:  -product org.eclipse.epp.package.jee.product -application abc
Command-line arguments:  -os macosx -ws cocoa -arch x86_64 -product org.eclipse.epp.package.jee.product -application abc
 
!ENTRY org.eclipse.osgi 4 0 2012-03-01 20:34:36.580
!MESSAGE Application error
!STACK 1
java.lang.RuntimeException: Application "abc" could not be found in the registry. The applications available are: org.eclipse.ant.core.antRunner, org.eclipse.ant.ui.antRunner, org.eclipse.datatools.connectivity.console.profile.StorageFileEditor, org.eclipse.emf.importer.ecore.Ecore2GenModel, org.eclipse.emf.importer.java.Java2GenModel, org.eclipse.emf.importer.rose.Rose2GenModel, org.eclipse.equinox.app.error, org.eclipse.equinox.p2.director, org.eclipse.equinox.p2.garbagecollector.application, org.eclipse.equinox.p2.publisher.InstallPublisher, org.eclipse.equinox.p2.publisher.EclipseGenerator, org.eclipse.equinox.p2.publisher.ProductPublisher, org.eclipse.equinox.p2.publisher.FeaturesAndBundlesPublisher, org.eclipse.equinox.p2.reconciler.application, org.eclipse.equinox.p2.repository.repo2runnable, org.eclipse.equinox.p2.repository.metadataverifier, org.eclipse.equinox.p2.artifact.repository.mirrorApplication, org.eclipse.equinox.p2.metadata.repository.mirrorApplication, org.eclipse.equinox.p2.updatesite.UpdateSitePublisher, org.eclipse.equinox.p2.publisher.UpdateSitePublisher, org.eclipse.equinox.p2.publisher.CategoryPublisher, org.eclipse.help.base.infocenterApplication, org.eclipse.help.base.helpApplication, org.eclipse.help.base.indexTool, org.eclipse.jdt.apt.core.aptBuild, org.eclipse.pde.build.Build, org.eclipse.pde.junit.runtime.uitestapplication, org.eclipse.pde.junit.runtime.legacytestapplication, org.eclipse.pde.junit.runtime.coretestapplication, org.eclipse.pde.junit.runtime.coretestapplicationnonmain, org.eclipse.pde.junit.runtime.nonuithreadtestapplication, org.eclipse.ui.ide.workbench, org.eclipse.update.core.standaloneUpdate, org.eclipse.update.core.siteOptimizer, org.eclipse.wst.server.preview.preview, org.eclipse.jdt.core.JavaCodeFormatter, org.eclipse.emf.codegen.CodeGen, org.eclipse.emf.codegen.JMerger, org.eclipse.emf.codegen.ecore.Generator, org.eclipse.wst.jsdt.core.JavaCodeFormatter.
at org.eclipse.equinox.internal.app.EclipseAppContainer.startDefaultApp(EclipseAppContainer.java:248)
at org.eclipse.equinox.internal.app.MainApplicationLauncher.run(MainApplicationLauncher.java:29)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.runApplication(EclipseAppLauncher.java:110)
at org.eclipse.core.runtime.internal.adaptor.EclipseAppLauncher.start(EclipseAppLauncher.java:79)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:344)
at org.eclipse.core.runtime.adaptor.EclipseStarter.run(EclipseStarter.java:179)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.eclipse.equinox.launcher.Main.invokeFramework(Main.java:622)
at org.eclipse.equinox.launcher.Main.basicRun(Main.java:577)
at org.eclipse.equinox.launcher.Main.run(Main.java:1410)
~

Still doesn’t give you any hints? Ok, let’s look again — look for this line: “Application “abc” could not be found in the registry. The applications available are:” and then a whole dump of Java classes: those classes are the applications you can start by invoking Eclipse! Lots of them, huh?

The one we are interested in is this one: org.eclipse.jdt.core.JavaCodeFormatter — that is the main code formatting application in Java (as a side note, org.eclipse.wst.jsdt.core.JavaCodeFormatter can format JavsScript files too!). Next thing to do now is to try to invoke that application:

/eclipse/eclipse -application org.eclipse.jdt.core.JavaCodeFormatter
No configuration file specified.
 
Usage: eclipse -application org.eclipse.jdt.core.JavaCodeFormatter [ OPTIONS ] -config configFile files
 
files   Java source files and/or directories to format.
Only files ending with .java will be formatted in the given directory.
-config configFile Use the formatting style from the specified properties file.
Refer to the help documentation to find out how to generate this file.
 
OPTIONS:
 
-help                Display this message.
-quiet               Only print error messages.
-verbose             Be verbose about the formatting job.

So you can specify a config file and a bunch of Java files to be formatted — cool!
Having tried around various combinations, it turns out that the configFile needs to point to a project config file — if you’re familiar with Eclipse you are familiar with the .settings directory Eclipse create for each project — and typically in there you will find these files:

-rw-r--r--  1 liviutudor  staff  7335 26 Jan 17:39 edu.umd.cs.findbugs.core.prefs
-rw-r--r--  1 liviutudor  staff   222 15 Feb 16:00 org.eclipse.core.resources.prefs
-rw-r--r--  1 liviutudor  staff   682  1 Mar 12:45 org.eclipse.jdt.core.prefs
-rw-r--r--  1 liviutudor  staff    90  1 Mar 12:45 org.eclipse.jdt.ui.prefs
-rw-r--r--  1 liviutudor  staff   116 15 Dec 04:18 org.eclipse.m2e.core.prefs

The one that springs to mind (based on the application name we call) is org.eclipse.jdt.core.prefs — looking into this file doesn’t show much … until you enable project-specific code formatting:

Once you do that your config file will look something like this (this is just a preview of the first few lines):

#Thu Mar 01 12:26:48 PST 2012
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.6
org.eclipse.jdt.core.compiler.compliance=1.6
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
org.eclipse.jdt.core.compiler.source=1.6
org.eclipse.jdt.core.formatter.align_type_members_on_columns=true
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_annotation=0
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_enum_constant=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_explicit_constructor_call=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_arguments_in_qualified_allocation_expression=16
org.eclipse.jdt.core.formatter.alignment_for_assignment=0
org.eclipse.jdt.core.formatter.alignment_for_binary_expression=16
org.eclipse.jdt.core.formatter.alignment_for_compact_if=16
org.eclipse.jdt.core.formatter.alignment_for_conditional_expression=80
org.eclipse.jdt.core.formatter.alignment_for_enum_constants=0
org.eclipse.jdt.core.formatter.alignment_for_expressions_in_array_initializer=16
org.eclipse.jdt.core.formatter.alignment_for_method_declaration=0
org.eclipse.jdt.core.formatter.alignment_for_multiple_fields=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_parameters_in_method_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_selector_in_method_invocation=16
org.eclipse.jdt.core.formatter.alignment_for_superclass_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_enum_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_superinterfaces_in_type_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_constructor_declaration=16
org.eclipse.jdt.core.formatter.alignment_for_throws_clause_in_method_declaration=16
org.eclipse.jdt.core.formatter.blank_lines_after_imports=1
org.eclipse.jdt.core.formatter.blank_lines_after_package=1
org.eclipse.jdt.core.formatter.blank_lines_before_field=0
org.eclipse.jdt.core.formatter.blank_lines_before_first_class_body_declaration=0
org.eclipse.jdt.core.formatter.blank_lines_before_imports=1
org.eclipse.jdt.core.formatter.blank_lines_before_member_type=1
org.eclipse.jdt.core.formatter.blank_lines_before_method=1
org.eclipse.jdt.core.formatter.blank_lines_before_new_chunk=1
org.eclipse.jdt.core.formatter.blank_lines_before_package=0

Which you will notice pretty much describes your formatting template defined in Eclipse (I have my personal “style” which I have defined in Eclipse — you will notice it appearing in the above screenshot as Liv in the list). So now we are nearly ready to launch this formatter against a source file:

/eclipse/eclipse -application org.eclipse.jdt.core.JavaCodeFormatter -config (path to your .settings/org.eclipse.jdt.core.prefs) /path/to/my/javasource.java

This will take a while,  as the whole Eclipse platform gets started, it loads the file and finally runs it through the code formatter, then exists. However, at the end of it, you will have a nicely-formatted Java source file!

So out comes the idea — I can run this through a simple find command, which goes through all my source files, and for each file invoke the above Eclipse application to format the code. And yep, you can definitely do that — and it will work! There is one issue with that: for every file you will be starting Eclipse (takes a while), load the code formatter, load the file, format it, and write it back then exit Eclipse. Do this for 10-20 files and it’s the perfect excuse for an early long lunch! 😀

However, the idea is not bad, we do need to run some sort of find to retrieve the paths to all the Java sources, but would be good if then we can pass it back in one go to the Eclipse application. Turns out you can — if you scroll back up to the help screen printed out, you will notice the help message refers to files (not just file!), so let’s try to pass the code formatter a bunch of files, not just one!

So rather than using a find -exec with the above command, how about we perform 2 steps:

  1. build up the list of all the java source files — using find
  2. pass this list in one go to the code formatter

Let’s try something like this:

#!/bin/bash
FILES=`find /Users/liviutudor/Documents/workspace -regex ".*\.java"`
/eclipse/eclipse -application org.eclipse.jdt.core.JavaCodeFormatter \
-verbose -config (path to your .settings/org.eclipse.jdt.core.prefs) $FILES

Save this script as say format_java.sh (don’t forget to chmod +x!) and then run it. In the above case it would go through all the .java files found under /Users/liviutudor/Documents/workspace and build up the list in the FILES variable, it would then pass this list to the formatter — which would go and format these sources in batch!

You can open the sources at the end and verify the formatting happened — you will be pleasantly surprised that they have been, I bet!

Few things worth mentioning here:

  1. Make sure you specify absolute paths to your source files — Eclipse will start in its installation directory so relative paths won’t be found (that’s why I used the whole path in the above script /Users/liviutudor/Documents/workspace !)
  2. Secondly, if your java executable is not in the path or if your Eclipse complains about not being able to find a JVM, you might have to specify this in the command line (eclipse -vm /path/to/java) otherwise Eclipse won’t be able to start.
  3. The reason for performing the find in the first place is because passing in a wildcard (/Users/liviutudor/Documents/worskspace/*.java) will not work — Eclipse on its own can’t deal with wildcard it seems, and if you leave it to the bash interpreter, this will perform the globbing only in the given directory, without diving deeper into subdirs.
  4. Last but not least, if you have a huge file list, you will notice an error message about “too many files specified” — in that case you might have to perform the formatting in steps — specify one subdirectory first (e.g. /Users/liviutudor/Documents/workspace/Project1/) then another one, and another one etc.

You can modify the above script I’m sure to parametrize it even more — in fact I’d love to hear if someone did that — to do all that for you, maybe limit the file names to 255 in one go etc. I might even do this myself at some point, but for now, my problem was solved by a similar script to the above so it will be a while before I set off to do 😉

NOTE: I have tried the above only in Eclipse Indigo — haven’t gone to the extent of trying it out in other Eclipse versions. If you manage to get it running in other Eclipse versions it would be a good idea to leave a message on this post so other users can see upfront if it is (or not!) possible to do this in your Eclipse version.

See below for my complete Eclipse version and configuration: