From 9cd7b155149c6e20b799528c6d5bf2f0e553e9f3 Mon Sep 17 00:00:00 2001 From: Ben Smith-Mannschott Date: Tue, 26 Jan 2010 21:23:05 +0100 Subject: 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 --- src/main/clojure/clojure/contrib/io.clj | 171 +++++++++++++++++++++++--------- 1 file changed, 122 insertions(+), 49 deletions(-) diff --git a/src/main/clojure/clojure/contrib/io.clj b/src/main/clojure/clojure/contrib/io.clj index 4d03e9d1..7101947c 100644 --- a/src/main/clojure/clojure/contrib/io.clj +++ b/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,6 +99,51 @@ (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, @@ -105,48 +151,93 @@ 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))) -- cgit v1.2.3-18-g5258