Skip to content

Instantly share code, notes, and snippets.

@kbsriram
Created November 24, 2013 21:48
Show Gist options
  • Save kbsriram/7632814 to your computer and use it in GitHub Desktop.
Save kbsriram/7632814 to your computer and use it in GitHub Desktop.
simple emacs tags generator for java source - only indexes classes and interfaces.
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.File;
import java.io.Reader;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.StreamTokenizer;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
// Usage:
// java Gen <full-path-to-source-tree>
//
// That recursively traverses it, looks for *.java
// and attempts to generate a tags file that ONLY
// indexes classes and interfaces (including inner ones.)
public class Gen
{
public static void main(String args[])
throws IOException
{
File root = new File(args[0]);
File tags = new File("tags");
if (tags.canRead()) {
throw new IOException
("Won't overwrite existing tags file - "+
"please move or remove it first.");
}
BufferedOutputStream bout = new BufferedOutputStream
(new FileOutputStream(tags));
process(root, bout);
bout.close();
}
private final static void process(File root, OutputStream out)
throws IOException
{
if (root.isDirectory()) {
File[] children = root.listFiles();
if (children != null) {
for (int i=0; i<children.length; i++) {
process(children[i], out);
}
}
return;
}
String path = root.getPath();
if (!path.endsWith(".java")) { return; }
analyzeFile(root, out);
}
// crude technique [ahem] way to parse the file. I just want
// class names; and this parser fits into the "works for me"
// category.
private final static void analyzeFile
(File root, OutputStream out)
throws IOException
{
CountingReader br = null;
try {
br = new CountingReader(new FileReader(root));
StreamTokenizer stok = new StreamTokenizer(br);
stok.resetSyntax();
stok.slashStarComments(true);
stok.slashSlashComments(true);
stok.wordChars('a', 'z');
stok.wordChars('A', 'Z');
stok.wordChars('_', '_');
stok.wordChars('.', '.'); // takes care of MyClass.class
stok.wordChars('0', '9');
stok.wordChars(128+32, 255);
stok.whitespaceChars(0, ' ');
stok.quoteChar('"');
Locations loc = new Locations(root.getPath());
stok.nextToken();
parse(stok, br, loc, "");
dumpTag(out, loc);
int a4444 = 444;
}
finally {
if (br != null) {
try { br.close(); }
catch (IOException ign) {}
}
}
}
// always enter with stok.ttype pointing to the
// token to be parsed.
private final static void parse
(StreamTokenizer stok, CountingReader br, Locations loc, String pfx)
throws IOException
{
while (true) {
switch (stok.ttype) {
case StreamTokenizer.TT_EOF:
return;
case '{':
// eat and push a level down
stok.nextToken();
parse(stok, br, loc, pfx);
break;
case '}':
// eat and pop up a level
stok.nextToken();
return;
case StreamTokenizer.TT_WORD:
if ("class".equals(stok.sval) ||
"interface".equals(stok.sval)) {
parseLocationBlock(stok, br, loc, pfx);
}
else {
// fuggetaboudit
stok.nextToken();
}
break;
default:
// eat and keep going.
stok.nextToken();
break;
}
}
}
private final static void parseLocationBlock
(StreamTokenizer stok, CountingReader br, Locations loc, String pfx)
throws IOException
{
// This is a bit fiddly; because the tokenizer keep going
// till it goes one token past the name. But by that time,
// we may have hit a newline.
String cn = expectName(stok);
int curoff = br.m_lcount - 1;
String lpfx = br.m_lb.toString();
if (lpfx.length() == 0) {
lpfx = br.m_prev.toString();
curoff -= lpfx.length();
}
StringBuilder sb = new StringBuilder();
sb.append(lpfx);
sb.append("\u007f"); // ^?
sb.append(pfx);
sb.append(cn);
sb.append("\u0001"); // ^A
sb.append(String.valueOf(stok.lineno()));
sb.append(",");
sb.append(String.valueOf(curoff));
loc.m_refs.add(sb.toString());
while (stok.nextToken() != '{') {
if (stok.ttype == StreamTokenizer.TT_EOF) {
throw new IOException("Missing { at "+stok.lineno());
}
}
// leave cursor at start of next token.
stok.nextToken();
parse(stok, br, loc, pfx+cn+".");
}
private final static String expectName(StreamTokenizer stok)
throws IOException
{
if (stok.nextToken() != StreamTokenizer.TT_WORD) {
throw new IOException("Unexpected - non-word at "+ stok.lineno());
}
return stok.sval;
}
private final static void dumpTag(OutputStream out, Locations loc)
throws IOException
{
// header ^L^J
out.write(0x0c);
out.write(0x0a);
// I need the length of the section.
StringBuilder sb = new StringBuilder();
for (String ref: loc.m_refs) {
sb.append(ref);
sb.append("\n"); // ^J
}
byte[] def = asBytes(sb.toString());
out.write(asBytes(loc.m_path));
out.write(asBytes(","));
out.write(asBytes(String.valueOf(def.length)));
out.write(0x0a);
out.write(def);
}
private final static byte[] asBytes(String s)
throws IOException
{ return s.getBytes("utf-8"); }
private final static class Locations
{
private Locations(String path)
{ m_path = path; }
private final String m_path;
private final List<String> m_refs = new ArrayList<String>();
}
private final static class CountingReader
extends BufferedReader
{
public CountingReader(Reader in)
{ super(in); }
@Override
public int read()
throws IOException
{
m_count++;
int ret = super.read();
if (ret == '\n') {
m_prev = m_lb;
m_lb = new StringBuilder();
m_lcount = m_count;
}
else {
m_lb.append((char) ret);
}
return ret;
}
private int m_count = 0;
private int m_lcount = 0;
private StringBuilder m_lb = new StringBuilder();
private StringBuilder m_prev = null;
}
}
@kbsriram
Copy link
Author

Frustrated with the tag generators for java [massively complex, or unusable ones] for my simple needs [I just want to quickly jump around the android source via class names] here's a single file that walks through a source tree and does the trick for me.

$ javac Gen.java
$ java Gen ~/android-sdk-macosx/sources/android-19
$ ls -lh tags
-rw-r--r--  1 kbs  staff   1.6M Nov 24 13:46 tags
$ 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment