(rontolisp) docs
← リファレンス

clojure.java.io

ファイル、URL と、その上のストリームの名前空間です。Clojure と同じくプログラムより前にロードされる ので、clojure.java.io/file は require なしで使えます。Clojure の同名の名前空間について文書化 された振る舞いをもとに rontolisp 向けに書いた Clojure ソースです。値はホストオブジェクトではなく rontolisp 自身のもので、java.io.File、java.net.URL、java.net.URI とバイトストリームは インタプリタ、JVM、2 つの WASM ターゲットのどれでも同じように振る舞います。WASM では slurp と同じく、ファイルにはそれを含む --dir プリオープンが必要です。なければ open はオラクルの java.io.FileNotFoundException になります。

var振る舞い
file(file arg) / (file parent child & more): パス、File、file: URL が指す java.io.File(nil には nil)。2 つ目以降の引数は、それぞれ直前のものの下の相対パス
as-file, as-urlCoercions プロトコル: 値が File として、また URL として表すもの。nil は nil
as-relative-path(as-relative-path x): 相対の File か文字列のパス。絶対パスは IllegalArgumentException
reader, writer(reader x & opts): パス、File、URL、URI、バイト配列、バイトストリームの上のバッファ付き文字ストリーム、またはストリームそのもの。:encoding で文字セットを指定し(既定は UTF-8)、:append true で writer のテキストを追記する
input-stream, output-stream(input-stream x & opts): パス、File、URL、URI、バイト配列の上のバッファ付きバイトストリーム、またはバイトストリームそのもの。:append true で出力ストリームのバイトを追記する
copy(copy input output & opts): バイトストリーム、バイト配列、リーダー、File、文字列の中身を、バイトストリーム、ライター、File に書く。文字は :encoding で変換する。ほかの組み合わせはオラクルの IllegalArgumentException
delete-file(delete-file f & [silently]): ファイルを削除して true を返す。削除できなければ、silently が truthy ならそれを返し、そうでなければ java.io.IOException を投げる
make-parents(make-parents f & more): File (file f & more) の上の足りないディレクトリを作り、作ったかどうかを返す
resource(resource name): ソースパスが name で持つファイルかディレクトリの URL(リソース)、なければ nil
make-reader, make-writer, make-input-stream, make-output-stream4 つのストリーム関数がオプションをマップにして呼ぶ IOFactory プロトコル
default-streams-impl型を拡張するための IOFactory のメソッド: 入力ストリームの上のリーダー、出力ストリームの上のライター、両バイトストリームの拒否

slurp と spit は、パスでないものを Clojure と同じく reader と writer で 開きます。file-seq は File のディレクトリツリーをたどります。

