/
array.clj
278 lines (238 loc) · 7.86 KB
/
array.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
(ns bites.array
(:require [bites
[buffer :as buffer]
[constants :as constants]
[util :as ut]]
[clojure.java.io :as io])
(:import (java.nio ByteBuffer ReadOnlyBufferException CharBuffer)
(java.nio.charset Charset CharsetEncoder)
(java.nio.channels FileChannel ReadableByteChannel Channels)
(java.net URI URL)
(java.io File InputStream ByteArrayOutputStream ObjectOutputStream Serializable ByteArrayInputStream ObjectInputStream FileInputStream)
(java.util UUID Arrays)
(javax.imageio ImageIO)
(java.awt.image BufferedImage)))
(defprotocol ToByteArray (toBytes ^bytes [x opts]))
(defmulti fromBytes (fn [klass x opts] klass))
;;==============<PRIVATE HELPERS>================
(defn- base-encoded
[^String s enc b64-flavor]
(if (empty? s)
(byte-array 0)
(case enc
:uuid (-> (UUID/fromString s) (toBytes s))
:b2 (ut/binary-bytes s)
:b8 (ut/octal-bytes s)
:b64 (ut/b64-bytes s b64-flavor)
:b16 (ut/b16-bytes s))))
(defn- base-decoded
^String [^bytes bs enc b64-flavor]
(if (empty? bs)
constants/EMPTY_STRING
(case enc
:uuid (str (fromBytes UUID bs nil))
:b2 (ut/binary-str bs)
:b8 (ut/octal-str bs)
:b64 (ut/b64-str bs b64-flavor)
:b16 (ut/b16-str bs))))
;;==============<CONCRETIONS>================
(defmethod fromBytes UUID
[_ ^bytes bs _]
(let [bb (ByteBuffer/wrap bs)
high (.getLong bb)
low (.getLong bb)]
(UUID. high low)))
(defmethod fromBytes Integer
[_ ^bytes bs _]
(-> bs ByteBuffer/wrap .getInt))
(defmethod fromBytes Long
[_ ^bytes bs _]
(-> bs ByteBuffer/wrap .getLong))
(defmethod fromBytes Double
[_ ^bytes bs _]
(-> bs ByteBuffer/wrap .getDouble))
(defmethod fromBytes BigInteger
[_ ^bytes bs _]
(BigInteger. bs))
(defmethod fromBytes BigDecimal
[_ ^bytes bs _]
(let [scale-bs (byte-array (take 4 bs))
^int scale (fromBytes Integer scale-bs nil)
unscaled-value-bs (byte-array (drop 4 bs))
unscaled-value (BigInteger. unscaled-value-bs)]
(BigDecimal. unscaled-value scale)))
(defmethod fromBytes InputStream
[_ ^bytes bs _]
(ByteArrayInputStream. bs))
(defmethod fromBytes String
[_ ^bytes bs opts]
(if-let [enc (:encoding opts)]
(condp instance? enc
Charset (String. bs ^Charset enc)
String (String. bs ^String enc)
(base-decoded bs enc (:b64-flavor opts)))
(String. bs)))
(defmethod fromBytes BufferedImage
[_ ^bytes bs _]
(let [in (ByteArrayInputStream. bs)]
(ImageIO/read in)))
(defmethod fromBytes ByteBuffer
[_ ^bytes bs _]
(ByteBuffer/wrap bs))
(defmethod fromBytes ReadableByteChannel
[_ ^bytes bs _]
(let [in (ByteArrayInputStream. bs)]
(Channels/newChannel in)))
(defmethod fromBytes Serializable
[_ ^bytes bs _]
(let [in (ByteArrayInputStream. bs)]
(with-open [oin (ObjectInputStream. in)]
(.readObject oin))))
;;----------------------------------------------
(extend-protocol ToByteArray
Short
(toBytes [this opts]
(let [^ByteBuffer bb (buffer/byte-buffer Short/BYTES (:byte-order opts))]
(.putShort bb this)
(.array bb)))
Integer
(toBytes [this opts]
(let [^ByteBuffer bb (buffer/byte-buffer Integer/BYTES (:byte-order opts))]
(.putInt bb this)
(.array bb)))
Long
(toBytes [this opts]
(let [^ByteBuffer bb (buffer/byte-buffer Long/BYTES (:byte-order opts))]
(.putLong bb this)
(.array bb)))
Double
(toBytes [this opts]
(let [^ByteBuffer bb (buffer/byte-buffer Double/BYTES (:byte-order opts))]
(.putDouble bb this)
(.array bb)))
BigInteger
(toBytes [this opts]
(let [be-bs (.toByteArray this)]
(if (= :le (:byte-order opts))
(byte-array (reverse be-bs))
be-bs))
)
BigDecimal
(toBytes [this _]
(let [bi (.unscaledValue this)
bi-bytes (.toByteArray bi)
bi-length (alength bi-bytes)
scale (.scale this)
scale-bytes (toBytes scale nil)
ret (byte-array (+ 4 bi-length) scale-bytes)]
(System/arraycopy bi-bytes 0 ret 4 bi-length)
ret))
ByteArrayOutputStream
(toBytes [this _]
(.toByteArray this))
String
(toBytes [this {:keys [encoding b64-flavor]}]
(if (nil? encoding)
(.getBytes this)
(condp instance? encoding
Charset (.getBytes this ^Charset encoding)
String (.getBytes this ^String encoding)
(base-encoded this encoding b64-flavor))))
UUID
(toBytes [this opts]
(let [^ByteBuffer bb (buffer/byte-buffer 16 (:byte-order opts))]
(.putLong bb (.getMostSignificantBits this))
(.putLong bb (.getLeastSignificantBits this))
(.array bb)))
InputStream
(toBytes [this opts]
(.readAllBytes this))
File ;; delegates to `InputStream` impl
(toBytes [this opts]
(with-open [in (FileInputStream. this)]
(toBytes in opts)))
FileChannel
(toBytes [this opts]
(let [length (.size this)
^ByteBuffer buf (if (> length constants/MAX_ARRAY_SIZE)
(throw (OutOfMemoryError. "Required array size too large!"))
(buffer/byte-buffer length (:byte-order opts)))]
(.read this buf)
(.array buf)))
URL ;; delegates to `InputStream` impl
(toBytes [this opts]
(with-open [in (.openStream this)]
(toBytes in opts)))
URI ;; delegates to `URL` impl
(toBytes [this opts]
(-> (.toURL this)
(toBytes opts)))
BufferedImage
(toBytes [this opts]
(let [buffer (:buffer-size opts constants/DEFAULT_BUFFER_SIZE)
^String img-type (:image-type opts "png")
out (ByteArrayOutputStream. buffer)]
(ImageIO/write this img-type out)
(toBytes out nil)))
ByteBuffer
(toBytes [this _]
(try
;; one copy, but the ByteBuffer remains usable
(aclone (.array this))
(catch UnsupportedOperationException _
(byte-array 0))
(catch ReadOnlyBufferException _
(-> this
(io/make-output-stream nil)
(toBytes nil)))))
CharBuffer
(toBytes [this opts]
(-> opts
ut/charset-encoder
(.encode this)
.array))
Character ;; delegates to `CharBuffer` impl
(toBytes [this opts]
(-> [this]
char-array
CharBuffer/wrap
(toBytes opts)))
ReadableByteChannel
(toBytes [this opts]
(let [buffer-size (:buffer-size opts constants/DEFAULT_BUFFER_SIZE)
read! #(.read this %)]
(loop [accum (transient [])
buffer (ByteBuffer/allocate buffer-size)
nread (read! buffer)]
(cond
(> (count accum) constants/MAX_ARRAY_SIZE)
;; mimic `Files/readAllBytes` method
(throw (OutOfMemoryError. "Required array size too large!"))
;; EOS - return result
(neg? nread) (-> accum persistent! byte-array)
;; nothing was read - recur without touching anything
(zero? nread) (recur accum buffer (read! buffer))
;; something was read - grow the result, clear the buffer and recur
:else
(let [relevant (cond->> (.array buffer)
(< nread buffer-size) (take nread))
accum (reduce conj! accum relevant)
buffer (.clear buffer)]
(recur accum buffer (read! buffer)))))))
Serializable ;; catch-all extension clause
(toBytes [this opts]
(let [buffer (:buffer-size opts constants/DEFAULT_BUFFER_SIZE)
out (ByteArrayOutputStream. buffer)]
(with-open [oout (ObjectOutputStream. out)]
(.writeObject oout this)
(.flush oout)
(toBytes out nil))))
)
(extend-protocol ToByteArray
(Class/forName "[C")
(toBytes [this opts]
(-> (CharBuffer/wrap ^chars this)
(toBytes opts))))
(extend-protocol ToByteArray
(Class/forName "[B")
(toBytes [this _] this))