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.
- The java.lang.* and classes under the current package are recognized so, and not bothered.
- 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: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:
- 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. - Where is the current method defined in all the classes/interfaces of the inheritance tree.
- The java.lang.Object will be shown for the class which as no
extends
clause in its declaration. - 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.
Here is another one showing a similar class and the onKeyUp method.
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:
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
- Should support batch overriding for e.g., all the methods of an interface.
- 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:
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):
- 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
andint
is non-removable, but a space character after the>
inArrayList<String>
can be safely removed (I think). - Insert a newline after these 3 characters
(;{})
. - 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; } } }