Doing Android Java Programming in Emacs

I used to use Eclim as the Emacs Java programming aid. But soon I find out eclipse scales very badly if I put the whole Android project into it – it will take lots of minutes to start, strace showing it scanning all the .repo/.git/out directories for source code files.

So I switched to grow my own scripts to do the job. Here I will describe to you what I have achieved, and more importantly, what remain to be done (it's an embarrassingly long list).

You can watch a (soundless) video of me demoing this tool on youtube, but sadly, I cannot view it myself after I upload it (because I'm behind the Great Kungfu Wall). So if you find problems of this video (as I can't verify it myself), please view it on youku. (The first one who find the youtube video is OK, please leave a comment below it, so that I know it. I can read comments/upload, but not watch. Thanks!)

1 Features already implemented

Using the features of Eclim as a reference, I have implemented the following which I find most useful (and most fun).

1.1 ajoke-get-import

Given a java file, I will parse it and find all the classes/interfaces missing, and add the corresponding import statement at the start of the java source file.

  1. The java.lang.* and classes under the current package are recognized so, and not bothered.
  2. When the supporting script (described below) find more than one class in the whole project with the same name, the elisp command completing-read will be used to trivially prompt you to choose one, as you can see in the video or the screenshot below:

    emacs-java-import.png

    Note that I am using anything.el (now renamed as helm) to make completing-read even more friendly.

1.2 ajoke-get-hierarchy

I can use this command to draw the inheritance tree of a class I am interested in.

Compared to what was provided by Eclim, mine will provide these very useful pieces of information:

  1. Where are all the classes/interfaces in the inheritance tree defined.

    This information is provided in a compilation-mode buffer, so that you can easily jump over.

  2. Where is the current method defined in all the classes/interfaces of the inheritance tree.
  3. The java.lang.Object will be shown for the class which as no extends clause in its declaration.
  4. If required (with an option flag for the underlying perl script), list all the methods/fields of all the classes/interfaces in the tree (see usage below, see FIXMEs below).

You can see it in the video, or the below screenshot, note how the onCreate method has been show below each of the classes who defines it.

emacs-java-hierarchy.png

Here is another one showing a similar class and the onKeyUp method.

ajoke-get-hierarchy.png

1.3 ajoke-resolve

This method seem to be not very useful, but it is depended upon by the next very useful command.

To put it simply, put the point behind xx. below and invoke this command, and it will tell you its type is java.util.ArrayList or simply ArrayList, depending on whether you have imported ArrayList.

ArrayList<String> xx;
xx.  <= put point here and M-x ajoke-resolve

1.4 ajoke-complete-method

As said above, if you put point after xx. and invoke ajoke-complete-method, it will let you completing-read choose from all the methods of ArrayList (and its superclass and interfaces's).

It works by reusing ajoke-resolve on xx and find its type ArrayList, then ajoke-get-hierarchy on ArrayList to list all its methods.

See the video or the below screenshot:

ajoke-complete-method.png

1.5 ajoke-get-override

You may need to redefine a method of a parent class/interface, this elisp command can come to help. You can completing-read choose which method to override. See the video or the screenshot:

ajoke-get-override.png

2 Usage

This system is part of my personal configuration system, I have not found time to exact them into a separate project yet.

It is all in this github project(and its git submodules). You can start from the .emacs and find the scripts under bin.

If you are willing to try my whole configuration, you must use a recent Debian or Ubuntu distribution, and can do it in following steps.

Warning: before you start, I highly suggest you create and use a test account (sudo useradd) because your existing .emacs/.bashrc etc. will be moved to .emacs.bak/.bashrc.bak and maybe get lost.

2.1 Set up my configuration

cd ~
git clone --recursive http://github.com/baohaojun/system-config
~/system-config/bin/after-co-ln-s.sh
~/system-config/bin/Linux/after-check-out.sh

2.2 Test the system

cd ~/system-config/gcode/java-test
for-code-reading
ajoke-get-hierarchy.pl Child -m Child

You should see output like the following:

=> class com.human.Child at com/human/Child.java line 2.
   => interface com.human.Mother at com/human/Mother.java line 2.

3 FIXME

This system is far from complete. Even this FIXME list is not complete, as I can only list those that I am aware of.

Please note that some issues are not meant to be fixed, because fixing them will turn this tool into exactly the same thing as Eclipse and get too slow:-)

3.1 Overall problems

  • Not a separate project.

    This will give other people a very difficult choice to use my scripts.

  • only works with android

    Because all the class/method/field information is extracted from the java source file using ctags-exuberant, and not from some .jar files in the CLASSPATH, the whole java system source code must be present for this system to work.

    Android is the only system I know that satisfies this high-demanding condition. Maybe I will work on this someday when I need write a non-Android project.

  • Some commands are too slow (several seconds to complete a method)

    I think I should someday add additional caching for better performance.

3.2 Problems of individual commands

  • ajoke-get-import

    It should not offer to import the android resouce class android.R, because an APK project will generate its own R.java in the same package.

    And it does not handle generic types very well.

  • ajoke-resolve

    It parse String[] strArr as java.lang.String, not as Array.

  • ajoke-get-hierarchy

    When asked to list all methods/fields, it will literally list everything, even private ones.

  • ajoke-get-override
    1. Should support batch overriding for e.g., all the methods of an interface.
    2. Methods already defined should be filtered out and not offer to be overridden again.

4 Misc information

4.1 Heavy use of other FLOSS projects

ctags-exuberant and global are heavily used.

4.2 Skeleton and regexp rewrite completion

In that video, you will see me type a skeleten and get it completed, this idea is very much like anything.el or (as I think) icicle, ido, etc. You may also find the Firefox browser's URL completion similar to this idea.

The usual Emacs completion require matching at the head, for e.g., if you want to complete this identifier, savedInstanceState, you must type in the beginning part of it sav, or saved, and press M-/ to hippie-expand it. This can be sometimes a bit inconvenient.

With my completion, I can start typing anything that is in the identifier, such as state or sainst, or vedstst, as long as the characters are in the same order as the original word. Here the skeleton vedstst will be rewritten as a regexp by using (mapconcat 'string "vedstst" ".*") as v.*e.*d.*s.*t.*s.*t, which matches and completes to the target savedInstanceState.

When there are multiple matches, hippie-expand (by default) will rotate the completions one-by-one, and I have never found out how to rotate backwards if I rotated one past the right choice. Undo?

With my completion, all the choices are listed in the rough order how far they are found from the point (I think hippie-expand will try all matches after the point, then all those before the point; mine will interleave them), and you can scroll up and down the list (just like anything.el's completing-read).

This tool is so convenient that I don't find some of my java-coding scrits's bugs so urgent to fix.

You can see in the video or the below screenshot: regexp-completion.png

You can find the elisp source in skeleton-complete.el.

4.3 What about JDEE or other Emacs Java plugins?

I have not tried them. Not a big fan of ecb/cedet etc. because these tools are not friendly when using them with TRAMP.

4.4 What parser did I use for parsing the java file

I tried ANTLR, but found it too heavy and too slow.

But while trying ANTLR, I learned how Java is a very regular language compared to C and C++, since it has no preprocessor. So I devised a simple script to turn a Java source file into its truely most regular form (hopefully still a 95% valid java program):

  1. Get rid of all comment, all whitespace that won't affect sematic when removed, and all string/character literals (replacing them all with empty string literal ("").

    An white space between 2 words such as public and int is non-removable, but a space character after the > in ArrayList<String> can be safely removed (I think).

  2. Insert a newline after these 3 characters (;{}).
  3. Indent it properly if you want it to be more beautiful to the eyes.

An example follows. Given this wildly irregular piece of Java code, notice how difficult it will be to figure out that the prototype of getWordIdx is @Override public int getWordIdx(String word){ (maybe without the @Override and the {), or even to tell that there is a method named getWordIdx:

class test 
implements
    stintf {

/*
watch how the following very irregular method will be formatted
beautifully

*/

    @Override
    public
        int
        getWordIdx(String
 word) 
    {
ArrayList<String> xx;
if (word == "hello world") {
        return
            0;
    }
    }
}

You can use flatten.pl to filter it into the following highly regexp matchable piece of Java, and you can easily extract the whole prototype of the getWordIdx method, or the class declaration line for test:

class test implements stintf{
    @Override public int getWordIdx(String word){
        ArrayList<String>xx;
        if(word==""){
            return 0;
        }
    }
}