Remove clashing error prefix; Use better name for RatufaCoat ROMs.
[SquirrelJME.git] / buildSrc / src / main / java / cc / squirreljme / plugin / util / Base64Decoder.java
blob437fb3a1d816b0ecd017d2eea37c48943126f76f
1 // -*- Mode: Java; indent-tabs-mode: t; tab-width: 4 -*-
2 // ---------------------------------------------------------------------------
3 // SquirrelJME
4 // Copyright (C) Stephanie Gawroriski <xer@multiphasicapps.net>
5 // ---------------------------------------------------------------------------
6 // SquirrelJME is under the GNU General Public License v3+, or later.
7 // See license.mkd for licensing and copyright information.
8 // ---------------------------------------------------------------------------
10 package cc.squirreljme.plugin.util;
12 import java.io.ByteArrayOutputStream;
13 import java.io.IOException;
14 import java.io.InputStream;
15 import java.io.Reader;
16 import java.io.StringReader;
17 import java.util.Arrays;
19 /**
20 * This decodes the base64 character set, ignoring invalid characters, and
21 * provides the binary data for the input. If the padding character is reached
22 * or if the input stream runs out of characters then EOF is triggered.
24 * @since 2018/03/05
26 public final class Base64Decoder
27 extends InputStream
29 /** The source reader. */
30 protected final Reader in;
32 /** Ignore padding characters. */
33 protected final boolean ignorepadding;
35 /** The alphabet to use for decoding. */
36 private final char[] _alphabet;
38 /** The ASCII map for quick lookup. */
39 private final byte[] _ascii;
41 /** Output bytes to drain. */
42 private final byte[] _drain =
43 new byte[3];
45 /** The current fill buffer. */
46 private volatile int _buffer;
48 /** The number of bits which are in the buffer. */
49 private volatile int _bits;
51 /** Has EOF been reached if the pad has been detected? */
52 private volatile boolean _readeof;
54 /** The current output drain position. */
55 private volatile int _drained =
56 -1;
58 /** The maximum value for drained values. */
59 private volatile int _drainedmax =
60 -1;
62 /**
63 * Initializes the decode the default MIME alphabet.
65 * @param __in The input set of characters.
66 * @throws NullPointerException On null arguments.
67 * @since 2018/11/23
69 public Base64Decoder(Reader __in)
71 this(__in, Base64Alphabet.BASIC);
74 /**
75 * Initializes the decoder using the specified alphabet.
77 * @param __in The input set of characters.
78 * @param __chars The pre-defined character set to use for the alphabet.
79 * @throws NullPointerException On null arguments.
80 * @since 2018/03/05
82 public Base64Decoder(Reader __in, Base64Alphabet __chars)
83 throws NullPointerException
85 this(__in, __chars._alphabet, false);
88 /**
89 * Initializes the decoder using the specified custom alphabet.
91 * @param __in The input set of characters.
92 * @param __chars The characters to use for the alphabet.
93 * @throws IllegalArgumentException If the alphabet is of the incorrect
94 * size.
95 * @throws NullPointerException On null arguments.
96 * @since 2018/03/05
98 public Base64Decoder(Reader __in, String __chars)
99 throws IllegalArgumentException, NullPointerException
101 this(__in, __chars.toCharArray(), false);
105 * Initializes the decoder using the specified custom alphabet.
107 * @param __in The input set of characters.
108 * @param __chars The characters to use for the alphabet.
109 * @throws IllegalArgumentException If the alphabet is of the incorrect
110 * size.
111 * @throws NullPointerException On null arguments.
112 * @since 2018/03/05
114 public Base64Decoder(Reader __in, char[] __chars)
115 throws IllegalArgumentException, NullPointerException
117 this(__in, __chars, false);
121 * Initializes the decoder using the default alphabet.
123 * @param __in The input set of characters.
124 * @param __chars The pre-defined character set to use for the alphabet.
125 * @param __ip Ignore padding characters and do not treat them as the end
126 * of the stream.
127 * @throws NullPointerException On null arguments.
128 * @since 2018/03/05
130 public Base64Decoder(Reader __in, Base64Alphabet __chars, boolean __ip)
131 throws NullPointerException
133 this(__in, __chars._alphabet, __ip);
137 * Initializes the decoder using the specified custom alphabet.
139 * @param __in The input set of characters.
140 * @param __chars The characters to use for the alphabet.
141 * @param __ip Ignore padding characters and do not treat them as the end
142 * of the stream.
143 * @throws IllegalArgumentException If the alphabet is of the incorrect
144 * size.
145 * @throws NullPointerException On null arguments.
146 * @since 2018/03/05
148 public Base64Decoder(Reader __in, String __chars, boolean __ip)
149 throws IllegalArgumentException, NullPointerException
151 this(__in, __chars.toCharArray(), __ip);
155 * Initializes the decoder using the specified custom alphabet.
157 * @param __in The input set of characters.
158 * @param __chars The characters to use for the alphabet.
159 * @param __ip Ignore padding characters and do not treat them as the end
160 * of the stream.
161 * @throws IllegalArgumentException If the alphabet is of the incorrect
162 * size.
163 * @throws NullPointerException On null arguments.
164 * @since 2018/03/05
166 public Base64Decoder(Reader __in, char[] __chars, boolean __ip)
167 throws IllegalArgumentException, NullPointerException
169 if (__in == null || __chars == null)
170 throw new NullPointerException("NARG");
172 // {@squirreljme.error BD0g The alphabet to use for the base64
173 // decoder must be 64 characters plus one padding character.
174 // (The character count)}
175 int n;
176 if ((n = __chars.length) != 65)
177 throw new IllegalArgumentException(String.format("BD0g %d", n));
179 // Set
180 this.in = __in;
181 this.ignorepadding = __ip;
182 this._alphabet = (__chars = __chars.clone());
184 // Build ASCII map for quick in-range character lookup
185 byte[] ascii = new byte[128];
186 Arrays.fill(ascii, (byte)-1);
187 for (int i = 0; i < 65; i++)
189 int dx = __chars[i];
190 if (dx < 128)
191 ascii[dx] = (byte)i;
193 this._ascii = ascii;
197 * {@inheritDoc}
198 * @since 2018/11/25
200 @Override
201 public final int available()
202 throws IOException
204 int drained = this._drained;
206 // There are bytes which are ready and in the drain that we do not
207 // need to block reading them?
208 if (drained != -1)
209 return this._drainedmax - drained;
210 return 0;
214 * {@inheritDoc}
215 * @since 2018/03/05
217 @Override
218 public final void close()
219 throws IOException
221 this.in.close();
225 * {@inheritDoc}
226 * @since 2018/03/05
228 @Override
229 public final int read()
230 throws IOException
232 // If there is stuff to be drained, quickly drain that so we do not
233 // need to go deeper into the heavier method
234 int drained = this._drained;
235 if (drained != -1)
237 // Read in drained character
238 int rv = this._drain[drained++] & 0xFF;
240 // Reached the drain limit?
241 if (drained == this._drainedmax)
243 this._drained = -1;
244 this._drainedmax = -1;
247 // Would still be drain
248 else
249 this._drained = drained;
251 // Return the value
252 return rv;
255 // Previously read EOF, so this will just return EOF
256 if (this._readeof)
257 return -1;
259 // Otherwise decode and read
260 byte[] next = new byte[1];
261 for (;;)
263 int rc = this.read(next, 0, 1);
265 // EOF?
266 if (rc < 0)
267 return -1;
269 // Missed read
270 else if (rc == 0)
271 continue;
273 return (next[0] & 0xFF);
278 * {@inheritDoc}
279 * @since 2018/11/25
281 @Override
282 public final int read(byte[] __b)
283 throws IOException, NullPointerException
285 if (__b == null)
286 throw new NullPointerException("NARG");
288 return this.read(__b, 0, __b.length);
292 * {@inheritDoc}
293 * @since 2018/03/05
295 @Override
296 public final int read(byte[] __b, int __o, int __l)
297 throws IndexOutOfBoundsException, IOException, NullPointerException
299 if (__b == null)
300 throw new NullPointerException("NARG");
301 if (__o < 0 || __l < 0 || (__o + __l) < 0 || (__o + __l) > __b.length)
302 throw new IndexOutOfBoundsException("IOOB");
304 // Did a previous read cause a padded EOF?
305 boolean readeof = this._readeof;
307 // Need lookups
308 Reader in = this.in;
309 boolean ignorepadding = this.ignorepadding;
310 char[] alphabet = this._alphabet;
311 byte[] ascii = this._ascii;
312 byte[] drain = this._drain;
314 // This buffer is filled into as needed when input characters are read
315 int buffer = this._buffer,
316 bits = this._bits,
317 drained = this._drained,
318 drainedmax = this._drainedmax;
320 // Keep trying to fill bytes in
321 int rv = 0;
322 while (rv < __l)
324 // Still need to drain bytes away
325 if (drained != -1 && drained < drainedmax)
327 // Drain it
328 __b[__o++] = drain[drained++];
329 rv++;
331 // Drained all the characters
332 if (drained == drainedmax)
333 drained = drainedmax = -1;
335 // Try again
336 else
337 continue;
340 // EOF was reached
341 if (readeof)
342 break;
344 // Read in character and decode it
345 int ch = in.read();
347 // Is EOF?
348 if (ch < 0)
350 // {@squirreljme.error BD01 Read EOF from input when there
351 // were expected to be more characters or the ending padding
352 // character. (The bits in the buffer)}
353 if (bits != 0)
354 throw new IOException("BD01 " + bits);
356 // Did read EOF
357 readeof = true;
358 break;
361 // Determine the value of the character
362 if (ch < 128)
363 ch = ascii[ch];
364 else
366 ch = -1;
367 for (int i = 0; i < 65; i++)
368 if (i == alphabet[i])
370 ch = i;
371 break;
375 // Invalid, ignore and continue
376 if (ch == -1 || (ignorepadding && ch == 64))
377 continue;
379 // Decoded padding character
380 else if (ch == 64)
382 // {@squirreljme.error BD02 Did not expect a padding character.
383 // (The number of decoded bits in queue)}
384 if (bits == 0 || bits == 24)
385 throw new IOException("BD02 " + bits);
387 // Only want to store a single extra byte since that is
388 // all that is valid
389 else if (bits < 16)
391 // {@squirreljme.error BD03 Expected another padding
392 // character.}
393 if (in.read() != alphabet[64])
394 throw new IOException("BD03");
396 drain[0] = (byte)(buffer >>> 4);
398 drainedmax = 1;
401 // Otherwise there will be two characters to drain
402 else
404 drain[0] = (byte)(buffer >>> 10);
405 drain[1] = (byte)(buffer >>> 2);
407 drainedmax = 2;
410 // Need to drain all
411 drained = 0;
413 // Clear the buffer
414 buffer = bits = 0;
416 // Did read EOF
417 readeof = true;
420 // Normal data
421 else
423 // Shift in six bits
424 buffer <<= 6;
425 buffer |= ch;
426 bits += 6;
428 // Drain and empty the buffer
429 if (bits == 24)
431 // Fill the drain
432 drain[0] = (byte)(buffer >>> 16);
433 drain[1] = (byte)(buffer >>> 8);
434 drain[2] = (byte)buffer;
436 // Set these to drain
437 drained = 0;
438 drainedmax = 3;
440 // Clear the buffer
441 buffer = bits = 0;
446 // Store state for next run
447 this._buffer = buffer;
448 this._bits = bits;
449 this._readeof = readeof;
450 this._drained = drained;
451 this._drainedmax = drainedmax;
453 // Return the read count
454 if (readeof && rv == 0)
455 return -1;
456 return rv;
460 * Decodes the input string to byte values.
462 * @param __in The string to decode.
463 * @param __ab The alphabet to use.
464 * @return The resulting byte array.
465 * @throws IllegalArgumentException If the input string is not valid.
466 * @throws NullPointerException On null arguments.
467 * @since 2018/11/06
469 public static final byte[] decode(String __in, Base64Alphabet __ab)
470 throws IllegalArgumentException, NullPointerException
472 return Base64Decoder.decode(__in, __ab, false);
476 * Decodes the input string to byte values.
478 * @param __in The string to decode.
479 * @param __ab The alphabet to use.
480 * @param __ip Is padding ignored?
481 * @return The resulting byte array.
482 * @throws IllegalArgumentException If the input string is not valid.
483 * @throws NullPointerException On null arguments.
484 * @since 2018/11/04
486 public static final byte[] decode(String __in, Base64Alphabet __ab,
487 boolean __ip)
488 throws IllegalArgumentException, NullPointerException
490 if (__in == null || __ab == null)
491 throw new NullPointerException("NARG");
493 // Wrap in a reader to decode
494 try (ByteArrayOutputStream baos = new ByteArrayOutputStream())
496 byte[] buf = new byte[32];
498 // Loop handle bytes
499 try (InputStream in = new Base64Decoder(
500 new StringReader(__in), __ab, __ip))
502 for (;;)
504 int rc = in.read(buf);
506 // EOF?
507 if (rc < 0)
508 break;
510 // Copy
511 baos.write(buf, 0, rc);
515 // Return resulting byte array
516 return baos.toByteArray();
519 // {@squirreljme.error BD04 Could not decode the input string.}
520 catch (IOException e)
522 throw new IllegalArgumentException("BD04", e);