001: package xtc.lang;
002:
003: import java.io.CharArrayWriter;
004: import java.io.FileReader;
005: import java.io.IOException;
006: import java.io.LineNumberReader;
007: import java.io.File;
008: import java.io.PrintWriter;
009: import java.util.HashMap;
010: import java.util.regex.Matcher;
011: import java.util.regex.Pattern;
012: import java.util.TreeSet;
013:
014: /**
015: * A class to extract a line number mapping for a source-to-source
016: * transformation. The "input source" and the "output source" are input and
017: * output of the source-to-source transformation. For instance, Main.jni and
018: * Main.java are input source and output source files in the Jeannie framework.
019: * This source map extractor assumes that the output source file contains the
020: * input source line directives in a "//#line ${lineno} ${source file} form, and
021: * this form is similiar to the C line number directive.
022: *
023: * The source map extractor takes an output source file (Main.java), and
024: * produces a mapping from the input source files (Main.jni) to output source
025: * file (Main.java) in JSR45 SMAP format. For more information, look at JSR45
026: * [http://jcp.org/en/jsr/detail?id=45].
027: *
028: * @author Byeongcheol Lee
029: */
030: public class SourceMapExtractor {
031:
032: /**
033: * A regular expression to recognize line number directive like the following.
034: *
035: * //#line ${lineno} ${source file}
036: */
037: private static final Pattern lineDirectivePattern = Pattern
038: .compile("^\\s*//#line\\s+([0-9]+)\\s+\"(\\S+)\"$");
039:
040: /**
041: * Print a command line usage, and exit with an error code.
042: *
043: * @param msg A user message to explain what was wrong.
044: */
045: private static void usage(String msg) {
046: String umsg = "usage: SourceMapExtractor [source file]";
047: System.err.println(umsg + "\n" + msg);
048: System.exit(-1);
049: }
050:
051: /**
052: * Drives printing the source remapping infomation for the given
053: * #line-decorated source file.
054: *
055: * @param args A command line arguments.
056: */
057: public static void main(String[] args) {
058:
059: String outputSourceFileName = null;
060:
061: // parse command line argument
062: for (int i = 0; i < args.length; i++) {
063: String arg = args[i];
064: if (outputSourceFileName == null) {
065: outputSourceFileName = arg;
066: } else {
067: usage("can not recognize command line option:" + arg);
068: }
069: }
070:
071: // check outputSourceFileName is readable
072: if (outputSourceFileName == null) {
073: usage("please, specify [java source file]");
074: }
075: File javaSourceFile = new File(outputSourceFileName);
076: if (!javaSourceFile.isFile()) {
077: usage("can not find " + outputSourceFileName);
078: }
079: if (!javaSourceFile.canRead()) {
080: usage("check permission for reading "
081: + outputSourceFileName);
082: }
083:
084: try {
085: // read output source file to get source-to-source mapping
086: SourceMapExtractor smapgen = new SourceMapExtractor(
087: outputSourceFileName);
088: smapgen.genSMAP();
089:
090: // print the source-to-source map to standard output.
091: System.out.println(smapgen.toStringInSMAPFormat());
092:
093: } catch (IOException ioe) {
094: System.err.println("failed in extracting SMAP from the "
095: + outputSourceFileName);
096: }
097: }
098:
099: /**
100: * The input source file.
101: */
102: final protected String sourceFile;
103:
104: /**
105: * The total number of lines in the source file.
106: */
107: protected int numSourceLines = 0;
108:
109: /**
110: * The line-to-line mapping from the input source file to the output source
111: * file.
112: */
113: final protected TreeSet<SMAPLineEntry> smapLineEntries = new TreeSet<SMAPLineEntry>();
114:
115: /**
116: * The mapping from an inputSourceFile to its unique id. Here, the input
117: * source files appear in the line directive.
118: */
119: final protected HashMap<String, Integer> inputSourceFile2id = new HashMap<String, Integer>();
120:
121: /**
122: * The number of input source files so far. This is to generate unique id for
123: * new input file.
124: */
125: protected int numInputSourceFiles = 0;
126:
127: /**
128: * A reverse mapping from an output source line to an input source line in
129: * case of a single input source file.
130: */
131: private int[] outputSourceLine2inputSourceLine;
132:
133: /**
134: * @param s An input source file.
135: */
136: public SourceMapExtractor(String s) {
137: this .sourceFile = s;
138: }
139:
140: /**
141: * @return A number of input source files in the source-to-source mapping.
142: */
143: public int getNumberOfInputSourceFiles() {
144: return numInputSourceFiles;
145: }
146:
147: /**
148: * Search for line number directive (//#line ...) to generate a
149: * source-to-source mapping information.
150: */
151: public void genSMAP() throws IOException {
152: FileReader freader = new FileReader(sourceFile);
153: LineNumberReader lnr = new LineNumberReader(freader);
154:
155: LineNumberDirective prevJNILineDirective = null;
156: while (true) {
157: String line = lnr.readLine();
158: if (line == null) {
159: numSourceLines = lnr.getLineNumber();
160: // EOF
161: if (prevJNILineDirective != null) {
162: addJNISourceLine(prevJNILineDirective, lnr
163: .getLineNumber());
164: }
165: break;
166: }
167:
168: // check the line is of the form: //#line ${line number} ${filename}
169: LineNumberDirective curJNILineDirective = checkJNILineNumberDirective(
170: line, lnr.getLineNumber());
171: if (curJNILineDirective == null) {
172: continue;
173: }
174:
175: // handle line directive: //#line ${line number} ${filename}
176: if (prevJNILineDirective != null) {
177: // flush the previous line number directive.
178: addJNISourceLine(prevJNILineDirective, lnr
179: .getLineNumber() - 1);
180: }
181:
182: prevJNILineDirective = curJNILineDirective;
183: }
184: }
185:
186: /**
187: * Check if the current line is line number directive. If this line is not a
188: * line number directive, this method returns null. Otherwise, this method
189: * returns a line number directive object.
190: *
191: * @param line A line to be checked.
192: * @param lineNumber The current java line where the line appears.
193: * @return An line number directive object.
194: */
195: private LineNumberDirective checkJNILineNumberDirective(
196: String line, int lineNumber) {
197: // try paring : //#line ${file name} ${line number}
198: final Matcher m = lineDirectivePattern.matcher(line);
199: if (!m.matches()) {
200: return null;
201: }
202:
203: assert (m.group(1).length() > 0) && (m.group(2).length() > 0);
204:
205: int jniLineNumber = Integer.parseInt(m.group(1));
206: String fileName = m.group(2);
207: int fileID = ensureSourceFileID(fileName);
208: LineNumberDirective directive = new LineNumberDirective(fileID,
209: jniLineNumber, lineNumber);
210:
211: return directive;
212: }
213:
214: /**
215: * Add a line-to-line mapping entry to the smapLineEntries table.
216: *
217: * @param lineDirective A line number directive.
218: * @param lineEnd The end line number that the lineDirective affects.
219: */
220: private void addJNISourceLine(
221: final LineNumberDirective lineDirective, final int lineEnd) {
222:
223: final int javaBegin = lineDirective.outputSourceLineNumber + 1;
224: final int count = lineEnd - javaBegin + 1;
225: // non-empty java line
226: assert (javaBegin > 0) && (lineEnd > 0) && (count >= 1);
227:
228: final int jniLineStart = lineDirective.inputSurceLineNumber;
229: final int jniFileID = lineDirective.sourceFileID;
230:
231: SMAPLineEntry entry = new SMAPLineEntry(jniLineStart,
232: jniFileID, javaBegin, count);
233:
234: smapLineEntries.add(entry);
235: }
236:
237: /**
238: * Ensure the input source file id is present in the inputSourceFile2id table.
239: *
240: * @param sourceFile A source file name.
241: * @return A file identification number.
242: */
243: private int ensureSourceFileID(final String sourceFile) {
244: int id;
245:
246: if (inputSourceFile2id.containsKey(sourceFile)) {
247: id = inputSourceFile2id.get(sourceFile);
248: } else {
249: numInputSourceFiles++;
250: id = numInputSourceFiles;
251: inputSourceFile2id.put(sourceFile, id);
252: }
253:
254: assert inputSourceFile2id.containsKey(sourceFile);
255:
256: return id;
257: }
258:
259: /**
260: * This routine returns a source file name that appeared in all the line
261: * number directive. This routine is only for the -fatten option in the
262: * ClassSOurceRemapper.
263: *
264: * @return A source file name.
265: */
266: protected String getSingleSourceFileName() {
267:
268: assert numInputSourceFiles == 1;
269:
270: String singleJNIFile = null;
271: for (final String jniFile : inputSourceFile2id.keySet()) {
272: singleJNIFile = jniFile;
273: break;
274: }
275:
276: return singleJNIFile;
277: }
278:
279: /**
280: * Given output source line number (e.g. in Main.java), this routine returns a
281: * corresponding input source line number (e.g. in Main.jni). This routine is
282: * only for the -fatten option in the ClassSOurceRemapper.
283: *
284: * @param sourceLineNumber An output source line number.
285: * @return An input source line number.
286: */
287: protected int getSingleSourceLine(int sourceLineNumber) {
288:
289: assert numInputSourceFiles == 1;
290:
291: if (outputSourceLine2inputSourceLine == null) {
292: outputSourceLine2inputSourceLine = new int[numSourceLines + 1];
293: for (final SMAPLineEntry entry : smapLineEntries) {
294: int begin = entry.getOutputLineBegin();
295: int end = entry.getOutputLineEnd();
296: assert (begin >= 1) && (end >= begin);
297: for (int outputLine = begin; outputLine <= end; outputLine++) {
298: int inputLine = entry
299: .getInputSourceLine(outputLine);
300: int oldInputLine = outputSourceLine2inputSourceLine[outputLine];
301: if (oldInputLine >= 1) {
302: // take minium if conflict
303: if (oldInputLine < inputLine) {
304: outputSourceLine2inputSourceLine[outputLine] = inputLine;
305: }
306: } else {
307: // first time
308: outputSourceLine2inputSourceLine[outputLine] = inputLine;
309: }
310: }
311: }
312: }
313:
314: assert outputSourceLine2inputSourceLine != null;
315: return outputSourceLine2inputSourceLine[sourceLineNumber];
316: }
317:
318: /**
319: * Return the source-to-source information in SMAP (JSR45) format.
320: *
321: * @return A text in SMAP format.
322: */
323: public String toStringInSMAPFormat() {
324:
325: CharArrayWriter wa = new CharArrayWriter();
326: PrintWriter w = new PrintWriter(wa);
327: w.println("SMAP");
328: w.println(sourceFile);
329: w.println("JNI");
330: w.println("*S JNI");
331:
332: // list of jni source file name and id
333: w.println("*F");
334: for (final String fname : inputSourceFile2id.keySet()) {
335: int fid = inputSourceFile2id.get(fname);
336: w.println("" + fid + " " + fname);
337: }
338:
339: // list of JNI to java line number with the following format (JSR45)
340: w.println("*L");
341: for (final SMAPLineEntry jline : smapLineEntries) {
342: w.println(jline.toStringInSMAPFormat());
343: }
344:
345: w.println("*E");
346: w.close();
347:
348: return wa.toString();
349: }
350:
351: /**
352: * A SMAP line mapping entry class.
353: *
354: */
355: private static final class SMAPLineEntry implements
356: Comparable<SMAPLineEntry> {
357:
358: /**
359: * These fields represent a partial continuous line number mapping from the
360: * input source files to the output source file. For futher information,
361: * look at JSR45.
362: */
363: private final int inputStartLine;
364:
365: private final int inputFileID;
366:
367: private final int repeat;
368:
369: private final int outputStartline;
370:
371: private final int ouputLineIncrement;
372:
373: /**
374: * @param inputLine A beginning input source line number.
375: * @param inputFileID An intput source file id number.
376: * @param outputLineStart A beginning output source line number.
377: * @param count A number of lines in the inputr source.
378: */
379: SMAPLineEntry(int inputLine, int inputFileID,
380: int outputLineStart, int count) {
381: assert (inputLine >= 1) && (outputLineStart >= 1)
382: && (count > 0);
383: this .inputStartLine = inputLine;
384: this .inputFileID = inputFileID;
385: this .repeat = count;
386: this .outputStartline = outputLineStart;
387: this .ouputLineIncrement = 1;
388: }
389:
390: /**
391: * @return The beginning input source line number.
392: */
393: private int getInputLineBegin() {
394: return inputStartLine;
395: }
396:
397: /**
398: * @return The ending input source line number.
399: */
400: private int getInputLineEnd() {
401: return inputStartLine + repeat - 1;
402: }
403:
404: /**
405: * @return The beginning output source line number.
406: */
407: private int getOutputLineBegin() {
408: return outputStartline;
409: }
410:
411: /**
412: * @return The end of output source line number.
413: */
414: private int getOutputLineEnd() {
415: return outputStartline + repeat * ouputLineIncrement - 1;
416: }
417:
418: /**
419: * @param outputLine The line number of the output file.
420: * @return An line number of the input file.
421: */
422: private int getInputSourceLine(int outputLine) {
423: assert (outputLine >= getOutputLineBegin())
424: && (outputLine <= getOutputLineEnd());
425: int jniLine = (outputLine - outputStartline)
426: / ouputLineIncrement + inputStartLine;
427: assert (jniLine >= inputStartLine)
428: && (jniLine <= getInputLineEnd());
429: return jniLine;
430: }
431:
432: /**
433: * @return A text in the SMAP format.
434: */
435: private String toStringInSMAPFormat() {
436: StringBuffer sb = new StringBuffer();
437:
438: sb.append(inputStartLine);
439: sb.append("#").append(inputFileID);
440: if (repeat != 1) {
441: sb.append(",").append(repeat);
442: }
443: sb.append(":").append(outputStartline);
444: if (ouputLineIncrement != 1) {
445: sb.append(",").append(ouputLineIncrement);
446: }
447:
448: return sb.toString();
449: }
450:
451: /**
452: * Compare and Order smap entry by output source line number.
453: *
454: * @param o2 A SMAP entry.
455: * @return An integer value to compare two SMAP entry.
456: */
457: public int compareTo(SMAPLineEntry o2) {
458: if (inputFileID != o2.inputFileID) {
459: return inputFileID - o2.inputFileID;
460: } else {
461: if (outputStartline != o2.outputStartline) {
462: return outputStartline - o2.outputStartline;
463: } else {
464: return inputStartLine - o2.inputStartLine;
465: }
466: }
467: }
468: }
469:
470: /**
471: * A class to represent a line number directive event.
472: */
473: private static final class LineNumberDirective {
474:
475: /**
476: * The output source file line number where this event happened.
477: */
478: private final int outputSourceLineNumber;
479:
480: /**
481: * The input source line number that appeared in the line number directive.
482: */
483: private final int inputSurceLineNumber;
484:
485: /**
486: * The id number for the input source file that appeared in the line number
487: * directive.
488: */
489: private final int sourceFileID;
490:
491: /**
492: * @param fid The input source file id number.
493: * @param inline The input source line number.
494: * @param outline The outoput source line number.
495: */
496: public LineNumberDirective(int fid, int inline, int outline) {
497: assert (fid > 0) & (inline > 0) & (outline > 0);
498: sourceFileID = fid;
499: inputSurceLineNumber = inline;
500: outputSourceLineNumber = outline;
501: }
502: }
503: }
|