diff options
-rw-r--r-- | src/library/scala/collection/immutable/PagedSeq.scala | 2 | ||||
-rw-r--r-- | src/library/scala/io/BufferedSource.scala | 17 | ||||
-rw-r--r-- | src/library/scala/io/File.scala | 26 | ||||
-rw-r--r-- | src/library/scala/io/Source.scala | 243 | ||||
-rw-r--r-- | src/library/scala/xml/parsing/ExternalSources.scala | 57 | ||||
-rw-r--r-- | test/files/jvm/unittest_io.scala | 4 |
6 files changed, 175 insertions, 174 deletions
diff --git a/src/library/scala/collection/immutable/PagedSeq.scala b/src/library/scala/collection/immutable/PagedSeq.scala index 3b839fe678..51bcfebf52 100644 --- a/src/library/scala/collection/immutable/PagedSeq.scala +++ b/src/library/scala/collection/immutable/PagedSeq.scala @@ -96,7 +96,7 @@ object PagedSeq { /** Constructs a character sequence from a scala.io.Source value */ def fromSource(source: io.Source) = - fromLines(source.getLines) + fromLines(source.getLines()) } diff --git a/src/library/scala/io/BufferedSource.scala b/src/library/scala/io/BufferedSource.scala index 6727b27fb7..57cfac2bfa 100644 --- a/src/library/scala/io/BufferedSource.scala +++ b/src/library/scala/io/BufferedSource.scala @@ -23,23 +23,20 @@ object BufferedSource * * @param inputStream the input stream from which to read * @param bufferSize buffer size (defaults to Source.DefaultBufSize) - * @param reset a () => Source which resets the stream (defaults to Source.NoReset) + * @param reset a () => Source which resets the stream (if unset, reset() will throw an Exception) * @param codec (implicit) a scala.io.Codec specifying behavior (defaults to Codec.default) * @return the buffered source */ def fromInputStream( inputStream: InputStream, bufferSize: Int = DefaultBufSize, - reset: () => Source = null + reset: () => Source = null, + close: () => Unit = null )(implicit codec: Codec = Codec.default) = { - if (reset == null) new BufferedSource(inputStream, bufferSize, codec) - else { - def _reset = reset - new BufferedSource(inputStream, bufferSize, codec) { - override def reset = _reset() - } - } + new BufferedSource(inputStream, bufferSize, codec) . + withReset (reset) . + withClose (close) } } @@ -74,7 +71,5 @@ extends Source c } } - def close: Unit = reader.close - def reset(): Source = NoReset() } diff --git a/src/library/scala/io/File.scala b/src/library/scala/io/File.scala index 593bbdc6d6..e6d3118286 100644 --- a/src/library/scala/io/File.scala +++ b/src/library/scala/io/File.scala @@ -10,7 +10,8 @@ package scala.io -import java.io.{ FileInputStream, FileOutputStream, File => JFile } +import java.io.{ FileInputStream, FileOutputStream, BufferedWriter, OutputStreamWriter, File => JFile } +import collection.Traversable object File { @@ -37,6 +38,10 @@ class File(val file: JFile) extends collection.Iterable[File] if (file.isDirectory) file.listFiles.iterator map (x => new File(x)) else Iterator.empty + /** Convenience function for iterating over the lines in the file. + */ + def lines: Iterator[String] = toSource.getLines() + /** Deletes the file or directory recursively. Returns false if it failed. * Use with caution! */ @@ -55,6 +60,25 @@ class File(val file: JFile) extends collection.Iterable[File] /** Obtains a OutputStream. */ def outputStream(append: Boolean = false) = new FileOutputStream(file, append) + /** Obtains an OutputStreamWriter wrapped around a FileOutputStream. + * This should behave like a less broken version of java.io.FileWriter, + * in that unlike the java version you can specify the encoding. + */ + def writer(append: Boolean = false)(implicit codec: Codec = Codec.default) = + new OutputStreamWriter(outputStream(append), codec.charSet) + + /** Wraps a BufferedWriter around the result of writer(). + */ + def bufferedWriter(append: Boolean = false)(implicit codec: Codec = Codec.default) = + new BufferedWriter(writer(append)(codec)) + + /** Writes all the Strings in the given iterator to the file. */ + def writeAll(xs: Traversable[String], append: Boolean = false)(implicit codec: Codec = Codec.default): Unit = { + val out = bufferedWriter(append)(codec) + xs foreach (out write _) + out close + } + /** Attempts to return the file extension. */ def extension = file.getName match { case extensionRegex(x) => Some(x) diff --git a/src/library/scala/io/Source.scala b/src/library/scala/io/Source.scala index 1d362cd285..4803842bbb 100644 --- a/src/library/scala/io/Source.scala +++ b/src/library/scala/io/Source.scala @@ -22,16 +22,19 @@ import java.net.{ URI, URL } */ object Source { val DefaultBufSize = 2048 - val NoReset: () => Source = () => throw new UnsupportedOperationException() /** Creates a <code>Source</code> from System.in. */ def stdin = fromInputStream(System.in) + /** Creates a <code>Source</code> from an Iterable. + * + * @param iterable the Iterable + * @return the <code>Source</code> instance. + */ def fromIterable(iterable: Iterable[Char]): Source = new Source { - def reset() = fromIterable(iterable) val iter = iterable.iterator - } + } withReset(() => fromIterable(iterable)) /** Creates a <code>Source</code> instance from a single character. * @@ -79,18 +82,13 @@ object Source { */ def fromFile(file: JFile, bufferSize: Int = DefaultBufSize)(implicit codec: Codec = Codec.default): Source = { val inputStream = new FileInputStream(file) - setFileDescriptor(file, - BufferedSource.fromInputStream(inputStream, bufferSize, () => fromFile(file, bufferSize)(codec))) - } - /** 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: JFile, source: Source): Source = { - source.descr = "file:" + file.getAbsolutePath - source + BufferedSource.fromInputStream( + inputStream, + bufferSize, + () => fromFile(file, bufferSize)(codec), + () => inputStream.close() + ) withDescription ("file:" + file.getAbsolutePath) } /** same as fromInputStream(url.openStream(), enc) @@ -101,127 +99,128 @@ object Source { /** same as BufferedSource.fromInputStream(is) */ def fromInputStream(inputStream: InputStream)(implicit codec: Codec = Codec.default): Source = - BufferedSource.fromInputStream(inputStream, DefaultBufSize, () => fromInputStream(inputStream)(codec)) + BufferedSource.fromInputStream( + inputStream, + DefaultBufSize, + () => fromInputStream(inputStream)(codec), + () => inputStream.close() + ) } + /** The class <code>Source</code> implements an iterable representation - * of source files. Calling method <code>reset</code> returns an identical, - * resetted source. + * of source data. Calling method <code>reset</code> returns an identical, + * resetted source, where possible. * * @author Burak Emir * @version 1.0 */ -abstract class Source extends Iterator[Char] { - - // ------ protected values - +abstract class Source extends Iterator[Char] +{ /** 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 + def getLine(line: Int): String = getLines() drop (line - 1) next + + class LineIterator(separator: String) extends Iterator[String] { + require(separator.length == 1 || separator.length == 2, "Line separator may be 1 or 2 characters only.") + lazy val iter: BufferedIterator[Char] = Source.this.iter.buffered + // For two character newline sequences like \r\n, we peek at + // the iterator head after seeing \r, and drop the \n if present. + val isNewline: Char => Boolean = { + val firstCh = separator(0) + if (separator.length == 1) (_ == firstCh) + else (ch: Char) => (ch == firstCh) && iter.hasNext && { + val res = iter.head == separator(1) + if (res) { iter.next } // drop the second character + res + } } + private[this] val sb = new StringBuilder + + private def getc() = + if (!iter.hasNext) false + else { + val ch = iter.next + if (isNewline(ch)) false + else { + sb append ch + true + } + } - 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 hasNext = iter.hasNext 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 + sb.clear + while (getc()) { } + sb.toString } - def hasNext = iter.hasNext } + + /** returns an iterator who returns lines (NOT including newline character(s)). + * If no separator is given, the platform-specific value "line.separator" is used. + * a line ends in \r, \n, or \r\n. + */ + def getLines(separator: String = compat.Platform.EOL): Iterator[String] = + new LineIterator(separator) + /** Returns <code>true</code> 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 + /** Returns next character. */ - def next: Char = { - ch = iter.next - pos = Position.encode(cline, ccol) - ch match { - case '\n' => - ccol = 1 - cline += 1 - case '\t' => - ccol += tabinc - case _ => - ccol += 1 + def next: Char = positioner.next + + class Positioner { + /** the last character returned by next. */ + var ch: Char = _ + + /** position of last character returned by next */ + var pos = 0 + + /** current line and column */ + var cline = 1 + var ccol = 1 + + /** default col increment for tabs '\t', set to 4 initially */ + var tabinc = 4 + + def next: Char = { + ch = iter.next + pos = Position.encode(cline, ccol) + ch match { + case '\n' => + ccol = 1 + cline += 1 + case '\t' => + ccol += tabinc + case _ => + ccol += 1 + } + ch } - ch } + object NoPositioner extends Positioner { + override def next: Char = iter.next + } + def ch = positioner.ch + def pos = positioner.pos /** Reports an error message to the output stream <code>out</code>. * @@ -238,24 +237,17 @@ abstract class Source extends Iterator[Char] { report(pos, msg, out) } + private def spaces(n: Int) = List.fill(n)(' ').mkString /** * @param pos the source position (line/column) * @param msg the error message to report * @param out PrintStream to use */ 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) + val line = Position line pos + val col = Position column pos + + out println "%s:%d:%d: %s%s%s^".format(descr, line, col, msg, getLine(line), spaces(col - 1)) } /** @@ -272,6 +264,35 @@ abstract class Source extends Iterator[Char] { report(pos, "warning! " + msg, out) } + private[this] var resetFunction: () => Source = null + private[this] var closeFunction: () => Unit = null + private[this] var positioner: Positioner = new Positioner + + def withReset(f: () => Source): this.type = { + resetFunction = f + this + } + def withClose(f: () => Unit): this.type = { + closeFunction = f + this + } + def withDescription(text: String): this.type = { + descr = text + this + } + // we'd like to default to no positioning, but for now we break + // less by defaulting to status quo. + def withPositioning(on: Boolean): this.type = { + positioner = if (on) new Positioner else NoPositioner + this + } + + /** The close() method closes the underlying resource. */ + def close: Unit = + if (closeFunction != null) closeFunction() + /** The reset() method creates a fresh copy of this Source. */ - def reset(): Source + def reset(): Source = + if (resetFunction != null) resetFunction() + else throw new UnsupportedOperationException("Source's reset() method was not set.") } diff --git a/src/library/scala/xml/parsing/ExternalSources.scala b/src/library/scala/xml/parsing/ExternalSources.scala index 01bbcb356d..c3b5cb1f05 100644 --- a/src/library/scala/xml/parsing/ExternalSources.scala +++ b/src/library/scala/xml/parsing/ExternalSources.scala @@ -13,6 +13,7 @@ package scala.xml package parsing import java.net.URL +import java.io.File.separator import scala.io.Source @@ -20,46 +21,9 @@ import scala.io.Source * @author Burak Emir * @version 1.0 */ -trait ExternalSources { self: ExternalSources with MarkupParser with MarkupHandler => - - private def externalSourceFromURL(url: URL): Source = { - import java.io.{BufferedReader, InputStreamReader} - val in = - new BufferedReader( - new InputStreamReader( - url.openStream())) - - //@todo: replace this hack with proper Source implementation - - val str = new StringBuilder() - var inputLine: String = null - - //while (inputLine = in.readLine()) != null) { - while ({inputLine = in.readLine(); inputLine} ne null) { - // Console.println(inputLine) // DEBUG - str.append(inputLine) - str.append('\n') // readable output - } - in.close() - - class MyClass extends Source { - - def newIter = new Iterator[Char] { - var i = -1 - private val len = str.length-1 - def hasNext = i < len - def next = { i += 1; str.charAt(i) } - } - - val iter = newIter - - def reset: Source = new MyClass - - /*override var*/ descr = url.toExternalForm() - } - - new MyClass - } +trait ExternalSources +{ + self: ExternalSources with MarkupParser with MarkupHandler => /** ... * @@ -68,16 +32,13 @@ trait ExternalSources { self: ExternalSources with MarkupParser with MarkupHandl */ def externalSource(systemId: String): Source = { if (systemId startsWith "http:") - return externalSourceFromURL(new URL(systemId)) + return Source fromURL new URL(systemId) - var fileStr = input.descr + val fileStr: String = input.descr match { + case x if x startsWith "file:" => x drop 5 + case x => x take ((x lastIndexOf separator) + 1) + } - if (input.descr startsWith "file:") { - fileStr = input.descr.substring(5, input.descr.length) - } else - fileStr = fileStr.substring(0, - fileStr.lastIndexOf(java.io.File.separator)+1) Source.fromPath(fileStr + systemId)() } - } diff --git a/test/files/jvm/unittest_io.scala b/test/files/jvm/unittest_io.scala index f4f2e08c7c..9fbe0013a0 100644 --- a/test/files/jvm/unittest_io.scala +++ b/test/files/jvm/unittest_io.scala @@ -7,7 +7,7 @@ object Test extends TestConsoleMain { new ReadlinesTest ) - class ReadlinesTest extends TestCase("scala.io.Source method getLines") { + class ReadlinesTest extends TestCase("scala.io.Source method getLines()") { val src = Source.fromString(""" This is a file @@ -15,7 +15,7 @@ it is split on several lines. isn't it? """) - def runTest() = assertEquals("wrong number of lines",src.getLines.toList.length,5) // five new lines in there + def runTest() = assertEquals("wrong number of lines",src.getLines().toList.length,5) // five new lines in there //for(val line <- src.getLines) { // Console.print(line) //} |