summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Phillips <paulp@improving.org>2009-08-16 17:13:27 +0000
committerPaul Phillips <paulp@improving.org>2009-08-16 17:13:27 +0000
commitf4d0095bf538cc90bcf465fd576207db7e264c87 (patch)
treedf02c4f97ec7cce8cb9507688cd555e4e8b04073
parent110b737f99c4e4850f9a0f861db6b9f831ae056e (diff)
downloadscala-f4d0095bf538cc90bcf465fd576207db7e264c87.tar.gz
scala-f4d0095bf538cc90bcf465fd576207db7e264c87.tar.bz2
scala-f4d0095bf538cc90bcf465fd576207db7e264c87.zip
A variety of work on scala.io.{ File, Source } ...
A variety of work on scala.io.{ File, Source } with changes including: getLines() now takes a line separator argument (defaults to platform line.separator) and drops the newlines. scala.io.File adds several convenience methods. The mechanisms for configuring Source are more consistent. This is not complete, performance issues remain to be investigated.
-rw-r--r--src/library/scala/collection/immutable/PagedSeq.scala2
-rw-r--r--src/library/scala/io/BufferedSource.scala17
-rw-r--r--src/library/scala/io/File.scala26
-rw-r--r--src/library/scala/io/Source.scala243
-rw-r--r--src/library/scala/xml/parsing/ExternalSources.scala57
-rw-r--r--test/files/jvm/unittest_io.scala4
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)
//}