/* ____ ____ ____ ____ ______ *\ ** / __// __ \/ __// __ \/ ____/ SOcos COmpiles Scala ** ** __\_ \/ /_/ / /__/ /_/ /\_ \ (c) 2002, LAMP/EPFL ** ** /_____/\____/\___/\____/____/ ** \* */ // $Id$ package ch.epfl.lamp.util; import java.io.File; import java.io.InputStream; import java.io.FileInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.ArrayList; /** * This class represents a single source file. */ public class SourceFile { //######################################################################## // Public Constants /** Constants used for source parsing */ public static final byte LF = 0x0A; public static final byte FF = 0x0C; public static final byte CR = 0x0D; public static final byte SU = 0x1A; //######################################################################## // Public Functions /** Returns the source file with the given id. */ public static SourceFile fromId(int id) { if (id <= 0 || files.size() < id) return UNKNOWN; return (SourceFile)files.get(id - 1); } /** Releases all source file. */ public static void releaseAll() { files.clear(); } //######################################################################## // Private Variables /** The unknown source file */ private static final SourceFile UNKNOWN = new SourceFile(); /** The list containing all source files */ private static final ArrayList files = new ArrayList(); //######################################################################## // Private Fields /** The name of this source file */ private final String name; /** The content of source this file */ private final byte[] bytes; /** The id of this source file */ private final int id; /** The encoding of this source file or null if unspecified */ private String encoding; /** The position of the last line returned by getLine */ private int lineNumber = 0; private int lineStart = 0; private int lineLength = 0; private int nextIndex = 0; //######################################################################## // Private Constructors /** Initializes a new instance. */ private SourceFile() { this.name = "<>"; this.bytes = normalize(new byte[0]); this.id = 0; } //######################################################################## // Public Constructors /** Initializes a new instance. */ public SourceFile(String name) throws IOException { this(new File(name)); } /** Initializes a new instance. */ public SourceFile(File name) throws IOException { this(name.toString(), read(name)); } /** Initializes a new instance. */ public SourceFile(String name, InputStream input) throws IOException { this(name, read(name, input)); } /** Initializes a new instance. */ public SourceFile(String name, byte[] bytes) { this.name = name; this.bytes = normalize(bytes); this.id = files.size() + 1; files.add(this); } //######################################################################## // Public Methods /** Returns the name of this source file. */ public String name() { return name; } /** Returns the content of this source file. */ public byte[] bytes() { return bytes; } /** Returns the id of this source file. */ public int id() { return id; } /** Sets the encoding of the file. */ public void setEncoding(String encoding) { this.encoding = encoding; } /** Returns the content of the given line. */ public String getLine(int line) { int index = lineNumber <= line ? nextIndex : (lineNumber = 0); for (; index < bytes.length && lineNumber < line; lineNumber++) { lineStart = index; for (; index < bytes.length; index++) { if (bytes[index] == CR) break; if (bytes[index] == LF) break; if (bytes[index] == FF) break; } lineLength = index - lineStart; if (index < bytes.length) index++; if (index < bytes.length) if (bytes[index - 1] == CR && bytes[index] == LF) index++; } nextIndex = index; try { return encoding != null ? new String(bytes, lineStart, lineLength, encoding) : new String(bytes, lineStart, lineLength); } catch (UnsupportedEncodingException exception) { throw new Error(exception); // !!! use ApplicationError } } /** * Returns an instance of Position representing the given line and * column of this source file. */ public Position getPosition(int line, int column) { return new Position(this, line, column); } /** * Returns the integer encoding the position of the given line and * column of this source file. */ public int getEncodedPosition(int line, int column) { return Position.encode(this, line, column); } public String toString() { return name; } //######################################################################## // Private Methods /** Reads the file and returns its content as a byte array. */ private static byte[] read(File file) throws IOException { InputStream input = new FileInputStream(file); try { return read(file.toString(), input); } finally { input.close(); } } /** Reads the InputStream and returns its content as a byte array. */ private static byte[] read(String name, InputStream input) throws IOException { try { byte[] bytes = new byte[input.available() + 1]; if (input.read(bytes) != bytes.length - 1) throw new IOException(); bytes[bytes.length - 1] = SU; return bytes; } catch (IOException exception) { throw new IOException("cannot read '" + name + "'"); } } /** Ensures that the last byte of the array is SU. */ private static byte[] normalize(byte[] input) { if (input.length > 0 && input[input.length - 1] == SU) return input; byte[] bytes = new byte[input.length + 1]; System.arraycopy(input, 0, bytes, 0, input.length); bytes[input.length] = SU; return bytes; } //######################################################################## }