rlm@10: ; Copyright (c) Rich Hickey. All rights reserved. rlm@10: ; The use and distribution terms for this software are covered by the rlm@10: ; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) rlm@10: ; which can be found in the file epl-v10.html at the root of this distribution. rlm@10: ; By using this software in any fashion, you are agreeing to be bound by rlm@10: ; the terms of this license. rlm@10: ; You must not remove this notice, or any other, from this software. rlm@10: rlm@10: (ns rlm@10: ^{:author "Stuart Sierra, Chas Emerick, Stuart Halloway", rlm@10: :doc "This file defines polymorphic I/O utility functions for Clojure."} rlm@10: clojure.java.io rlm@10: (:import rlm@10: (java.io Reader InputStream InputStreamReader PushbackReader rlm@10: BufferedReader File OutputStream rlm@10: OutputStreamWriter BufferedWriter Writer rlm@10: FileInputStream FileOutputStream ByteArrayOutputStream rlm@10: StringReader ByteArrayInputStream rlm@10: BufferedInputStream BufferedOutputStream rlm@10: CharArrayReader Closeable) rlm@10: (java.net URI URL MalformedURLException Socket))) rlm@10: rlm@10: (def rlm@10: ^{:doc "Type object for a Java primitive byte array." rlm@10: :private true rlm@10: } rlm@10: byte-array-type (class (make-array Byte/TYPE 0))) rlm@10: rlm@10: (def rlm@10: ^{:doc "Type object for a Java primitive char array." rlm@10: :private true} rlm@10: char-array-type (class (make-array Character/TYPE 0))) rlm@10: rlm@10: (defprotocol ^{:added "1.2"} Coercions rlm@10: "Coerce between various 'resource-namish' things." rlm@10: (^{:tag java.io.File, :added "1.2"} as-file [x] "Coerce argument to a file.") rlm@10: (^{:tag java.net.URL, :added "1.2"} as-url [x] "Coerce argument to a URL.")) rlm@10: rlm@10: (extend-protocol Coercions rlm@10: nil rlm@10: (as-file [_] nil) rlm@10: (as-url [_] nil) rlm@10: rlm@10: String rlm@10: (as-file [s] (File. s)) rlm@10: (as-url [s] (URL. s)) rlm@10: rlm@10: File rlm@10: (as-file [f] f) rlm@10: (as-url [f] (.toURL f)) rlm@10: rlm@10: URL rlm@10: (as-url [u] u) rlm@10: (as-file [u] rlm@10: (if (= "file" (.getProtocol u)) rlm@10: (as-file (.getPath u)) rlm@10: (throw (IllegalArgumentException. "Not a file: " u)))) rlm@10: rlm@10: URI rlm@10: (as-url [u] (.toURL u)) rlm@10: (as-file [u] (as-file (as-url u)))) rlm@10: rlm@10: (defprotocol ^{:added "1.2"} IOFactory rlm@10: "Factory functions that create ready-to-use, buffered versions of rlm@10: the various Java I/O stream types, on top of anything that can rlm@10: be unequivocally converted to the requested kind of stream. rlm@10: rlm@10: Common options include rlm@10: rlm@10: :append true to open stream in append mode rlm@10: :encoding string name of encoding to use, e.g. \"UTF-8\". rlm@10: rlm@10: Callers should generally prefer the higher level API provided by rlm@10: reader, writer, input-stream, and output-stream." rlm@10: (^{:added "1.2"} make-reader [x opts] "Creates a BufferedReader. See also IOFactory docs.") rlm@10: (^{:added "1.2"} make-writer [x opts] "Creates a BufferedWriter. See also IOFactory docs.") rlm@10: (^{:added "1.2"} make-input-stream [x opts] "Creates a BufferedInputStream. See also IOFactory docs.") rlm@10: (^{:added "1.2"} make-output-stream [x opts] "Creates a BufferedOutputStream. See also IOFactory docs.")) rlm@10: rlm@10: (defn ^Reader reader rlm@10: "Attempts to coerce its argument into an open java.io.Reader. rlm@10: Default implementations always return a java.io.BufferedReader. rlm@10: rlm@10: Default implementations are provided for Reader, BufferedReader, rlm@10: InputStream, File, URI, URL, Socket, byte arrays, character arrays, rlm@10: and String. rlm@10: rlm@10: If argument is a String, it tries to resolve it first as a URI, then rlm@10: as a local file name. URIs with a 'file' protocol are converted to rlm@10: local file names. rlm@10: rlm@10: Should be used inside with-open to ensure the Reader is properly rlm@10: closed." rlm@10: {:added "1.2"} rlm@10: [x & opts] rlm@10: (make-reader x (when opts (apply hash-map opts)))) rlm@10: rlm@10: (defn ^Writer writer rlm@10: "Attempts to coerce its argument into an open java.io.Writer. rlm@10: Default implementations always return a java.io.BufferedWriter. rlm@10: rlm@10: Default implementations are provided for Writer, BufferedWriter, rlm@10: OutputStream, File, URI, URL, Socket, and String. rlm@10: rlm@10: If the argument is a String, it tries to resolve it first as a URI, then rlm@10: as a local file name. URIs with a 'file' protocol are converted to rlm@10: local file names. rlm@10: rlm@10: Should be used inside with-open to ensure the Writer is properly rlm@10: closed." rlm@10: {:added "1.2"} rlm@10: [x & opts] rlm@10: (make-writer x (when opts (apply hash-map opts)))) rlm@10: rlm@10: (defn ^InputStream input-stream rlm@10: "Attempts to coerce its argument into an open java.io.InputStream. rlm@10: Default implementations always return a java.io.BufferedInputStream. rlm@10: rlm@10: Default implementations are defined for OutputStream, File, URI, URL, rlm@10: Socket, byte array, and String arguments. rlm@10: rlm@10: If the argument is a String, it tries to resolve it first as a URI, then rlm@10: as a local file name. URIs with a 'file' protocol are converted to rlm@10: local file names. rlm@10: rlm@10: Should be used inside with-open to ensure the InputStream is properly rlm@10: closed." rlm@10: {:added "1.2"} rlm@10: [x & opts] rlm@10: (make-input-stream x (when opts (apply hash-map opts)))) rlm@10: rlm@10: (defn ^OutputStream output-stream rlm@10: "Attempts to coerce its argument into an open java.io.OutputStream. rlm@10: Default implementations always return a java.io.BufferedOutputStream. rlm@10: rlm@10: Default implementations are defined for OutputStream, File, URI, URL, rlm@10: Socket, and String arguments. rlm@10: rlm@10: If the argument is a String, it tries to resolve it first as a URI, then rlm@10: as a local file name. URIs with a 'file' protocol are converted to rlm@10: local file names. rlm@10: rlm@10: Should be used inside with-open to ensure the OutputStream is rlm@10: properly closed." rlm@10: {:added "1.2"} rlm@10: [x & opts] rlm@10: (make-output-stream x (when opts (apply hash-map opts)))) rlm@10: rlm@10: (defn- ^Boolean append? [opts] rlm@10: (boolean (:append opts))) rlm@10: rlm@10: (defn- ^String encoding [opts] rlm@10: (or (:encoding opts) "UTF-8")) rlm@10: rlm@10: (defn- buffer-size [opts] rlm@10: (or (:buffer-size opts) 1024)) rlm@10: rlm@10: (def default-streams-impl rlm@10: {:make-reader (fn [x opts] (make-reader (make-input-stream x opts) opts)) rlm@10: :make-writer (fn [x opts] (make-writer (make-output-stream x opts) opts)) rlm@10: :make-input-stream (fn [x opts] rlm@10: (throw (IllegalArgumentException. rlm@10: (str "Cannot open <" (pr-str x) "> as an InputStream.")))) rlm@10: :make-output-stream (fn [x opts] rlm@10: (throw (IllegalArgumentException. rlm@10: (str "Cannot open <" (pr-str x) "> as an OutputStream."))))}) rlm@10: rlm@10: (defn- inputstream->reader rlm@10: [^InputStream is opts] rlm@10: (make-reader (InputStreamReader. is (encoding opts)) opts)) rlm@10: rlm@10: (defn- outputstream->writer rlm@10: [^OutputStream os opts] rlm@10: (make-writer (OutputStreamWriter. os (encoding opts)) opts)) rlm@10: rlm@10: (extend BufferedInputStream rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-input-stream (fn [x opts] x) rlm@10: :make-reader inputstream->reader)) rlm@10: rlm@10: (extend InputStream rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-input-stream (fn [x opts] (BufferedInputStream. x)) rlm@10: :make-reader inputstream->reader)) rlm@10: rlm@10: (extend Reader rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-reader (fn [x opts] (BufferedReader. x)))) rlm@10: rlm@10: (extend BufferedReader rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-reader (fn [x opts] x))) rlm@10: rlm@10: (extend Writer rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-writer (fn [x opts] (BufferedWriter. x)))) rlm@10: rlm@10: (extend BufferedWriter rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-writer (fn [x opts] x))) rlm@10: rlm@10: (extend OutputStream rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-output-stream (fn [x opts] (BufferedOutputStream. x)) rlm@10: :make-writer outputstream->writer)) rlm@10: rlm@10: (extend BufferedOutputStream rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-output-stream (fn [x opts] x) rlm@10: :make-writer outputstream->writer)) rlm@10: rlm@10: (extend File rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-input-stream (fn [^File x opts] (make-input-stream (FileInputStream. x) opts)) rlm@10: :make-output-stream (fn [^File x opts] (make-output-stream (FileOutputStream. x (append? opts)) opts)))) rlm@10: rlm@10: (extend URL rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-input-stream (fn [^URL x opts] rlm@10: (make-input-stream rlm@10: (if (= "file" (.getProtocol x)) rlm@10: (FileInputStream. (.getPath x)) rlm@10: (.openStream x)) opts)) rlm@10: :make-output-stream (fn [^URL x opts] rlm@10: (if (= "file" (.getProtocol x)) rlm@10: (make-output-stream (File. (.getPath x)) opts) rlm@10: (throw (IllegalArgumentException. (str "Can not write to non-file URL <" x ">"))))))) rlm@10: rlm@10: (extend URI rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-input-stream (fn [^URI x opts] (make-input-stream (.toURL x) opts)) rlm@10: :make-output-stream (fn [^URI x opts] (make-output-stream (.toURL x) opts)))) rlm@10: rlm@10: (extend String rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-input-stream (fn [^String x opts] rlm@10: (try rlm@10: (make-input-stream (URL. x) opts) rlm@10: (catch MalformedURLException e rlm@10: (make-input-stream (File. x) opts)))) rlm@10: :make-output-stream (fn [^String x opts] rlm@10: (try rlm@10: (make-output-stream (URL. x) opts) rlm@10: (catch MalformedURLException err rlm@10: (make-output-stream (File. x) opts)))))) rlm@10: rlm@10: (extend Socket rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-input-stream (fn [^Socket x opts] (make-input-stream (.getInputStream x) opts)) rlm@10: :make-output-stream (fn [^Socket x opts] (make-output-stream (.getOutputStream x) opts)))) rlm@10: rlm@10: (extend byte-array-type rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-input-stream (fn [x opts] (make-input-stream (ByteArrayInputStream. x) opts)))) rlm@10: rlm@10: (extend char-array-type rlm@10: IOFactory rlm@10: (assoc default-streams-impl rlm@10: :make-reader (fn [x opts] (make-reader (CharArrayReader. x) opts)))) rlm@10: rlm@10: (extend Object rlm@10: IOFactory rlm@10: default-streams-impl) rlm@10: rlm@10: (defmulti rlm@10: #^{:doc "Internal helper for copy" rlm@10: :private true rlm@10: :arglists '([input output opts])} rlm@10: do-copy rlm@10: (fn [input output opts] [(type input) (type output)])) rlm@10: rlm@10: (defmethod do-copy [InputStream OutputStream] [#^InputStream input #^OutputStream output opts] rlm@10: (let [buffer (make-array Byte/TYPE (buffer-size opts))] rlm@10: (loop [] rlm@10: (let [size (.read input buffer)] rlm@10: (when (pos? size) rlm@10: (do (.write output buffer 0 size) rlm@10: (recur))))))) rlm@10: rlm@10: (defmethod do-copy [InputStream Writer] [#^InputStream input #^Writer output opts] rlm@10: (let [#^"[B" buffer (make-array Byte/TYPE (buffer-size opts))] rlm@10: (loop [] rlm@10: (let [size (.read input buffer)] rlm@10: (when (pos? size) rlm@10: (let [chars (.toCharArray (String. buffer 0 size (encoding opts)))] rlm@10: (do (.write output chars) rlm@10: (recur)))))))) rlm@10: rlm@10: (defmethod do-copy [InputStream File] [#^InputStream input #^File output opts] rlm@10: (with-open [out (FileOutputStream. output)] rlm@10: (do-copy input out opts))) rlm@10: rlm@10: (defmethod do-copy [Reader OutputStream] [#^Reader input #^OutputStream output opts] rlm@10: (let [#^"[C" buffer (make-array Character/TYPE (buffer-size opts))] rlm@10: (loop [] rlm@10: (let [size (.read input buffer)] rlm@10: (when (pos? size) rlm@10: (let [bytes (.getBytes (String. buffer 0 size) (encoding opts))] rlm@10: (do (.write output bytes) rlm@10: (recur)))))))) rlm@10: rlm@10: (defmethod do-copy [Reader Writer] [#^Reader input #^Writer output opts] rlm@10: (let [#^"[C" buffer (make-array Character/TYPE (buffer-size opts))] rlm@10: (loop [] rlm@10: (let [size (.read input buffer)] rlm@10: (when (pos? size) rlm@10: (do (.write output buffer 0 size) rlm@10: (recur))))))) rlm@10: rlm@10: (defmethod do-copy [Reader File] [#^Reader input #^File output opts] rlm@10: (with-open [out (FileOutputStream. output)] rlm@10: (do-copy input out opts))) rlm@10: rlm@10: (defmethod do-copy [File OutputStream] [#^File input #^OutputStream output opts] rlm@10: (with-open [in (FileInputStream. input)] rlm@10: (do-copy in output opts))) rlm@10: rlm@10: (defmethod do-copy [File Writer] [#^File input #^Writer output opts] rlm@10: (with-open [in (FileInputStream. input)] rlm@10: (do-copy in output opts))) rlm@10: rlm@10: (defmethod do-copy [File File] [#^File input #^File output opts] rlm@10: (with-open [in (FileInputStream. input) rlm@10: out (FileOutputStream. output)] rlm@10: (do-copy in out opts))) rlm@10: rlm@10: (defmethod do-copy [String OutputStream] [#^String input #^OutputStream output opts] rlm@10: (do-copy (StringReader. input) output opts)) rlm@10: rlm@10: (defmethod do-copy [String Writer] [#^String input #^Writer output opts] rlm@10: (do-copy (StringReader. input) output opts)) rlm@10: rlm@10: (defmethod do-copy [String File] [#^String input #^File output opts] rlm@10: (do-copy (StringReader. input) output opts)) rlm@10: rlm@10: (defmethod do-copy [char-array-type OutputStream] [input #^OutputStream output opts] rlm@10: (do-copy (CharArrayReader. input) output opts)) rlm@10: rlm@10: (defmethod do-copy [char-array-type Writer] [input #^Writer output opts] rlm@10: (do-copy (CharArrayReader. input) output opts)) rlm@10: rlm@10: (defmethod do-copy [char-array-type File] [input #^File output opts] rlm@10: (do-copy (CharArrayReader. input) output opts)) rlm@10: rlm@10: (defmethod do-copy [byte-array-type OutputStream] [#^"[B" input #^OutputStream output opts] rlm@10: (do-copy (ByteArrayInputStream. input) output opts)) rlm@10: rlm@10: (defmethod do-copy [byte-array-type Writer] [#^"[B" input #^Writer output opts] rlm@10: (do-copy (ByteArrayInputStream. input) output opts)) rlm@10: rlm@10: (defmethod do-copy [byte-array-type File] [#^"[B" input #^Writer output opts] rlm@10: (do-copy (ByteArrayInputStream. input) output opts)) rlm@10: rlm@10: (defn copy rlm@10: "Copies input to output. Returns nil or throws IOException. rlm@10: Input may be an InputStream, Reader, File, byte[], or String. rlm@10: Output may be an OutputStream, Writer, or File. rlm@10: rlm@10: Options are key/value pairs and may be one of rlm@10: rlm@10: :buffer-size buffer size to use, default is 1024. rlm@10: :encoding encoding to use if converting between rlm@10: byte and char streams. rlm@10: rlm@10: Does not close any streams except those it opens itself rlm@10: (on a File)." rlm@10: {:added "1.2"} rlm@10: [input output & opts] rlm@10: (do-copy input output (when opts (apply hash-map opts)))) rlm@10: rlm@10: (defn ^String as-relative-path rlm@10: "Take an as-file-able thing and return a string if it is rlm@10: a relative path, else IllegalArgumentException." rlm@10: {:added "1.2"} rlm@10: [x] rlm@10: (let [^File f (as-file x)] rlm@10: (if (.isAbsolute f) rlm@10: (throw (IllegalArgumentException. (str f " is not a relative path"))) rlm@10: (.getPath f)))) rlm@10: rlm@10: (defn ^File file rlm@10: "Returns a java.io.File, passing each arg to as-file. Multiple-arg rlm@10: versions treat the first argument as parent and subsequent args as rlm@10: children relative to the parent." rlm@10: {:added "1.2"} rlm@10: ([arg] rlm@10: (as-file arg)) rlm@10: ([parent child] rlm@10: (File. ^File (as-file parent) ^String (as-relative-path child))) rlm@10: ([parent child & more] rlm@10: (reduce file (file parent child) more))) rlm@10: rlm@10: (defn delete-file rlm@10: "Delete file f. Raise an exception if it fails unless silently is true." rlm@10: {:added "1.2"} rlm@10: [f & [silently]] rlm@10: (or (.delete (file f)) rlm@10: silently rlm@10: (throw (java.io.IOException. (str "Couldn't delete " f))))) rlm@10: rlm@10: (defn make-parents rlm@10: "Given the same arg(s) as for file, creates all parent directories of rlm@10: the file they represent." rlm@10: {:added "1.2"} rlm@10: [f & more] rlm@10: (.mkdirs (.getParentFile ^File (apply file f more)))) rlm@10: rlm@10: (defn ^URL resource rlm@10: "Returns the URL for a named resource. Use the context class loader rlm@10: if no loader is specified." rlm@10: {:added "1.2"} rlm@10: ([n] (resource n (.getContextClassLoader (Thread/currentThread)))) rlm@10: ([n ^ClassLoader loader] (.getResource loader n)))