Enterprise Java Development in Emacs

png

I will on this page describe how I use GNU Emacs for enterprise Java ™ development using JDEE, ECB, JDIbug, The Eclipse Java Compiler and nxml-mode.

On this page, you'll learn how you can:

Bear necessities

The bear necessities for any java developer is syntax highlighting, autocompletion of methods and a class browser. Luckily enough, Emacs provides them all :-)

ecb and auto complete menu

On the fly syntax checking

A well kept secret, is that it's possible to achieve on the fly syntax checking of your Java code using The Eclipse compiler and Emacs' built-in flymake mode. flymake and ecj

I was first made aware of this thanks to Arjen Wiersma's excellent blog post

Remote debugging

Emacs can quite comfortably do debugging of Java applications running on remote JVMs using the excellent JDIbug.

JDEE provides two other means to debug applications, but I would recommend JDIbug any day of the week. It's fast and provides a visual tree of variables. Currently (2009-07-18 13:06), it has one drawback; it cannot debug inner classes, but I'm sure this is something that will be remedied in a later release.

remote debugging with JDIbug

Given that you have the given source code in the jde-sourcepath variable, you can browse the call stack leading up to the break point. Here, I am browsing the state of one of Tomcat's classes when executing my test.jsp

remote debugging with JDIbug

Here's a wider screenshot when browsing the call stack

Maven support

maven

As a Java developer you're most probably using Maven to build, package and releaese software. Thanks to nxml-mode, Emacs provides auto completion of tags and on the fly syntax checking of your pom.xml files.

maven pom and nxml-mode

