summaryrefslogblamecommitdiff
path: root/sources/ch/epfl/lamp/util/SourceFile.java
blob: 44dbf82506d89df920045a32b9a8fc95bc410fad (plain) (tree)
























































































































































































































                                                                               
/*     ____ ____  ____ ____  ______                                     *\
**    / __// __ \/ __// __ \/ ____/    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 = "<<unknown source file>>";
        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;
    }

    //########################################################################
}