Permalink
Browse files

c.c.io provides input-stream and output-stream for byte-oriented I/O

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...
1 parent b52f0b6 commit 9cd7b155149c6e20b799528c6d5bf2f0e553e9f3 @bpsm bpsm committed with stuarthalloway Jan 26, 2010
Showing with 122 additions and 49 deletions.
  1. +122 −49 src/main/clojure/clojure/contrib/io.clj
@@ -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)))
@@ -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
@@ -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)
@@ -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)))

0 comments on commit 9cd7b15

Please sign in to comment.