/* * Bytecode Analysis Framework * Copyright (C) 2003,2004 University of Maryland * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package edu.umd.cs.findbugs.ba; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; /** * Cached data for a source file. Contains a map of line numbers to byte * offsets, for quick searching of source lines. * * @author David Hovemeyer * @see SourceFinder */ public class SourceFile { private static int intValueOf(byte b) { return b & 0xff; } /** * Helper object to build map of line number to byte offset for a source * file. */ private static class LineNumberMapBuilder { private SourceFile sourceFile; private int offset; private int lastSeen; public LineNumberMapBuilder(SourceFile sourceFile) { this.sourceFile = sourceFile; this.offset = 0; this.lastSeen = -1; } public void addData(byte[] data, int len) { for (int i = 0; i < len; ++i) { int ch = intValueOf(data[i]); // if (ch < 0) throw new IllegalStateException(); add(ch); } } public void eof() { add(-1); } private void add(int ch) { switch (ch) { case '\n': sourceFile.addLineOffset(offset + 1); break; case '\r': // Need to see next character to know if it's a // line terminator. break; default: if (lastSeen == '\r') { // We consider a bare CR to be an end of line // if it is not followed by a new line. // Mac OS has historically used a bare CR as // its line terminator. sourceFile.addLineOffset(offset); } } lastSeen = ch; ++offset; } } private static final int DEFAULT_SIZE = 100; private SourceFileDataSource dataSource; private byte[] data; private int[] lineNumberMap; private int numLines; /** * Constructor. * * @param dataSource * the SourceFileDataSource object which will provide the data of * the source file */ public SourceFile(SourceFileDataSource dataSource) { this.dataSource = dataSource; this.lineNumberMap = new int[DEFAULT_SIZE]; this.numLines = 0; } /** * Get the full path name of the source file (with directory). */ public String getFullFileName() { return dataSource.getFullFileName(); } /** * Get an InputStream on data. * * @return an InputStream on the data in the source file, starting from * given offset */ public InputStream getInputStream() throws IOException { loadFileData(); return new ByteArrayInputStream(data); } /** * Get an InputStream on data starting at given offset. * * @param offset * the start offset * @return an InputStream on the data in the source file, starting at the * given offset */ public InputStream getInputStreamFromOffset(int offset) throws IOException { loadFileData(); return new ByteArrayInputStream(data, offset, data.length - offset); } /** * Add a source line byte offset. This method should be called for each line * in the source file, in order. * * @param offset * the byte offset of the next source line */ public void addLineOffset(int offset) { if (numLines >= lineNumberMap.length) { // Grow the line number map. int capacity = lineNumberMap.length * 2; int[] newLineNumberMap = new int[capacity]; System.arraycopy(lineNumberMap, 0, newLineNumberMap, 0, lineNumberMap.length); lineNumberMap = newLineNumberMap; } lineNumberMap[numLines++] = offset; } /** * Get the byte offset in the data for a source line. Note that lines are * considered to be zero-index, so the first line in the file is numbered * zero. * * @param line * the line number * @return the byte offset in the file's data for the line, or -1 if the * line is not valid */ public int getLineOffset(int line) { try { loadFileData(); } catch (IOException e) { System.err.println("SourceFile.getLineOffset: " + e.getMessage()); return -1; } if (line < 0 || line >= numLines) return -1; return lineNumberMap[line]; } private synchronized void loadFileData() throws IOException { if (data != null) return; InputStream in = null; try { in = dataSource.open(); ByteArrayOutputStream out = new ByteArrayOutputStream(); addLineOffset(0); // Line 0 starts at offset 0 LineNumberMapBuilder mapBuilder = new LineNumberMapBuilder(this); // Copy all of the data from the file into the byte array output // stream byte[] buf = new byte[1024]; int n; while ((n = in.read(buf)) >= 0) { mapBuilder.addData(buf, n); out.write(buf, 0, n); } mapBuilder.eof(); setData(out.toByteArray()); } finally { if (in != null) in.close(); } } /** * Set the source file data. * * @param data * the data */ private void setData(byte[] data) { this.data = data; } public long getLastModified() { return dataSource.getLastModified(); } } // vim:ts=4