001: /**************************************************************************/
002: /* N I C E */
003: /* A high-level object-oriented research language */
004: /* (c) Daniel Bonniot 2003 */
005: /* */
006: /* This program is free software; you can redistribute it and/or modify */
007: /* it under the terms of the GNU General Public License as published by */
008: /* the Free Software Foundation; either version 2 of the License, or */
009: /* (at your option) any later version. */
010: /* */
011: /**************************************************************************/package gnu.bytecode;
012:
013: import java.util.*;
014:
015: /**
016: A mapping from source files to the bytecode line numbers.
017: The format is specified by the JSR 45.
018:
019: @author Daniel Bonniot (bonniot@users.sourceforge.net)
020: */
021:
022: class SourceMap extends Attribute {
023: SourceMap(ClassType classfile) {
024: super ("SourceDebugExtension");
025: buffer = new StringBuffer("SMAP\n");
026: buffer.append(classfile.getName()).append('\n');
027: buffer.append("Default\n*S Default\n*F\n");
028: fileMap = new HashMap();
029: }
030:
031: private StringBuffer buffer;
032: private StringBuffer lines = new StringBuffer("*L\n");
033:
034: private static final String trailer = "*E\n";
035:
036: public int getLength() {
037: return getBytes().length;
038: }
039:
040: private byte[] getBytes() {
041: // write the last part.
042: writeCurrent();
043: currentFile = null;
044:
045: try {
046: String value = buffer.toString() + lines.toString()
047: + trailer;
048: return value.getBytes("UTF-8");
049: } catch (java.io.UnsupportedEncodingException e) {
050: // Should never happen, UTF-8 is standard.
051: throw new Error(e.toString());
052: }
053: }
054:
055: public void write(java.io.DataOutputStream out)
056: throws java.io.IOException {
057: byte[] bytes = getBytes();
058: out.write(bytes, 0, bytes.length);
059: }
060:
061: /*
062: We work by remembering the last file used, and add a new File
063: entry each time we change files. However, we keep the first line
064: number, so that each source line from the same file is relative to
065: that base. This helps keeping the output line number low.
066: */
067:
068: private String currentFile = null;
069: private int lastFileNumber = 0;
070: private int firstLine = -1;
071: private int lastLine = -1;
072: private int outputBase = 1;
073: private Map fileMap;
074:
075: /**
076: Return a line number which is unique in this source map,
077: and which can be translated back into the given file and source
078: line number.
079: */
080: int translate(String file, int line) {
081: // We cannot do anything without the file name.
082: if (file == null)
083: return -1;
084:
085: // Truncate the content to keep it under 65535, because of a JVM bug.
086: if (buffer.length() + lines.length() > 65000)
087: return -1;
088:
089: if (!file.equals(currentFile) || line < firstLine) {
090: writeCurrent();
091: currentFile = file;
092: firstLine = line;
093: }
094:
095: lastLine = line;
096: return outputBase + line - firstLine;
097: }
098:
099: private void writeCurrent() {
100: if (currentFile != null) {
101: int fileNumber = writeFile(currentFile);
102: int len = lastLine - firstLine + 1;
103: lines.append(
104: "" + firstLine + '#' + fileNumber + ',' + len + ':'
105: + outputBase).append('\n');
106:
107: outputBase += len;
108: }
109: }
110:
111: private int writeFile(String file) {
112: Integer fileNumber = (Integer) fileMap.get(file);
113: if (fileNumber == null) {
114: lastFileNumber++;
115: fileNumber = new Integer(lastFileNumber);
116: fileMap.put(file, fileNumber);
117:
118: buffer.append(lastFileNumber).append(' ').append(file)
119: .append('\n');
120: }
121:
122: return fileNumber.intValue();
123: }
124:
125: }
|