To compile projects from within Emacs, I've bound Emacs' compile command to C-z. This is personal preference of course, but I far more often want to compile my software than minimise Emacs (which is the default behaviour of C-z

Here, I am compiling my project using Maven. Compilation errors are given as links that will take you to the line and column in the source file where the error is. As with all compile buffers in Emacs, you can jump to the next error by hittihg C-` (very easy shortcut on an American keyboard, on e.g. Norwegian or German it's not, hence I recommend using American keyboard layout for programming).

compile with Maven

Ant support

Emacs will happily allow you to compile Ant projects with jump to error links (as shown above with Maven projects) and edit Ant build.xml files.

ant and nxml-mode

Support for editing Java related XML formats

As a Java developer, you're most likely to also want support for the following XML formats:

Luckily, this is a doodle for Emacs, as long as it has everything's setup correctly.

Running JUnit tests

Override method

Override any of the parent class(es)' methods by hitting C-c C-v o. Emacs will then let you TAB complete your way through the available methods.

override method

Jump to source

Emacs will jump to the source of the class at point or where the variable is instantiated when you hit C-c C-v C-y

Imports

Emacs can either import the class where your cursor is by hitting C-c C-v C-z or import all classes missing from the import list by hitting the (almost identical) shortcult: C-c C-v z

import all

Implement interface

Emacs will implement any interface that in its jde-global-classpath variable (or prj.el). Hit C-x C-v i and TAB-complete your way to the interface and hit ENTER. Emacs will then generated method stubs of all the interface methods.

implement interface

How to set it all up

Okay, now I've shown you what you get, now I show you what you need to get it :-)

Using Debian based systems

If you're using Debian or a Debian based system such as Ubuntu, then all you need to do to install the different packages are:

# apt-get install sun-java6-bin emacs22 nxml-mode jde ecb ecj

Currently (2009-07-18 16:12), there's no Debian package for JDIbug, so you must download it from code.google.com/jdibug

If you're using a different system

All the packages above are possible to install on other systems such as Microsoft Windows, Mac OS X, Sun Solaris, Fedora, SuSE and FreeBSD

The packages may or may not be available through the system's package manager (the BSDs, Open Solaris and GNU/Linux distributions have this, Windows, Mac and Sun Solaris doesn't).

To manually install the needed software packages, download them from the these addresses:

Support for Java related XML formats

Download these RNC files and this schemas.xml to make nxml-mode recognise Java related XML files such as spring-beans.xml, struts-config.xml, web.xml, pom.xml and build.xml.

Java related Emacs settings

This is my Java hook, giving me:

(defun my-c-mode-hook ()
    (setq c-basic-offset 2
          c-label-offset 0
          indent-tabs-mode nil
          compile-command "cd ~/projects/myproject; mvn compile"
          require-final-newline nil)
    (lambda () (auto-fill-mode 1))
    (define-key c-mode-base-map "\C-m" 'c-context-line-break)
    (global-set-key "\M-n" 'jde-complete-minibuf)
    (global-set-key "\M-N" 'jde-complete-menu)
    (define-key c-mode-base-map "\C-c\C-p" 'show-previous-error)
    (define-key c-mode-base-map "\C-c\C-n" 'show-next-error)
    (c-set-offset 'substatement-open 0))
  (add-hook 'c-mode-common-hook 'my-c-mode-hook)

Understanding compiler output

(require 'compile)
  (setq compilation-error-regexp-alist
    (append (list
       ;; works for jikes
       '("^\\s-*\\[[^]]*\\]\\s-*\\(.+\\):\\([0-9]+\\):\\([0-9]+\\):[0-9]+:[0-9]+:" 1 2 3)
       ;; works for javac
       '("^\\s-*\\[[^]]*\\]\\s-*\\(.+\\):\\([0-9]+\\):" 1 2)
       ;; works for maven
       '("^\\(.*\\):\\[\\([0-9]*\\),\\([0-9]*\\)\\]" 1 2 3))
    compilation-error-regexp-alist))

Setting up JDIbug

Add this to your .emacs, adjusting the path to jdibug to where you have your copy.

(add-to-list 'load-path "/usr/local/src/jdibug")
  (require 'jdibug)

Be sure to start the app server with the following JVM parameters: -Xdebug -Xnoagent -Djava.compiler=NONE -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=6001

If you're using Apache Tomcat, you can set the CATALINA_OPTS variable, e.g.:

$ export CATALINA_OPTS="-Xdebug -Xnoagent -Djava.compiler=NONE \
      -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=6001"
  $ ./tomcat/bin/catalina.sh start

Nedless to say, you will soon put this in a script instead of typing it every time you start the app server :-)

Setting up flymake and ejc

This configuration is taken from Arjen Wiersma's excellent blog post, I have modified it a wee bit (using the ecj BASH script instead of calling the JAR directly) and added two methods for jumping to next/previous errors.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Java flymake support using the Eclipse compiler
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  (require 'flymake)

  (defun flymake-java-ecj-init ()
    (let* ((temp-file   (flymake-init-create-temp-buffer-copy
                         'jde-ecj-create-temp-file))
           (local-file  (file-relative-name
                         temp-file
                         (file-name-directory buffer-file-name))))

      ;; if you've downloaded ecj from eclipse.org, then use these two lines:
      ;;    (list "java" (list "-jar" 
      ;;                       "/usr/share/java/ecj.jar"

      ;; if installing it with DEB packages,or by some other means
      ;; giving you the ecj BASH script front end, simply use this line
      ;; instead:
      (list "ecj" (list 
                         "-Xemacs" 
                         "-d" "/dev/null" 
                         "-source" "1.5"
                         "-target" "1.5"
                         "-sourcepath" (car jde-sourcepath)
                         "-classpath" 
                         (jde-build-classpath jde-global-classpath)
                         local-file))))

  (defun flymake-java-ecj-cleanup ()
    "Cleanup after `flymake-java-ecj-init' -- delete temp file and dirs."
    (flymake-safe-delete-file flymake-temp-source-file-name)
    (when flymake-temp-source-file-name
      (flymake-safe-delete-directory
       (file-name-directory flymake-temp-source-file-name))))

  (defun jde-ecj-create-temp-file (file-name prefix)
    "Create the file FILE-NAME in a unique directory in the temp directory."
    (file-truename (expand-file-name
                    (file-name-nondirectory file-name)
                    (expand-file-name  (int-to-string (random)) 
                                       (flymake-get-temp-dir)))))

  (push '(".+\\.java$" flymake-java-ecj-init 
          flymake-java-ecj-cleanup) flymake-allowed-file-name-masks)

  (push '("\\(.*?\\):\\([0-9]+\\): error: \\(.*?\\)\n" 1 2 nil 2 3
          (6 compilation-error-face)) compilation-error-regexp-alist)

  (push '("\\(.*?\\):\\([0-9]+\\): warning: \\(.*?\\)\n" 1 2 nil 1 3
          (6 compilation-warning-face)) compilation-error-regexp-alist)

  (defun credmp/flymake-display-err-minibuf () 
    "Displays the error/warning for the current line in the minibuffer"
    (interactive)
    (let* ((line-no             (flymake-current-line-no))
           (line-err-info-list  (nth 0 (flymake-find-err-info
                                        flymake-err-info line-no)))
           (count               (length line-err-info-list))
           )
      (while (> count 0)
        (when line-err-info-list
          (let* ((file       (flymake-ler-file (nth (1- count)
                                                    line-err-info-list)))
                 (full-file  (flymake-ler-full-file (nth (1- count)
                                                         line-err-info-list)))
                 (text (flymake-ler-text (nth (1- count) line-err-info-list)))
                 (line       (flymake-ler-line (nth (1- count)
                                                    line-err-info-list))))
            (message "[%s] %s" line text)
            )
          )
        (setq count (1- count)))))

  (defun show-previous-error ()
    (interactive)
    (flymake-goto-prev-error)
    (credmp/flymake-display-err-minibuf))

  (defun show-next-error ()
    (interactive)
    (flymake-goto-next-error)
    (credmp/flymake-display-err-minibuf))