001: /*
002: * Bytecode Analysis Framework
003: * Copyright (C) 2003,2004 University of Maryland
004: *
005: * This library is free software; you can redistribute it and/or
006: * modify it under the terms of the GNU Lesser General Public
007: * License as published by the Free Software Foundation; either
008: * version 2.1 of the License, or (at your option) any later version.
009: *
010: * This library is distributed in the hope that it will be useful,
011: * but WITHOUT ANY WARRANTY; without even the implied warranty of
012: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: * Lesser General Public License for more details.
014: *
015: * You should have received a copy of the GNU Lesser General Public
016: * License along with this library; if not, write to the Free Software
017: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: */
019:
020: package edu.umd.cs.findbugs.ba;
021:
022: import java.io.ByteArrayInputStream;
023: import java.io.ByteArrayOutputStream;
024: import java.io.IOException;
025: import java.io.InputStream;
026:
027: /**
028: * Cached data for a source file.
029: * Contains a map of line numbers to byte offsets, for quick
030: * searching of source lines.
031: *
032: * @author David Hovemeyer
033: * @see SourceFinder
034: */
035: public class SourceFile {
036: private static int intValueOf(byte b) {
037: return b & 0xff;
038: }
039:
040: /**
041: * Helper object to build map of line number to byte offset
042: * for a source file.
043: */
044: private static class LineNumberMapBuilder {
045: private SourceFile sourceFile;
046: private int offset;
047: private int lastSeen;
048:
049: public LineNumberMapBuilder(SourceFile sourceFile) {
050: this .sourceFile = sourceFile;
051: this .offset = 0;
052: this .lastSeen = -1;
053: }
054:
055: public void addData(byte[] data, int len) {
056: for (int i = 0; i < len; ++i) {
057: int ch = intValueOf(data[i]);
058: //if (ch < 0) throw new IllegalStateException();
059: add(ch);
060: }
061: }
062:
063: public void eof() {
064: add(-1);
065: }
066:
067: private void add(int ch) {
068: switch (ch) {
069: case '\n':
070: sourceFile.addLineOffset(offset + 1);
071: break;
072: case '\r':
073: // Need to see next character to know if it's a
074: // line terminator.
075: break;
076: default:
077: if (lastSeen == '\r') {
078: // We consider a bare CR to be an end of line
079: // if it is not followed by a new line.
080: // Mac OS has historically used a bare CR as
081: // its line terminator.
082: sourceFile.addLineOffset(offset);
083: }
084: }
085:
086: lastSeen = ch;
087: ++offset;
088: }
089: }
090:
091: private static final int DEFAULT_SIZE = 100;
092:
093: private SourceFileDataSource dataSource;
094: private byte[] data;
095: private int[] lineNumberMap;
096: private int numLines;
097:
098: /**
099: * Constructor.
100: *
101: * @param dataSource the SourceFileDataSource object which will
102: * provide the data of the source file
103: */
104: public SourceFile(SourceFileDataSource dataSource) {
105: this .dataSource = dataSource;
106: this .lineNumberMap = new int[DEFAULT_SIZE];
107: this .numLines = 0;
108: }
109:
110: /**
111: * Get the full path name of the source file (with directory).
112: */
113: public String getFullFileName() {
114: return dataSource.getFullFileName();
115: }
116:
117: /**
118: * Get an InputStream on data.
119: *
120: * @return an InputStream on the data in the source file,
121: * starting from given offset
122: */
123: public InputStream getInputStream() throws IOException {
124: loadFileData();
125: return new ByteArrayInputStream(data);
126: }
127:
128: /**
129: * Get an InputStream on data starting at given offset.
130: *
131: * @param offset the start offset
132: * @return an InputStream on the data in the source file,
133: * starting at the given offset
134: */
135: public InputStream getInputStreamFromOffset(int offset)
136: throws IOException {
137: loadFileData();
138: return new ByteArrayInputStream(data, offset, data.length
139: - offset);
140: }
141:
142: /**
143: * Add a source line byte offset.
144: * This method should be called for each line in the source file,
145: * in order.
146: *
147: * @param offset the byte offset of the next source line
148: */
149: public void addLineOffset(int offset) {
150: if (numLines >= lineNumberMap.length) {
151: // Grow the line number map.
152: int capacity = lineNumberMap.length * 2;
153: int[] newLineNumberMap = new int[capacity];
154: System.arraycopy(lineNumberMap, 0, newLineNumberMap, 0,
155: lineNumberMap.length);
156: lineNumberMap = newLineNumberMap;
157: }
158:
159: lineNumberMap[numLines++] = offset;
160: }
161:
162: /**
163: * Get the byte offset in the data for a source line.
164: * Note that lines are considered to be zero-index, so the first
165: * line in the file is numbered zero.
166: *
167: * @param line the line number
168: * @return the byte offset in the file's data for the line,
169: * or -1 if the line is not valid
170: */
171: public int getLineOffset(int line) {
172: if (line < 0 || line >= numLines)
173: return -1;
174: return lineNumberMap[line];
175: }
176:
177: private synchronized void loadFileData() throws IOException {
178: if (data != null)
179: return;
180:
181: InputStream in = null;
182:
183: try {
184: in = dataSource.open();
185: ByteArrayOutputStream out = new ByteArrayOutputStream();
186:
187: addLineOffset(0); // Line 0 starts at offset 0
188: LineNumberMapBuilder mapBuilder = new LineNumberMapBuilder(
189: this );
190:
191: // Copy all of the data from the file into the byte array output stream
192: byte[] buf = new byte[1024];
193: int n;
194: while ((n = in.read(buf)) >= 0) {
195: mapBuilder.addData(buf, n);
196: out.write(buf, 0, n);
197: }
198: mapBuilder.eof();
199:
200: setData(out.toByteArray());
201: } finally {
202: if (in != null)
203: in.close();
204: }
205:
206: }
207:
208: /**
209: * Set the source file data.
210: *
211: * @param data the data
212: */
213: private void setData(byte[] data) {
214: this .data = data;
215: }
216: }
217:
218: // vim:ts=4
|