Skip to content

Commit

Permalink
c.c.io provides input-stream and output-stream for byte-oriented I/O
Browse files Browse the repository at this point in the history
clojure.contrib.io already supports character-oriented I/O through the
multi-methods reader and writer.  This patch adds support for
byte-oriented I/O by providing the multi-methods input-stream and
output-stream.

* input-stream knows how to open InputStreams for reading bytes.
* reader has been refactored to build on input-stream.

* output-stream knows how to open OutputStreams for writing bytes.
* writer has been refactored to build on output-stream (where sensible)

  By recognizing that output-stream will throw exceptions for us if it's
  unable to open the underlying resource, we were able to use the
  :default method to cover URL, URI and Socket.

  The String writer has not been touched. (Writing it in terms of
  output-stream would have made it longer and more complex.)

* *append-to-writer* has been renamed to *append* for use with
  output-streams without souding foolish.

  This is a breaking change for clients that bind *append-to-writer*
  instead of calling append-writer or append-split as recommended in
  the docsting.

* Like writer, output-stream consults the current binding of *append*.

* append-output-stream is analagous to append-writer.

  Like append-writer, the implementation looks very general, but in
  reality it only works for files.

Signed-off-by: Stuart Halloway <stu@thinkrelevance.com>
  • Loading branch information
bpsm authored and stuarthalloway committed Apr 15, 2010
1 parent b52f0b6 commit 9cd7b15
Showing 1 changed file with 122 additions and 49 deletions.
171 changes: 122 additions & 49 deletions src/main/clojure/clojure/contrib/io.clj
Expand Up @@ -63,7 +63,8 @@
BufferedReader File PrintWriter OutputStream
OutputStreamWriter BufferedWriter Writer
FileInputStream FileOutputStream ByteArrayOutputStream
StringReader ByteArrayInputStream)
StringReader ByteArrayInputStream
BufferedInputStream BufferedOutputStream)
(java.net URI URL MalformedURLException Socket)))


Expand Down Expand Up @@ -98,55 +99,145 @@
(File. s)))


(defmulti #^{:tag BufferedInputStream
:doc "Attempts to coerce its argument into an open
java.io.BufferedInputStream. Argument may be an instance of
BufferedInputStream, InputStream, File, URI, URL, Socket, or String.
If argument is a String, it tries to resolve it first as a URI, then
as a local file name. URIs with a 'file' protocol are converted to
local file names. If this fails, a final attempt is made to resolve
the string as a resource on the CLASSPATH.
Should be used inside with-open to ensure the InputStream is properly
closed."
:arglists '([x])}
input-stream class)

(defmethod input-stream BufferedInputStream [x]
x)

(defmethod input-stream InputStream [x]
(BufferedInputStream. x))