clojure> (require '[clojure.java.io :as io])
nil
clojure> (def f (io/file "/tmp/notes" "a.txt"))
#'user/f
clojure> (io/make-parents f)
true
clojure> (with-open [w (io/writer f)] (.write w "one\ntwo\n"))
nil
clojure> (with-open [r (io/reader f)] (vec (line-seq r)))
["one" "two"]
clojure> (with-open [in (io/input-stream f)] (.read in))
111
clojure> (io/copy f (io/file "/tmp/notes/b.txt"))
nil
clojure> (sort (map str (file-seq (io/file "/tmp/notes"))))
("/tmp/notes" "/tmp/notes/a.txt" "/tmp/notes/b.txt")
clojure> (io/delete-file "/tmp/notes/b.txt")
true

ファイル、URL、URI

File はそのパスで、Unix の java.io.File と同じく正規化されます(スラッシュの重複も末尾の スラッシュもありません)。#object[java.io.File "path"] と印字され、str はパスを返し、パスが 同じ 2 つの File は = です。メソッドはパスから答えるもの(getName、getParent、 getParentFile、getPath、isAbsolute、getAbsolutePath、toURI、 toURL、compareTo)と、ファイルシステムから答えるもの(getCanonicalPath(シンボリック リンクをすべて解決し、存在しない部分は綴りのまま残す)、exists、isFile、isDirectory、 length、lastModified、canRead、isHidden、list、listFiles、mkdir、mkdirs、 createNewFile、renameTo、delete)があります。(java.io.File. path) と (java.io.File. parent child) は同じ値を作り、ファイルストリームの構築(パスか File の上の java.io.FileReader.、FileWriter.、FileInputStream.、FileOutputStream.)はこの名前空間の ストリームを作ります。

URL は綴りを保ち、getProtocol、getHost、getPort、getPath、getFile、getQuery、 getRef、getAuthority、getUserInfo に java.net.URL と同じく答えます。知られていない プロトコルの綴りはオラクルの java.net.MalformedURLException です。file: URL はそのファイルを、 http: と https: の URL はその応答を開きます(HTTP の URL)。URI は getScheme と getPath に答え、toURL はその URL を返し、uri? は true です。 どれも class はそのクラスのキーワード(:java.io.File)を返し、instance?、class で振り分ける マルチメソッド、そのクラスに拡張したプロトコルは、上位型も含めて値を受け付けます(バイトストリーム なら java.io.InputStream)。

ストリーム

ファイルの上のリーダーとライターは *in* や *out* と同じ文字ストリームなので、line-seq、 read、.readLine、*in* や *out* の binding、with-open が受け付けます。バイトストリームは read(次のオクテット。終端の先では -1)、バイト配列やその一部への read(読んだ数。終端の 先では -1)、readNBytes、readAllBytes(バイト配列)、available、skip、transferTo、 write(int の下位オクテット、バイト配列やその一部)、flush、close に答えます。バイト ストリームの上のリーダーはそれを復号し、その上のライターは flush か close のときにテキストを 符号化して書き込みます。:encoding が指定できるのは UTF-8、ISO-8859-1、US-ASCII(とその別名) で、ほかの名前はオラクルの java.io.UnsupportedEncodingException です。Charset を渡すとオラクルの ClassCastException になります。(InputStreamReader. in charset)、(OutputStreamWriter. out charset)、 ByteArrayOutputStream の .toString は Charset も受け取ります(byte-array)。

(java.io.ByteArrayInputStream. bytes) はバイト配列をコピーせずに読み、 (java.io.ByteArrayInputStream. bytes off len) は off からの一部を読みます。 (java.io.ByteArrayOutputStream.) は書き込まれたオクテットを集めます。toByteArray はその 写しを、size は個数を、toString は(str と同じく)UTF-8 か指定した文字セットのテキストを 返し、writeTo は別のバイトストリームへ書き、reset は中身を空にします。Java と同じく、 どちらも close しても何も変わりません。オクテットの上のストリーム(バイト配列、リソース)と ファイルの上のストリームは mark で取った位置を保持し、reset はその後どれだけ読んでいても そこへ戻ります。mark より前の reset はオラクルの java.io.IOException です。 FileInputStream. と http: URL の上のストリームは位置を保持しません(markSupported は false)。ByteArrayInputStream の input-stream はそれ自身の BufferedInputStream、 ByteArrayOutputStream の output-stream は書き込まれたオクテットを flush か close まで (8192 オクテットまで)保持する BufferedOutputStream で、その下のストリームは閉じません。 copy でバイトストリームをこれに書いても flush されず、ライターや文字列からの copy は flush します。

HTTP の URL

http: と https: の URL(URL、URI、URL を綴った文字列)は、オラクルの java.net.HttpURLConnection と同じように rontolisp:fetch で 読みます。slurp、reader、input-stream と URL の .openStream は GET を送り、同じ プロトコルの URL へのリダイレクト(300、301、302、303、307)をたどり、ステータスが 400 以上の 応答をストリームを開く時点で拒否します。404 と 410 は URL を示す java.io.FileNotFoundException、それ以外はコードを示す java.io.IOException で、20 回目の リダイレクトは java.net.ProtocolException です。ボディは届いた分から読み、応答が示す文字 セットではなく :encoding(既定は UTF-8)で復号し、伸張はしません。input-stream はその上の java.io.BufferedInputStream を、.openStream は接続自身のストリームを返します。書き込みは オラクルと同じ文言で拒否します。

プログラムがこの URL を読むのは、読み取りが開く式の中に URL を文字列リテラルで書いたとき ((slurp "https://...")、(io/reader (str "https://" host path))、 (.openStream (io/as-url "https://...")))か、何も定義しない名前空間 rontolisp.http-urls を require したときです。どちらの場合もプログラムは開始時から fetch を使うので、ターゲットには fetch が必要です(HTTP クライアントのターゲット)。素の Preview 1 モジュールと、 --host-fetch なしの --no-wasi モジュールは、コンパイルの時点で拒否されます。それ以外の プログラムでは、http: URL の読み取りは 2 つの方法を示す UnsupportedOperationException です。 このプログラムは fetch を使わないので、ファイルだけを読むコンポーネントは wasi:http を インポートしません。

{
:not-found

プログラムが計算した URL は、この名前空間を require すれば読めます。

$ cat get.clj
(ns get (:require [rontolisp.http-urls]))
(println (subs (slurp (first *command-line-args*)) 0 15))
$ rontolisp get.clj -- https://example.com/
<!doctype html>

リソース

resource は名前をソースパス(プログラムのプロジェクトと依存先のルート。ディレクトリと jar。 プロジェクト)で探します。オラクルはクラスパスで探しますが、 同じプロジェクトはそこへ同じルートを入れます。ディレクトリのファイルとサブディレクトリはその file: URL を、jar のエントリは jar:file:...!/name URL を返します(ディレクトリのエントリ name/ は name でも見つかります)。文字列リテラルで書いた名前はプログラムのコンパイル時に 見つけ、見つけた内容をバイト列のままプログラムに埋め込むので、WASM モジュールを含め、どこで 動かしても同じように読めます。内容はプログラムの開始前に揃うので、その URL の読み出しはどれも、 手で綴った URL の読み出しも含めて、この内容を読みます。プログラムが計算した名前は実行時に、ソースパスの各ルートを順に、 jar のエントリも含めて探します。返した jar: URL を読むと、そのエントリを jar から読みます。 WASM では、それらのディレクトリと jar に --dir のプリオープンが要ります。resource に 文字列リテラルしか渡さないプログラムは、実行時に名前を探すコードを含みません。

$ cat resources/config.edn
{:port 8080}
$ cat src/app/main.clj
(ns app.main (:require [clojure.java.io :as io] [clojure.edn :as edn]))
(prn (edn/read-string (slurp (io/resource "config.edn"))))
$ rontolisp src/app/main.clj        # deps.edn holds {:paths ["src" "resources"]}
{:port 8080}

プロトコル

Coercions と IOFactory は、オラクルと同じくプログラムが拡張するプロトコルです。Coercions に拡張した型は file にとって File になり、IOFactory に拡張した型は自分のメソッドで開かれ、 slurp と spit もそれを使います。extend は、メソッドを 1 つ差し替えた default-streams-impl を受け付けます。

違い

  • File、URL、URI、ストリームは、オラクルの #object[...] から識別ハッシュを除いた形で印字 されます。ストリームの str はクラス名です(オラクルの Class@hash からハッシュを除いたもの)。 class は、ほかの値と同じくキーワードを返します(仕様との差異)。
  • URL と URI の .hashCode は綴りの String.hashCode で、オラクルは構成要素からハッシュを 作ります。綴りが同じ 2 つは = で、java.net.URL は解決したホストを比べます。File の .hashCode はオラクルと同じです。
  • http: と https: の URL を読むのは、そのために fetch を使うプログラムだけです (HTTP の URL)。オラクルはどのプログラムでも読みます。jar: URL も同様に、 読めるのは resource がコンパイル時に見つけたものと、jar を含むソースパスで計算した名前を 探すプログラムが読むものだけです。file:、http:、https:、jar: 以外のプロトコルの URL の読み取りは、名前を挙げて拒否します。オラクルは接続を開きます。
  • http: URL の読み取りでは、トランスポートの失敗はトランスポートのメッセージを持つ java.io.IOException です(オラクルの java.net.ConnectException と UnknownHostException はその一種です)。305 の応答はたどりません(オラクルは応答が示す プロキシを通して送り直します)。リクエストには fetch の User-Agent が付きます。 .available は届いた分を答え、最初の読み取りの前は 0 です。
  • :encoding が知る文字セットは 3 つで、オラクルは JDK のものを知っています。
  • ByteArrayInputStream の input-stream は、そのストリームが残していたオクテットを自分で 読むので、下のストリームの位置は動きません。オラクルのバッファは先読みし、下のストリームは バッファした分の末尾まで進みます。input-stream と output-stream はそれ以外のバイト ストリーム(ファイルや http: URL の上のもの)をそのまま返し、オラクルは包みます。 mark は読み取り上限を保持しません。上限を越えた reset はオラクルではバッファが マークを捨てて失敗しますが、ここでは成功します。http: URL の上のストリームは mark の 位置を保持しません。
  • resource は渡されたクラスローダーを参照しません。
  • (java.net.URL. s) と (java.net.URI. s) はホストオブジェクトを作ります(インタプリタと JVM)。どのバックエンドにもある URL は as-url が作り、その .toURI が URI です。
  • lastModified は秒単位(1000 の倍数)で答えます。オラクルはミリ秒単位で答えることがあります。
  • WASM では相対の File の getAbsolutePath を拒否します(作業ディレクトリがありません)。 canRead はファイルが存在するかを答えます。
  • line-seq はパスと同じく File、URL、バイトストリームも受け付けます(line-seq)。 オラクルはリーダーだけを受け付けます。