/* __ *\
** ________ ___ / / ___ Scala API **
** / __/ __// _ | / / / _ | (c) 2003-2008, LAMP/EPFL **
** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
** /____/\___/_/ |_/____/_/ | | **
** |/ **
\* */
// $Id$
package scala.io
import java.io.{BufferedInputStream, File, FileInputStream, InputStream,
PrintStream}
import java.nio.{ByteBuffer, CharBuffer}
import java.nio.charset.Charset
import java.net.{URI, URL}
/** This object provides convenience methods to create an iterable
* representation of a source file.
*
* @author Burak Emir
* @version 1.0, 19/08/2004
*/
object Source {
val DefaultBufSize = 2048
val NoReset: () => Source = () => throw new UnsupportedOperationException()
/** Creates a Source
instance from the given array of bytes,
* with empty description.
*
* @param bytes ...
* @return the created Source
instance.
*/
def fromBytes(bytes: Array[Byte]): Source =
fromString(new String(bytes))
/** Creates Source from array of bytes with given encoding, with
* empty description.
*
* @param bytes ...
* @param enc ...
* @return ...
*/
def fromBytes(bytes: Array[Byte], enc: String): Source =
fromString(new String(bytes, enc))
/** Creates a Source
instance from a single character.
*
* @param c ...
* @return the create Source
instance.
*/
def fromChar(c: Char): Source = {
val it = Iterator.single(c)
new Source {
def reset() = fromChar(c)
val iter = it
}
}
/** creates Source from array of characters, with empty description.
*
* @param chars ...
* @return ...
*/
def fromChars(chars: Array[Char]): Source = {
val it = chars.elements
new Source {
def reset() = fromChars(chars)
val iter = it
}
}
/** creates Source from string, with empty description.
*
* @param s ...
* @return ...
*/
def fromString(s: String): Source = {
val it = s.elements
new Source {
def reset() = fromString(s)
val iter = it
}
}
/** creates Source from file with given name, setting its description to
* filename.
*/
def fromFile(name: String): Source =
fromFile(name, util.Properties.encodingString)
/** creates Source from file with given name, using given encoding, setting
* its description to filename.
*/
def fromFile(name: String, enc: String): Source =
fromFile(new File(name), enc)
/** creates Source
from file with given file: URI
*/
def fromFile(uri: URI): Source =
fromFile(uri, util.Properties.encodingString)
/** creates Source from file with given file: URI
*/
def fromFile(uri: URI, enc: String): Source =
fromFile(new File(uri), enc)
/** creates Source from file, using default character encoding, setting its
* description to filename.
*/
def fromFile(file: File): Source =
fromFile(file, util.Properties.encodingString, Source.DefaultBufSize)
/** same as fromFile(file, enc, Source.DefaultBufSize)
*/
def fromFile(file: File, enc: String): Source =
fromFile(file, enc, Source.DefaultBufSize)
/** Creates Source from file
, using given character encoding,
* setting its description to filename. Input is buffered in a buffer of
* size bufferSize
.
*/
def fromFile(file: File, enc: String, bufferSize: Int): Source = {
val inpStream = new FileInputStream(file)
val size = if (bufferSize > 0) bufferSize else Source.DefaultBufSize
setFileDescriptor(file,
BufferedSource.fromInputStream(inpStream, enc, size, { () => fromFile(file, enc, size)}))
}
/** This method sets the descr property of the given source to a string of the form "file:"+path
* @param file the file whose path we want to describe
* @param s the source whose property we set
* @return s
*/
private def setFileDescriptor(file: File, s: Source): Source = {
s.descr = new StringBuilder("file:").append(file.getAbsolutePath()).toString();
s
}
/**
* @param s ...
* @return ...
* @deprecated use fromURL(s, enc)
*/
def fromURL(s: String): Source =
fromURL(new URL(s))
/** same as fromURL(new URL(s), enc)
*/
def fromURL(s: String, enc:String): Source =
fromURL(new URL(s), enc)
/**
* @param url ...
* @return ...
* @deprecated use fromURL(url, enc)
*/
def fromURL(url: URL): Source = {
val it = new Iterator[Char] {
var data: Int = _
def hasNext = {data != -1}
def next = {val x = data.asInstanceOf[Char]; data = bufIn.read(); x}
val in = url.openStream()
val bufIn = new BufferedInputStream(in)
data = bufIn.read()
}
val s = new Source {
def reset() = fromURL(url)
val iter = it
}
s.descr = url.toString()
s
}
/** same as fromInputStream(url.openStream(), enc)
*/
def fromURL(url: URL, enc:String): Source =
fromInputStream(url.openStream(), enc)
/** reads data from istream
into a byte array, and calls
* fromBytes
with given encoding enc
.
* If maxlen
is given, reads not more bytes than maxlen
;
* if maxlen
was not given, or was <= 0
, then
* whole istream
is read and closed afterwards.
*
* @param istream the input stream from which to read
* @param enc the encoding to apply to the bytes
* @param maxlen optionally, a positive int specifying maximum number of bytes to read
*/
@deprecated def fromInputStream(istream: InputStream, enc: String, maxlen: Option[Int]): Source = {
val limit = maxlen match { case Some(i) => i; case None => 0 }
val bi = new BufferedInputStream(istream, Source.DefaultBufSize)
val bytes = new collection.mutable.ArrayBuffer[Byte]()
var b = 0
var i = 0
while( {b = bi.read; i += 1; b} != -1 && (limit <= 0 || i < limit)) {
bytes += b.toByte;
}
if(limit <= 0) bi.close
fromBytes(bytes.toArray, enc)
}
/** same as BufferedSource.fromInputStream(is, enc, Source.DefaultBufSize)
*/
def fromInputStream(is: InputStream, enc: String): Source =
BufferedSource.fromInputStream(is, enc, Source.DefaultBufSize, { () => fromInputStream(is, enc) })
/** same as BufferedSource.fromInputStream(is, "utf-8", Source.DefaultBufSize) */
def fromInputStream(is: InputStream): Source =
BufferedSource.fromInputStream(is, "utf-8", Source.DefaultBufSize, { () => fromInputStream(is) })
}
/** The class Source
implements an iterable representation
* of source files. Calling method reset
returns an identical,
* resetted source.
*
* @author Burak Emir
* @version 1.0
*/
abstract class Source extends Iterator[Char] {
// ------ protected values
/** the actual iterator */
protected val iter: Iterator[Char]
protected var cline = 1
protected var ccol = 1
// ------ public values
/** position of last character returned by next*/
var pos = 0
/** the last character returned by next.
* the value before the first call to next is undefined.
*/
var ch: Char = _
/** description of this source, default empty */
var descr: String = ""
var nerrors = 0
var nwarnings = 0
/** default col increment for tabs '\t', set to 4 initially
*/
var tabinc = 4
//
// -- methods
//
/** convenience method, returns given line (not including newline)
* from Source.
*
* @param line the line index, first line is 1
* @return the character string of the specified line.
* @throws scala.compat.Platform.IllegalArgumentException
*
*/
def getLine(line: Int): String = { // faster than getLines.drop(line).next
// todo: should @throws scala.compat.Platform.IndexOutOfBoundsException
if (line < 1) throw new IllegalArgumentException(line.toString);
val buf = new StringBuilder()
val it = reset
var i = 0
while (it.hasNext && i < (line-1))
if ('\n' == it.next)
i += 1;
if (!it.hasNext) // this should not happen
throw new IllegalArgumentException(
"line " + line + " does not exist"
);
var ch = it.next
while (it.hasNext && '\n' != ch) {
buf append ch
ch = it.next
}
if ('\n' != ch)
buf append ch
val res = buf.toString()
buf setLength 0 // hopefully help collector to deallocate StringBuilder
res
}
/** returns an iterator who returns lines (including newline character).
* a line ends in \n.
*/
def getLines: Iterator[String] = new Iterator[String] {
val buf = new StringBuilder
def next = {
var ch = iter.next
while(ch != '\n' && iter.hasNext) {
buf append ch
ch = iter.next
}
buf.append(ch)
val res = buf.toString()
buf setLength 0 // clean things up for next call of "next"
res
}
def hasNext = iter.hasNext
}
/** Returns true
if this source has more characters.
*/
def hasNext = iter.hasNext
/** returns next character and has the following side-effects: updates
* position (ccol and cline) and assigns the character to ch
*/
def next = {
ch = iter.next
pos = Position.encode(cline,ccol)
ch match {
case '\n' =>
ccol = 1
cline += 1
case '\t' =>
ccol += tabinc
case _ =>
ccol += 1
}
ch
}
/** Reports an error message to console.
*
* @param pos ...
* @param msg the error message to report
*/
def reportError(pos: Int, msg: String) {
reportError(pos, msg, java.lang.System.out)
}
/** Reports an error message to the output stream out
.
*
* @param pos ...
* @param msg the error message to report
* @param out ...
*/
def reportError(pos: Int, msg: String, out: PrintStream) {
nerrors = nerrors + 1
report(pos, msg, out)
}
/**
* @param pos ...
* @param msg the error message to report
* @param out ...
*/
def report(pos: Int, msg: String, out: PrintStream) {
val buf = new StringBuilder
val line = Position.line(pos)
val col = Position.column(pos)
buf.append(descr + ":" + line + ":" + col + ": " + msg)
buf.append(getLine(line))
var i = 1
while (i < col) {
buf.append(' ')
i += 1
}
buf.append('^')
out.println(buf.toString)
}
/** Reports a warning message to java.lang.System.out
.
*
* @param pos ...
* @param msg the warning message to report
*/
def reportWarning(pos: Int, msg: String) {
reportWarning(pos, msg, java.lang.System.out)
}
/**
* @param pos ...
* @param msg the warning message to report
* @param out ...
*/
def reportWarning(pos: Int, msg: String, out: PrintStream) {
nwarnings = nwarnings + 1
report(pos, "warning! " + msg, out)
}
/** the actual reset method */
def reset(): Source
}