(defmethod input-stream File [#^File x]
(input-stream (FileInputStream. x)))

(defmethod input-stream URL [#^URL x]
(input-stream (if (= "file" (.getProtocol x))
(FileInputStream. (.getPath x))
(.openStream x))))

(defmethod input-stream URI [#^URI x]
(input-stream (.toURL x)))

(defmethod input-stream String [#^String x]
(try (let [url (URL. x)]
(input-stream url))
(catch MalformedURLException e
(input-stream (File. x)))))

(defmethod input-stream Socket [#^Socket x]
(input-stream (.getInputStream x)))

(defmethod input-stream :default [x]
(throw (Exception. (str "Cannot open " (pr-str x) " as an InputStream."))))


(defmulti #^{:tag BufferedReader
:doc "Attempts to coerce its argument into an open
java.io.BufferedReader. Argument may be an instance of Reader,
BufferedReader, InputStream, File, URI, URL, Socket, or String.
If argument is a String, it tries to resolve it first as a URI, then
as a local file name. URIs with a 'file' protocol are converted to
local file names. Uses *default-encoding* as the text encoding.
local file names. If this fails, a final attempt is made to resolve
the string as a resource on the CLASSPATH.
Uses *default-encoding* as the text encoding.
Should be used inside with-open to ensure the Reader is properly
closed."
:arglists '([x])}
reader class)

(defmethod reader BufferedReader [x]
x)

(defmethod reader Reader [x]
(BufferedReader. x))

(defmethod reader InputStream [#^InputStream x]
(BufferedReader. (InputStreamReader. x *default-encoding*)))
(reader (InputStreamReader. x *default-encoding*)))

(defmethod reader File [#^File x]
(reader (FileInputStream. x)))
(defmethod reader :default [x]
; input-stream throws if it can't hanlde x.
(reader (input-stream x)))

(defmethod reader URL [#^URL x]
(reader (if (= "file" (.getProtocol x))
(FileInputStream. (.getPath x))
(.openStream x))))
(def
#^{:doc "If true, writer, output-stream and spit will open files in append mode.
Defaults to false. Instead of binding this var directly, use append-writer,
append-output-stream or append-spit."
:tag "java.lang.Boolean"}
*append* false)

(defmethod reader URI [#^URI x]
(reader (.toURL x)))
(defn- assert-not-appending []
(when *append*
(throw (Exception. "Cannot change an open stream to append mode."))))

(defmethod reader String [#^String x]
(try (let [url (URL. x)]
(reader url))
(catch MalformedURLException e
(reader (File. x)))))
(defmulti #^{:tag OutputStream
:doc "Attempts to coerce its argument into an open
java.io.OutputStream or java.io.BufferedOutputStream. Argument may
be an instance of OutputStream, File, URI, URL, Socket, or String.
(defmethod reader Socket [#^Socket x]
(reader (.getInputStream x)))
If argument is a String, it tries to resolve it first as a URI, then
as a local file name. URIs with a 'file' protocol are converted to
local file names.
(defmethod reader :default [x]
(throw (Exception. (str "Cannot open " (pr-str x) " as a reader."))))
Should be used inside with-open to ensure the OutputStream is
properly closed."
:arglists '([x])}
output-stream class)

(defmethod output-stream BufferedOutputStream [#^BufferedOutputStream x]
(assert-not-appending)
x)

(def
#^{:doc "If true, writer and spit will open files in append mode.
Defaults to false. Use append-writer or append-spit."
:tag "java.lang.Boolean"}
*append-to-writer* false)
(defmethod output-stream OutputStream [#^OutputStream x]
(assert-not-appending)
(BufferedOutputStream. x))

(defmethod output-stream File [#^File x]
(let [stream (FileOutputStream. x *append*)]
(binding [*append* false]
(output-stream stream))))

(defmethod output-stream URL [#^URL x]
(if (= "file" (.getProtocol x))
(output-stream (File. (.getPath x)))
(throw (Exception. (str "Can not write to non-file URL <" x ">")))))

(defmethod output-stream URI [#^URI x]
(output-stream (.toURL x)))

(defmethod output-stream String [#^String x]
(try (let [url (URL. x)]
(output-stream url))
(catch MalformedURLException err
(output-stream (File. x)))))

(defmethod output-stream Socket [#^Socket x]
(output-stream (.getOutputStream x)))

(defmethod output-stream :default [x]
(throw (Exception. (str "Cannot open <" (pr-str x) "> as an output stream."))))

(defn append-output-stream
"Like output-stream but opens file for appending. Does not work on streams
that are already open."
[x]
(binding [*append* true]
(output-stream x)))


(defmulti #^{:tag PrintWriter
Expand All @@ -164,10 +255,6 @@
:arglists '([x])}
writer class)

(defn- assert-not-appending []
(when *append-to-writer*
(throw (Exception. "Cannot change an open stream to append mode."))))

(defmethod writer PrintWriter [x]
(assert-not-appending)
x)
Expand All @@ -179,45 +266,31 @@
(defmethod writer Writer [x]
(assert-not-appending)
;; Writer includes sub-classes such as FileWriter
(PrintWriter. (BufferedWriter. x)))
(writer (BufferedWriter. x)))

(defmethod writer OutputStream [#^OutputStream x]
(assert-not-appending)
(PrintWriter.
(BufferedWriter.
(OutputStreamWriter. x *default-encoding*))))
(writer (OutputStreamWriter. x *default-encoding*)))

(defmethod writer File [#^File x]
(let [stream (FileOutputStream. x *append-to-writer*)]
(binding [*append-to-writer* false]
(let [stream (FileOutputStream. x *append*)]
(binding [*append* false]
(writer stream))))

(defmethod writer URL [#^URL x]
(if (= "file" (.getProtocol x))
(writer (File. (.getPath x)))
(throw (Exception. (str "Cannot write to non-file URL <" x ">")))))

(defmethod writer URI [#^URI x]
(writer (.toURL x)))

(defmethod writer String [#^String x]
(try (let [url (URL. x)]
(writer url))
(catch MalformedURLException err
(writer (File. x)))))

(defmethod writer Socket [#^Socket x]
(writer (.getOutputStream x)))

(defmethod writer :default [x]
(throw (Exception. (str "Cannot open <" (pr-str x) "> as a writer."))))

(writer (output-stream x)))

(defn append-writer
"Like writer but opens file for appending. Does not work on streams
that are already open."
[x]
(binding [*append-to-writer* true]
(binding [*append* true]
(writer x)))


Expand Down

0 comments on commit 9cd7b15

Please sign in to comment.