Created
November 24, 2013 21:48
-
-
Save kbsriram/7632814 to your computer and use it in GitHub Desktop.
simple emacs tags generator for java source - only indexes classes and interfaces.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.