001/*
002 * Copyright (C) 2012 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the License
010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
011 * or implied. See the License for the specific language governing permissions and limitations under
012 * the License.
013 */
014
015package com.google.common.io;
016
017import static com.google.common.base.Preconditions.checkArgument;
018import static com.google.common.base.Preconditions.checkNotNull;
019import static com.google.common.base.Preconditions.checkPositionIndexes;
020import static com.google.common.base.Preconditions.checkState;
021import static com.google.common.math.IntMath.divide;
022import static com.google.common.math.IntMath.log2;
023import static java.lang.Math.max;
024import static java.lang.Math.min;
025import static java.lang.System.arraycopy;
026import static java.math.RoundingMode.CEILING;
027import static java.math.RoundingMode.FLOOR;
028import static java.math.RoundingMode.UNNECESSARY;
029
030import com.google.common.annotations.GwtCompatible;
031import com.google.common.annotations.GwtIncompatible;
032import com.google.common.annotations.J2ktIncompatible;
033import com.google.common.base.Ascii;
034import com.google.errorprone.annotations.concurrent.GuardedBy;
035import com.google.errorprone.annotations.concurrent.LazyInit;
036import java.io.IOException;
037import java.io.InputStream;
038import java.io.OutputStream;
039import java.io.Reader;
040import java.io.Writer;
041import java.util.Arrays;
042import java.util.Objects;
043import org.jspecify.annotations.Nullable;
044
045/**
046 * A binary encoding scheme for reversibly translating between byte sequences and printable ASCII
047 * strings. This class includes several constants for encoding schemes specified by <a
048 * href="http://tools.ietf.org/html/rfc4648">RFC 4648</a>. For example, the expression:
049 *
050 * {@snippet :
051 * BaseEncoding.base32().encode("foo".getBytes(US_ASCII))
052 * }
053 *
054 * <p>returns the string {@code "MZXW6==="}, and
055 *
056 * {@snippet :
057 * byte[] decoded = BaseEncoding.base32().decode("MZXW6===");
058 * }
059 *
060 * <p>...returns the ASCII bytes of the string {@code "foo"}.
061 *
062 * <p>By default, {@code BaseEncoding}'s behavior is relatively strict and in accordance with RFC
063 * 4648. Decoding rejects characters in the wrong case, though padding is optional. To modify
064 * encoding and decoding behavior, use configuration methods to obtain a new encoding with modified
065 * behavior:
066 *
067 * {@snippet :
068 * BaseEncoding.base16().lowerCase().decode("deadbeef");
069 * }
070 *
071 * <p>Warning: BaseEncoding instances are immutable. Invoking a configuration method has no effect
072 * on the receiving instance; you must store and use the new encoding instance it returns, instead.
073 *
074 * {@snippet :
075 * // Do NOT do this
076 * BaseEncoding hex = BaseEncoding.base16();
077 * hex.lowerCase(); // does nothing!
078 * return hex.decode("deadbeef"); // throws an IllegalArgumentException
079 * }
080 *
081 * <p>It is guaranteed that {@code encoding.decode(encoding.encode(x))} is always equal to {@code
082 * x}, but the reverse does not necessarily hold.
083 *
084 * <table>
085 * <caption>Encodings</caption>
086 * <tr>
087 * <th>Encoding
088 * <th>Alphabet
089 * <th>{@code char:byte} ratio
090 * <th>Default padding
091 * <th>Comments
092 * <tr>
093 * <td>{@link #base16()}
094 * <td>0-9 A-F
095 * <td>2.00
096 * <td>N/A
097 * <td>Traditional hexadecimal. Defaults to upper case.
098 * <tr>
099 * <td>{@link #base32()}
100 * <td>A-Z 2-7
101 * <td>1.60
102 * <td>=
103 * <td>Human-readable; no possibility of mixing up 0/O or 1/I. Defaults to upper case.
104 * <tr>
105 * <td>{@link #base32Hex()}
106 * <td>0-9 A-V
107 * <td>1.60
108 * <td>=
109 * <td>"Numerical" base 32; extended from the traditional hex alphabet. Defaults to upper case.
110 * <tr>
111 * <td>{@link #base64()}
112 * <td>A-Z a-z 0-9 + /
113 * <td>1.33
114 * <td>=
115 * <td>
116 * <tr>
117 * <td>{@link #base64Url()}
118 * <td>A-Z a-z 0-9 - _
119 * <td>1.33
120 * <td>=
121 * <td>Safe to use as filenames, or to pass in URLs without escaping
122 * </table>
123 *
124 * <p>All instances of this class are immutable, so they may be stored safely as static constants.
125 *
126 * @author Louis Wasserman
127 * @since 14.0
128 */
129// We use `& 0xff` before casting to `(byte)` to keep GWT from letting the byte be out of range:
130// https://www.gwtproject.org/doc/latest/DevGuideCodingBasicsCompatibility
131@SuppressWarnings("MaskAndCastToByte")
132@GwtCompatible
133public abstract class BaseEncoding {
134  // TODO(lowasser): consider making encodeTo(Appendable, byte[], int, int) public.
135
136  BaseEncoding() {}
137
138  /**
139   * Exception indicating invalid base-encoded input encountered while decoding.
140   *
141   * @author Louis Wasserman
142   * @since 15.0
143   */
144  public static final class DecodingException extends IOException {
145    DecodingException(@Nullable String message) {
146      super(message);
147    }
148  }
149
150  /** Encodes the specified byte array, and returns the encoded {@code String}. */
151  public String encode(byte[] bytes) {
152    return encode(bytes, 0, bytes.length);
153  }
154
155  /**
156   * Encodes the specified range of the specified byte array, and returns the encoded {@code
157   * String}.
158   */
159  public final String encode(byte[] bytes, int off, int len) {
160    checkPositionIndexes(off, off + len, bytes.length);
161    StringBuilder result = new StringBuilder(maxEncodedSize(len));
162    try {
163      encodeTo(result, bytes, off, len);
164    } catch (IOException impossible) {
165      throw new AssertionError(impossible);
166    }
167    return result.toString();
168  }
169
170  /**
171   * Returns an {@code OutputStream} that encodes bytes using this encoding into the specified
172   * {@code Writer}. When the returned {@code OutputStream} is closed, so is the backing {@code
173   * Writer}.
174   */
175  @J2ktIncompatible
176  @GwtIncompatible // Writer,OutputStream
177  public abstract OutputStream encodingStream(Writer writer);
178
179  /**
180   * Returns a {@code ByteSink} that writes base-encoded bytes to the specified {@code CharSink}.
181   */
182  @J2ktIncompatible
183  @GwtIncompatible // ByteSink,CharSink
184  public final ByteSink encodingSink(CharSink encodedSink) {
185    checkNotNull(encodedSink);
186    return new ByteSink() {
187      @Override
188      public OutputStream openStream() throws IOException {
189        return encodingStream(encodedSink.openStream());
190      }
191    };
192  }
193
194  // TODO(lowasser): document the extent of leniency, probably after adding ignore(CharMatcher)
195
196  private static byte[] extract(byte[] result, int length) {
197    if (length == result.length) {
198      return result;
199    }
200    byte[] trunc = new byte[length];
201    arraycopy(result, 0, trunc, 0, length);
202    return trunc;
203  }
204
205  /**
206   * Determines whether the specified character sequence is a valid encoded string according to this
207   * encoding.
208   *
209   * @since 20.0
210   */
211  public abstract boolean canDecode(CharSequence chars);
212
213  /**
214   * Decodes the specified character sequence, and returns the resulting {@code byte[]}. This is the
215   * inverse operation to {@link #encode(byte[])}.
216   *
217   * @throws IllegalArgumentException if the input is not a valid encoded string according to this
218   *     encoding.
219   */
220  public final byte[] decode(CharSequence chars) {
221    try {
222      return decodeChecked(chars);
223    } catch (DecodingException badInput) {
224      throw new IllegalArgumentException(badInput);
225    }
226  }
227
228  /**
229   * Decodes the specified character sequence, and returns the resulting {@code byte[]}. This is the
230   * inverse operation to {@link #encode(byte[])}.
231   *
232   * @throws DecodingException if the input is not a valid encoded string according to this
233   *     encoding.
234   */
235  final byte[] decodeChecked(CharSequence chars)
236      throws DecodingException {
237    chars = trimTrailingPadding(chars);
238    byte[] tmp = new byte[maxDecodedSize(chars.length())];
239    int len = decodeTo(tmp, chars);
240    return extract(tmp, len);
241  }
242
243  /**
244   * Returns an {@code InputStream} that decodes base-encoded input from the specified {@code
245   * Reader}. The returned stream throws a {@link DecodingException} upon decoding-specific errors.
246   */
247  @J2ktIncompatible
248  @GwtIncompatible // Reader,InputStream
249  public abstract InputStream decodingStream(Reader reader);
250
251  /**
252   * Returns a {@code ByteSource} that reads base-encoded bytes from the specified {@code
253   * CharSource}.
254   */
255  @J2ktIncompatible
256  @GwtIncompatible // ByteSource,CharSource
257  public final ByteSource decodingSource(CharSource encodedSource) {
258    checkNotNull(encodedSource);
259    return new ByteSource() {
260      @Override
261      public InputStream openStream() throws IOException {
262        return decodingStream(encodedSource.openStream());
263      }
264    };
265  }
266
267  // Implementations for encoding/decoding
268
269  abstract int maxEncodedSize(int bytes);
270
271  abstract void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException;
272
273  abstract int maxDecodedSize(int chars);
274
275  abstract int decodeTo(byte[] target, CharSequence chars) throws DecodingException;
276
277  CharSequence trimTrailingPadding(CharSequence chars) {
278    return checkNotNull(chars);
279  }
280
281  // Modified encoding generators
282
283  /**
284   * Returns an encoding that behaves equivalently to this encoding, but omits any padding
285   * characters as specified by <a href="http://tools.ietf.org/html/rfc4648#section-3.2">RFC 4648
286   * section 3.2</a>, Padding of Encoded Data.
287   */
288  public abstract BaseEncoding omitPadding();
289
290  /**
291   * Returns an encoding that behaves equivalently to this encoding, but uses an alternate character
292   * for padding.
293   *
294   * @throws IllegalArgumentException if this padding character is already used in the alphabet or a
295   *     separator
296   */
297  public abstract BaseEncoding withPadChar(char padChar);
298
299  /**
300   * Returns an encoding that behaves equivalently to this encoding, but adds a separator string
301   * after every {@code n} characters. Any occurrences of any characters that occur in the separator
302   * are skipped over in decoding.
303   *
304   * @throws IllegalArgumentException if any alphabet or padding characters appear in the separator
305   *     string, or if {@code n <= 0}
306   * @throws UnsupportedOperationException if this encoding already uses a separator
307   */
308  public abstract BaseEncoding withSeparator(String separator, int n);
309
310  /**
311   * Returns an encoding that behaves equivalently to this encoding, but encodes and decodes with
312   * uppercase letters. Padding and separator characters remain in their original case.
313   *
314   * @throws IllegalStateException if the alphabet used by this encoding contains mixed upper- and
315   *     lower-case characters
316   */
317  public abstract BaseEncoding upperCase();
318
319  /**
320   * Returns an encoding that behaves equivalently to this encoding, but encodes and decodes with
321   * lowercase letters. Padding and separator characters remain in their original case.
322   *
323   * @throws IllegalStateException if the alphabet used by this encoding contains mixed upper- and
324   *     lower-case characters
325   */
326  public abstract BaseEncoding lowerCase();
327
328  /**
329   * Returns an encoding that behaves equivalently to this encoding, but decodes letters without
330   * regard to case.
331   *
332   * @throws IllegalStateException if the alphabet used by this encoding contains mixed upper- and
333   *     lower-case characters
334   * @since 32.0.0
335   */
336  public abstract BaseEncoding ignoreCase();
337
338  private static final BaseEncoding BASE64 =
339      new Base64Encoding(
340          "base64()", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/", '=');
341
342  /**
343   * The "base64" base encoding specified by <a
344   * href="http://tools.ietf.org/html/rfc4648#section-4">RFC 4648 section 4</a>, Base 64 Encoding.
345   * (This is the same as the base 64 encoding from <a
346   * href="http://tools.ietf.org/html/rfc3548#section-3">RFC 3548</a>.)
347   *
348   * <p>The character {@code '='} is used for padding, but can be {@linkplain #omitPadding()
349   * omitted} or {@linkplain #withPadChar(char) replaced}.
350   *
351   * <p>No line feeds are added by default, as per <a
352   * href="http://tools.ietf.org/html/rfc4648#section-3.1">RFC 4648 section 3.1</a>, Line Feeds in
353   * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}.
354   */
355  public static BaseEncoding base64() {
356    return BASE64;
357  }
358
359  private static final BaseEncoding BASE64_URL =
360      new Base64Encoding(
361          "base64Url()", "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_", '=');
362
363  /**
364   * The "base64url" encoding specified by <a
365   * href="http://tools.ietf.org/html/rfc4648#section-5">RFC 4648 section 5</a>, Base 64 Encoding
366   * with URL and Filename Safe Alphabet, also sometimes referred to as the "web safe Base64." (This
367   * is the same as the base 64 encoding with URL and filename safe alphabet from <a
368   * href="http://tools.ietf.org/html/rfc3548#section-4">RFC 3548</a>.)
369   *
370   * <p>The character {@code '='} is used for padding, but can be {@linkplain #omitPadding()
371   * omitted} or {@linkplain #withPadChar(char) replaced}.
372   *
373   * <p>No line feeds are added by default, as per <a
374   * href="http://tools.ietf.org/html/rfc4648#section-3.1">RFC 4648 section 3.1</a>, Line Feeds in
375   * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}.
376   */
377  public static BaseEncoding base64Url() {
378    return BASE64_URL;
379  }
380
381  private static final BaseEncoding BASE32 =
382      new StandardBaseEncoding("base32()", "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567", '=');
383
384  /**
385   * The "base32" encoding specified by <a href="http://tools.ietf.org/html/rfc4648#section-6">RFC
386   * 4648 section 6</a>, Base 32 Encoding. (This is the same as the base 32 encoding from <a
387   * href="http://tools.ietf.org/html/rfc3548#section-5">RFC 3548</a>.)
388   *
389   * <p>The character {@code '='} is used for padding, but can be {@linkplain #omitPadding()
390   * omitted} or {@linkplain #withPadChar(char) replaced}.
391   *
392   * <p>No line feeds are added by default, as per <a
393   * href="http://tools.ietf.org/html/rfc4648#section-3.1">RFC 4648 section 3.1</a>, Line Feeds in
394   * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}.
395   */
396  public static BaseEncoding base32() {
397    return BASE32;
398  }
399
400  private static final BaseEncoding BASE32_HEX =
401      new StandardBaseEncoding("base32Hex()", "0123456789ABCDEFGHIJKLMNOPQRSTUV", '=');
402
403  /**
404   * The "base32hex" encoding specified by <a
405   * href="http://tools.ietf.org/html/rfc4648#section-7">RFC 4648 section 7</a>, Base 32 Encoding
406   * with Extended Hex Alphabet. There is no corresponding encoding in RFC 3548.
407   *
408   * <p>The character {@code '='} is used for padding, but can be {@linkplain #omitPadding()
409   * omitted} or {@linkplain #withPadChar(char) replaced}.
410   *
411   * <p>No line feeds are added by default, as per <a
412   * href="http://tools.ietf.org/html/rfc4648#section-3.1">RFC 4648 section 3.1</a>, Line Feeds in
413   * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}.
414   */
415  public static BaseEncoding base32Hex() {
416    return BASE32_HEX;
417  }
418
419  private static final BaseEncoding BASE16 = new Base16Encoding("base16()", "0123456789ABCDEF");
420
421  /**
422   * The "base16" encoding specified by <a href="http://tools.ietf.org/html/rfc4648#section-8">RFC
423   * 4648 section 8</a>, Base 16 Encoding. (This is the same as the base 16 encoding from <a
424   * href="http://tools.ietf.org/html/rfc3548#section-6">RFC 3548</a>.) This is commonly known as
425   * "hexadecimal" format.
426   *
427   * <p>No padding is necessary in base 16, so {@link #withPadChar(char)} and {@link #omitPadding()}
428   * have no effect.
429   *
430   * <p>No line feeds are added by default, as per <a
431   * href="http://tools.ietf.org/html/rfc4648#section-3.1">RFC 4648 section 3.1</a>, Line Feeds in
432   * Encoded Data. Line feeds may be added using {@link #withSeparator(String, int)}.
433   *
434   * <p><b>Java 17+ users:</b> Consider using the {@link java.util.HexFormat} API instead.
435   */
436  public static BaseEncoding base16() {
437    return BASE16;
438  }
439
440  static final class Alphabet {
441    private final String name;
442    // this is meant to be immutable -- don't modify it!
443    private final char[] chars;
444    final int mask;
445    final int bitsPerChar;
446    final int charsPerChunk;
447    final int bytesPerChunk;
448    private final byte[] decodabet;
449    private final boolean[] validPadding;
450    private final boolean ignoreCase;
451
452    Alphabet(String name, char[] chars) {
453      this(name, chars, decodabetFor(chars), /* ignoreCase= */ false);
454    }
455
456    private Alphabet(String name, char[] chars, byte[] decodabet, boolean ignoreCase) {
457      this.name = checkNotNull(name);
458      this.chars = checkNotNull(chars);
459      try {
460        this.bitsPerChar = log2(chars.length, UNNECESSARY);
461      } catch (ArithmeticException e) {
462        throw new IllegalArgumentException("Illegal alphabet length " + chars.length, e);
463      }
464
465      // Compute how input bytes are chunked. For example, with base64 we chunk every 3 bytes into
466      // 4 characters. We have bitsPerChar == 6, charsPerChunk == 4, and bytesPerChunk == 3.
467      // We're looking for the smallest charsPerChunk such that bitsPerChar * charsPerChunk is a
468      // multiple of 8. A multiple of 8 has 3 low zero bits, so we just need to figure out how many
469      // extra zero bits we need to add to the end of bitsPerChar to get 3 in total.
470      // The logic here would be wrong for bitsPerChar > 8, but since we require distinct ASCII
471      // characters that can't happen.
472      int zeroesInBitsPerChar = Integer.numberOfTrailingZeros(bitsPerChar);
473      this.charsPerChunk = 1 << (3 - zeroesInBitsPerChar);
474      this.bytesPerChunk = bitsPerChar >> zeroesInBitsPerChar;
475
476      this.mask = chars.length - 1;
477
478      this.decodabet = decodabet;
479
480      boolean[] validPadding = new boolean[charsPerChunk];
481      for (int i = 0; i < bytesPerChunk; i++) {
482        validPadding[divide(i * 8, bitsPerChar, CEILING)] = true;
483      }
484      this.validPadding = validPadding;
485      this.ignoreCase = ignoreCase;
486    }
487
488    private static byte[] decodabetFor(char[] chars) {
489      byte[] decodabet = new byte[Ascii.MAX + 1];
490      Arrays.fill(decodabet, (byte) -1);
491      for (int i = 0; i < chars.length; i++) {
492        char c = chars[i];
493        checkArgument(c < decodabet.length, "Non-ASCII character: %s", c);
494        checkArgument(decodabet[c] == -1, "Duplicate character: %s", c);
495        decodabet[c] = (byte) i;
496      }
497      return decodabet;
498    }
499
500    /** Returns an equivalent {@code Alphabet} except it ignores case. */
501    Alphabet ignoreCase() {
502      if (ignoreCase) {
503        return this;
504      }
505
506      // We can't use .clone() because of GWT.
507      byte[] newDecodabet = Arrays.copyOf(decodabet, decodabet.length);
508      for (int upper = 'A'; upper <= 'Z'; upper++) {
509        int lower = upper | 0x20;
510        byte decodeUpper = decodabet[upper];
511        byte decodeLower = decodabet[lower];
512        if (decodeUpper == -1) {
513          newDecodabet[upper] = decodeLower;
514        } else {
515          checkState(
516              decodeLower == -1,
517              "Can't ignoreCase() since '%s' and '%s' encode different values",
518              (char) upper,
519              (char) lower);
520          newDecodabet[lower] = decodeUpper;
521        }
522      }
523      return new Alphabet(name + ".ignoreCase()", chars, newDecodabet, /* ignoreCase= */ true);
524    }
525
526    char encode(int bits) {
527      return chars[bits];
528    }
529
530    boolean isValidPaddingStartPosition(long index) {
531      return validPadding[(int) (index % charsPerChunk)];
532    }
533
534    boolean canDecode(char ch) {
535      return ch <= Ascii.MAX && decodabet[ch] != -1;
536    }
537
538    int decode(char ch) throws DecodingException {
539      if (ch > Ascii.MAX) {
540        throw new DecodingException("Unrecognized character: 0x" + Integer.toHexString(ch));
541      }
542      int result = decodabet[ch];
543      if (result == -1) {
544        if (ch <= 0x20 || ch == Ascii.MAX) {
545          throw new DecodingException("Unrecognized character: 0x" + Integer.toHexString(ch));
546        } else {
547          throw new DecodingException("Unrecognized character: " + ch);
548        }
549      }
550      return result;
551    }
552
553    private boolean hasLowerCase() {
554      for (char c : chars) {
555        if (Ascii.isLowerCase(c)) {
556          return true;
557        }
558      }
559      return false;
560    }
561
562    private boolean hasUpperCase() {
563      for (char c : chars) {
564        if (Ascii.isUpperCase(c)) {
565          return true;
566        }
567      }
568      return false;
569    }
570
571    Alphabet upperCase() {
572      if (!hasLowerCase()) {
573        return this;
574      }
575      checkState(!hasUpperCase(), "Cannot call upperCase() on a mixed-case alphabet");
576      char[] upperCased = new char[chars.length];
577      for (int i = 0; i < chars.length; i++) {
578        upperCased[i] = Ascii.toUpperCase(chars[i]);
579      }
580      Alphabet upperCase = new Alphabet(name + ".upperCase()", upperCased);
581      return ignoreCase ? upperCase.ignoreCase() : upperCase;
582    }
583
584    Alphabet lowerCase() {
585      if (!hasUpperCase()) {
586        return this;
587      }
588      checkState(!hasLowerCase(), "Cannot call lowerCase() on a mixed-case alphabet");
589      char[] lowerCased = new char[chars.length];
590      for (int i = 0; i < chars.length; i++) {
591        lowerCased[i] = Ascii.toLowerCase(chars[i]);
592      }
593      Alphabet lowerCase = new Alphabet(name + ".lowerCase()", lowerCased);
594      return ignoreCase ? lowerCase.ignoreCase() : lowerCase;
595    }
596
597    public boolean matches(char c) {
598      return c < decodabet.length && decodabet[c] != -1;
599    }
600
601    @Override
602    public String toString() {
603      return name;
604    }
605
606    @Override
607    public boolean equals(@Nullable Object other) {
608      if (other instanceof Alphabet) {
609        Alphabet that = (Alphabet) other;
610        return this.ignoreCase == that.ignoreCase && Arrays.equals(this.chars, that.chars);
611      }
612      return false;
613    }
614
615    @Override
616    public int hashCode() {
617      return Arrays.hashCode(chars) + (ignoreCase ? 1231 : 1237);
618    }
619  }
620
621  private static class StandardBaseEncoding extends BaseEncoding {
622    final Alphabet alphabet;
623
624    final @Nullable Character paddingChar;
625
626    StandardBaseEncoding(String name, String alphabetChars, @Nullable Character paddingChar) {
627      this(new Alphabet(name, alphabetChars.toCharArray()), paddingChar);
628    }
629
630    StandardBaseEncoding(Alphabet alphabet, @Nullable Character paddingChar) {
631      this.alphabet = checkNotNull(alphabet);
632      checkArgument(
633          paddingChar == null || !alphabet.matches(paddingChar),
634          "Padding character %s was already in alphabet",
635          paddingChar);
636      this.paddingChar = paddingChar;
637    }
638
639    @Override
640    int maxEncodedSize(int bytes) {
641      return alphabet.charsPerChunk * divide(bytes, alphabet.bytesPerChunk, CEILING);
642    }
643
644    @J2ktIncompatible
645    @GwtIncompatible // Writer,OutputStream
646    @Override
647    public OutputStream encodingStream(Writer out) {
648      checkNotNull(out);
649      return new OutputStream() {
650        @GuardedBy("this")
651        int bitBuffer = 0;
652
653        @GuardedBy("this")
654        int bitBufferLength = 0;
655
656        @GuardedBy("this")
657        int writtenChars = 0;
658
659        @GuardedBy("this")
660        boolean closed = false;
661
662        @Override
663        public synchronized void write(byte[] b, int off, int len) throws IOException {
664          super.write(b, off, len);
665        }
666
667        @Override
668        public synchronized void write(int b) throws IOException {
669          checkOpen();
670          bitBuffer <<= 8;
671          bitBuffer |= b & 0xFF;
672          bitBufferLength += 8;
673          while (bitBufferLength >= alphabet.bitsPerChar) {
674            int charIndex = (bitBuffer >> (bitBufferLength - alphabet.bitsPerChar)) & alphabet.mask;
675            out.write(alphabet.encode(charIndex));
676            writtenChars++;
677            bitBufferLength -= alphabet.bitsPerChar;
678          }
679        }
680
681        @Override
682        public synchronized void flush() throws IOException {
683          checkOpen();
684          out.flush();
685        }
686
687        @GuardedBy("this")
688        void checkOpen() throws IOException {
689          if (closed) {
690            throw new IOException("stream closed");
691          }
692        }
693
694        @Override
695        public synchronized void close() throws IOException {
696          if (closed) {
697            return;
698          }
699          closed = true;
700          if (bitBufferLength > 0) {
701            int charIndex = (bitBuffer << (alphabet.bitsPerChar - bitBufferLength)) & alphabet.mask;
702            out.write(alphabet.encode(charIndex));
703            writtenChars++;
704            if (paddingChar != null) {
705              while (writtenChars % alphabet.charsPerChunk != 0) {
706                out.write(paddingChar);
707                writtenChars++;
708              }
709            }
710          }
711          out.close();
712        }
713      };
714    }
715
716    @Override
717    void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException {
718      checkNotNull(target);
719      checkPositionIndexes(off, off + len, bytes.length);
720      for (int i = 0; i < len; i += alphabet.bytesPerChunk) {
721        encodeChunkTo(target, bytes, off + i, min(alphabet.bytesPerChunk, len - i));
722      }
723    }
724
725    final void encodeChunkTo(Appendable target, byte[] bytes, int off, int len) throws IOException {
726      checkNotNull(target);
727      checkPositionIndexes(off, off + len, bytes.length);
728      checkArgument(len <= alphabet.bytesPerChunk);
729      long bitBuffer = 0;
730      for (int i = 0; i < len; ++i) {
731        bitBuffer |= bytes[off + i] & 0xFF;
732        bitBuffer <<= 8; // Add additional zero byte in the end.
733      }
734      // Position of first character is length of bitBuffer minus bitsPerChar.
735      int bitOffset = (len + 1) * 8 - alphabet.bitsPerChar;
736      int bitsProcessed = 0;
737      while (bitsProcessed < len * 8) {
738        int charIndex = (int) (bitBuffer >>> (bitOffset - bitsProcessed)) & alphabet.mask;
739        target.append(alphabet.encode(charIndex));
740        bitsProcessed += alphabet.bitsPerChar;
741      }
742      if (paddingChar != null) {
743        while (bitsProcessed < alphabet.bytesPerChunk * 8) {
744          target.append(paddingChar);
745          bitsProcessed += alphabet.bitsPerChar;
746        }
747      }
748    }
749
750    @Override
751    int maxDecodedSize(int chars) {
752      return (int) ((alphabet.bitsPerChar * (long) chars + 7L) / 8L);
753    }
754
755    @Override
756    final CharSequence trimTrailingPadding(CharSequence chars) {
757      checkNotNull(chars);
758      if (paddingChar == null) {
759        return chars;
760      }
761      char padChar = paddingChar;
762      int l;
763      for (l = chars.length() - 1; l >= 0; l--) {
764        if (chars.charAt(l) != padChar) {
765          break;
766        }
767      }
768      return chars.subSequence(0, l + 1);
769    }
770
771    @Override
772    public boolean canDecode(CharSequence chars) {
773      checkNotNull(chars);
774      chars = trimTrailingPadding(chars);
775      if (!alphabet.isValidPaddingStartPosition(chars.length())) {
776        return false;
777      }
778      for (int i = 0; i < chars.length(); i++) {
779        if (!alphabet.canDecode(chars.charAt(i))) {
780          return false;
781        }
782      }
783      return true;
784    }
785
786    @Override
787    int decodeTo(byte[] target, CharSequence chars) throws DecodingException {
788      checkNotNull(target);
789      chars = trimTrailingPadding(chars);
790      if (!alphabet.isValidPaddingStartPosition(chars.length())) {
791        throw new DecodingException("Invalid input length " + chars.length());
792      }
793      int bytesWritten = 0;
794      for (int charIdx = 0; charIdx < chars.length(); charIdx += alphabet.charsPerChunk) {
795        long chunk = 0;
796        int charsProcessed = 0;
797        for (int i = 0; i < alphabet.charsPerChunk; i++) {
798          chunk <<= alphabet.bitsPerChar;
799          if (charIdx + i < chars.length()) {
800            chunk |= alphabet.decode(chars.charAt(charIdx + charsProcessed++));
801          }
802        }
803        int minOffset = alphabet.bytesPerChunk * 8 - charsProcessed * alphabet.bitsPerChar;
804        for (int offset = (alphabet.bytesPerChunk - 1) * 8; offset >= minOffset; offset -= 8) {
805          target[bytesWritten++] = (byte) ((chunk >>> offset) & 0xFF);
806        }
807      }
808      return bytesWritten;
809    }
810
811    @Override
812    @J2ktIncompatible
813    @GwtIncompatible // Reader,InputStream
814    public InputStream decodingStream(Reader reader) {
815      checkNotNull(reader);
816      return new InputStream() {
817        @GuardedBy("this")
818        int bitBuffer = 0;
819
820        @GuardedBy("this")
821        int bitBufferLength = 0;
822
823        @GuardedBy("this")
824        long readChars = 0;
825
826        @GuardedBy("this")
827        boolean hitPadding = false;
828
829        @Override
830        public synchronized int read() throws IOException {
831          while (true) {
832            int readChar = reader.read();
833            if (readChar == -1) {
834              if (!hitPadding && !alphabet.isValidPaddingStartPosition(readChars)) {
835                throw new DecodingException("Invalid input length " + readChars);
836              }
837              return -1;
838            }
839            readChars++;
840            char ch = (char) readChar;
841            if (paddingChar != null && paddingChar == ch) {
842              if (!hitPadding
843                  && (readChars == 1 || !alphabet.isValidPaddingStartPosition(readChars - 1))) {
844                throw new DecodingException("Padding cannot start at index " + readChars);
845              }
846              hitPadding = true;
847            } else if (hitPadding) {
848              throw new DecodingException(
849                  "Expected padding character but found '" + ch + "' at index " + readChars);
850            } else {
851              bitBuffer <<= alphabet.bitsPerChar;
852              bitBuffer |= alphabet.decode(ch);
853              bitBufferLength += alphabet.bitsPerChar;
854
855              if (bitBufferLength >= 8) {
856                bitBufferLength -= 8;
857                return (bitBuffer >> bitBufferLength) & 0xFF;
858              }
859            }
860          }
861        }
862
863        @Override
864        public synchronized int read(byte[] buf, int off, int len) throws IOException {
865          // Overriding this to work around the fact that InputStream's default implementation of
866          // this method will silently swallow exceptions thrown by the single-byte read() method
867          // (other than on the first call to it), which in this case can cause invalid encoded
868          // strings to not throw an exception.
869          // See https://github.com/google/guava/issues/3542
870          checkPositionIndexes(off, off + len, buf.length);
871
872          int i = off;
873          for (; i < off + len; i++) {
874            int b = read();
875            if (b == -1) {
876              int read = i - off;
877              return read == 0 ? -1 : read;
878            }
879            buf[i] = (byte) b;
880          }
881          return i - off;
882        }
883
884        @Override
885        public synchronized void close() throws IOException {
886          reader.close();
887        }
888      };
889    }
890
891    @Override
892    public BaseEncoding omitPadding() {
893      return (paddingChar == null) ? this : newInstance(alphabet, null);
894    }
895
896    @Override
897    public BaseEncoding withPadChar(char padChar) {
898      if (8 % alphabet.bitsPerChar == 0 || (paddingChar != null && paddingChar == padChar)) {
899        return this;
900      } else {
901        return newInstance(alphabet, padChar);
902      }
903    }
904
905    @Override
906    public BaseEncoding withSeparator(String separator, int afterEveryChars) {
907      for (int i = 0; i < separator.length(); i++) {
908        checkArgument(
909            !alphabet.matches(separator.charAt(i)),
910            "Separator (%s) cannot contain alphabet characters",
911            separator);
912      }
913      if (paddingChar != null) {
914        checkArgument(
915            separator.indexOf(paddingChar) < 0,
916            "Separator (%s) cannot contain padding character",
917            separator);
918      }
919      return new SeparatedBaseEncoding(this, separator, afterEveryChars);
920    }
921
922    @LazyInit private volatile @Nullable BaseEncoding upperCase;
923    @LazyInit private volatile @Nullable BaseEncoding lowerCase;
924    @LazyInit private volatile @Nullable BaseEncoding ignoreCase;
925
926    @Override
927    public BaseEncoding upperCase() {
928      BaseEncoding result = upperCase;
929      if (result == null) {
930        Alphabet upper = alphabet.upperCase();
931        result = upperCase = (upper == alphabet) ? this : newInstance(upper, paddingChar);
932      }
933      return result;
934    }
935
936    @Override
937    public BaseEncoding lowerCase() {
938      BaseEncoding result = lowerCase;
939      if (result == null) {
940        Alphabet lower = alphabet.lowerCase();
941        result = lowerCase = (lower == alphabet) ? this : newInstance(lower, paddingChar);
942      }
943      return result;
944    }
945
946    @Override
947    public BaseEncoding ignoreCase() {
948      BaseEncoding result = ignoreCase;
949      if (result == null) {
950        Alphabet ignore = alphabet.ignoreCase();
951        result = ignoreCase = (ignore == alphabet) ? this : newInstance(ignore, paddingChar);
952      }
953      return result;
954    }
955
956    BaseEncoding newInstance(Alphabet alphabet, @Nullable Character paddingChar) {
957      return new StandardBaseEncoding(alphabet, paddingChar);
958    }
959
960    @Override
961    public final String toString() {
962      StringBuilder builder = new StringBuilder("BaseEncoding.");
963      builder.append(alphabet);
964      if (8 % alphabet.bitsPerChar != 0) {
965        if (paddingChar == null) {
966          builder.append(".omitPadding()");
967        } else {
968          builder.append(".withPadChar('").append(paddingChar).append("')");
969        }
970      }
971      return builder.toString();
972    }
973
974    @Override
975    public final boolean equals(@Nullable Object other) {
976      if (other instanceof StandardBaseEncoding) {
977        StandardBaseEncoding that = (StandardBaseEncoding) other;
978        return this.alphabet.equals(that.alphabet)
979            && Objects.equals(this.paddingChar, that.paddingChar);
980      }
981      return false;
982    }
983
984    @Override
985    public final int hashCode() {
986      return alphabet.hashCode() ^ Objects.hashCode(paddingChar);
987    }
988  }
989
990  private static final class Base16Encoding extends StandardBaseEncoding {
991    final char[] encoding = new char[512];
992
993    Base16Encoding(String name, String alphabetChars) {
994      this(new Alphabet(name, alphabetChars.toCharArray()));
995    }
996
997    private Base16Encoding(Alphabet alphabet) {
998      super(alphabet, null);
999      checkArgument(alphabet.chars.length == 16);
1000      for (int i = 0; i < 256; ++i) {
1001        encoding[i] = alphabet.encode(i >>> 4);
1002        encoding[i | 0x100] = alphabet.encode(i & 0xF);
1003      }
1004    }
1005
1006    @Override
1007    void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException {
1008      checkNotNull(target);
1009      checkPositionIndexes(off, off + len, bytes.length);
1010      for (int i = 0; i < len; ++i) {
1011        int b = bytes[off + i] & 0xFF;
1012        target.append(encoding[b]);
1013        target.append(encoding[b | 0x100]);
1014      }
1015    }
1016
1017    @Override
1018    int decodeTo(byte[] target, CharSequence chars) throws DecodingException {
1019      checkNotNull(target);
1020      if (chars.length() % 2 == 1) {
1021        throw new DecodingException("Invalid input length " + chars.length());
1022      }
1023      int bytesWritten = 0;
1024      for (int i = 0; i < chars.length(); i += 2) {
1025        int decoded = alphabet.decode(chars.charAt(i)) << 4 | alphabet.decode(chars.charAt(i + 1));
1026        target[bytesWritten++] = (byte) decoded;
1027      }
1028      return bytesWritten;
1029    }
1030
1031    @Override
1032    BaseEncoding newInstance(Alphabet alphabet, @Nullable Character paddingChar) {
1033      return new Base16Encoding(alphabet);
1034    }
1035  }
1036
1037  private static final class Base64Encoding extends StandardBaseEncoding {
1038    Base64Encoding(String name, String alphabetChars, @Nullable Character paddingChar) {
1039      this(new Alphabet(name, alphabetChars.toCharArray()), paddingChar);
1040    }
1041
1042    private Base64Encoding(Alphabet alphabet, @Nullable Character paddingChar) {
1043      super(alphabet, paddingChar);
1044      checkArgument(alphabet.chars.length == 64);
1045    }
1046
1047    @Override
1048    void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException {
1049      checkNotNull(target);
1050      checkPositionIndexes(off, off + len, bytes.length);
1051      int i = off;
1052      for (int remaining = len; remaining >= 3; remaining -= 3) {
1053        int chunk = (bytes[i++] & 0xFF) << 16 | (bytes[i++] & 0xFF) << 8 | (bytes[i++] & 0xFF);
1054        target.append(alphabet.encode(chunk >>> 18));
1055        target.append(alphabet.encode((chunk >>> 12) & 0x3F));
1056        target.append(alphabet.encode((chunk >>> 6) & 0x3F));
1057        target.append(alphabet.encode(chunk & 0x3F));
1058      }
1059      if (i < off + len) {
1060        encodeChunkTo(target, bytes, i, off + len - i);
1061      }
1062    }
1063
1064    @Override
1065    int decodeTo(byte[] target, CharSequence chars) throws DecodingException {
1066      checkNotNull(target);
1067      chars = trimTrailingPadding(chars);
1068      if (!alphabet.isValidPaddingStartPosition(chars.length())) {
1069        throw new DecodingException("Invalid input length " + chars.length());
1070      }
1071      int bytesWritten = 0;
1072      for (int i = 0; i < chars.length(); ) {
1073        int chunk = alphabet.decode(chars.charAt(i++)) << 18;
1074        chunk |= alphabet.decode(chars.charAt(i++)) << 12;
1075        target[bytesWritten++] = (byte) (chunk >>> 16);
1076        if (i < chars.length()) {
1077          chunk |= alphabet.decode(chars.charAt(i++)) << 6;
1078          target[bytesWritten++] = (byte) ((chunk >>> 8) & 0xFF);
1079          if (i < chars.length()) {
1080            chunk |= alphabet.decode(chars.charAt(i++));
1081            target[bytesWritten++] = (byte) (chunk & 0xFF);
1082          }
1083        }
1084      }
1085      return bytesWritten;
1086    }
1087
1088    @Override
1089    BaseEncoding newInstance(Alphabet alphabet, @Nullable Character paddingChar) {
1090      return new Base64Encoding(alphabet, paddingChar);
1091    }
1092  }
1093
1094  @J2ktIncompatible
1095  @GwtIncompatible
1096  static Reader ignoringReader(Reader delegate, String toIgnore) {
1097    checkNotNull(delegate);
1098    checkNotNull(toIgnore);
1099    return new Reader() {
1100      @Override
1101      public int read() throws IOException {
1102        int readChar;
1103        do {
1104          readChar = delegate.read();
1105        } while (readChar != -1 && toIgnore.indexOf((char) readChar) >= 0);
1106        return readChar;
1107      }
1108
1109      @Override
1110      public int read(char[] cbuf, int off, int len) {
1111        throw new UnsupportedOperationException();
1112      }
1113
1114      @Override
1115      public void close() throws IOException {
1116        delegate.close();
1117      }
1118    };
1119  }
1120
1121  static Appendable separatingAppendable(
1122      Appendable delegate, String separator, int afterEveryChars) {
1123    checkNotNull(delegate);
1124    checkNotNull(separator);
1125    checkArgument(afterEveryChars > 0);
1126    return new Appendable() {
1127      int charsUntilSeparator = afterEveryChars;
1128
1129      @Override
1130      public Appendable append(char c) throws IOException {
1131        if (charsUntilSeparator == 0) {
1132          delegate.append(separator);
1133          charsUntilSeparator = afterEveryChars;
1134        }
1135        delegate.append(c);
1136        charsUntilSeparator--;
1137        return this;
1138      }
1139
1140      @Override
1141      public Appendable append(@Nullable CharSequence chars, int off, int len) {
1142        throw new UnsupportedOperationException();
1143      }
1144
1145      @Override
1146      public Appendable append(@Nullable CharSequence chars) {
1147        throw new UnsupportedOperationException();
1148      }
1149    };
1150  }
1151
1152  @J2ktIncompatible
1153  @GwtIncompatible // Writer
1154  static Writer separatingWriter(Writer delegate, String separator, int afterEveryChars) {
1155    Appendable separatingAppendable = separatingAppendable(delegate, separator, afterEveryChars);
1156    return new Writer() {
1157      @Override
1158      public void write(int c) throws IOException {
1159        separatingAppendable.append((char) c);
1160      }
1161
1162      @Override
1163      public void write(char[] chars, int off, int len) {
1164        throw new UnsupportedOperationException();
1165      }
1166
1167      @Override
1168      public void flush() throws IOException {
1169        delegate.flush();
1170      }
1171
1172      @Override
1173      public void close() throws IOException {
1174        delegate.close();
1175      }
1176    };
1177  }
1178
1179  static final class SeparatedBaseEncoding extends BaseEncoding {
1180    private final BaseEncoding delegate;
1181    private final String separator;
1182    private final int afterEveryChars;
1183
1184    SeparatedBaseEncoding(BaseEncoding delegate, String separator, int afterEveryChars) {
1185      this.delegate = checkNotNull(delegate);
1186      this.separator = checkNotNull(separator);
1187      this.afterEveryChars = afterEveryChars;
1188      checkArgument(
1189          afterEveryChars > 0, "Cannot add a separator after every %s chars", afterEveryChars);
1190    }
1191
1192    @Override
1193    CharSequence trimTrailingPadding(CharSequence chars) {
1194      return delegate.trimTrailingPadding(chars);
1195    }
1196
1197    @Override
1198    int maxEncodedSize(int bytes) {
1199      int unseparatedSize = delegate.maxEncodedSize(bytes);
1200      return unseparatedSize
1201          + separator.length() * divide(max(0, unseparatedSize - 1), afterEveryChars, FLOOR);
1202    }
1203
1204    @J2ktIncompatible
1205    @GwtIncompatible // Writer,OutputStream
1206    @Override
1207    public OutputStream encodingStream(Writer output) {
1208      return delegate.encodingStream(separatingWriter(output, separator, afterEveryChars));
1209    }
1210
1211    @Override
1212    void encodeTo(Appendable target, byte[] bytes, int off, int len) throws IOException {
1213      delegate.encodeTo(separatingAppendable(target, separator, afterEveryChars), bytes, off, len);
1214    }
1215
1216    @Override
1217    int maxDecodedSize(int chars) {
1218      return delegate.maxDecodedSize(chars);
1219    }
1220
1221    @Override
1222    public boolean canDecode(CharSequence chars) {
1223      StringBuilder builder = new StringBuilder();
1224      for (int i = 0; i < chars.length(); i++) {
1225        char c = chars.charAt(i);
1226        if (separator.indexOf(c) < 0) {
1227          builder.append(c);
1228        }
1229      }
1230      return delegate.canDecode(builder);
1231    }
1232
1233    @Override
1234    int decodeTo(byte[] target, CharSequence chars) throws DecodingException {
1235      StringBuilder stripped = new StringBuilder(chars.length());
1236      for (int i = 0; i < chars.length(); i++) {
1237        char c = chars.charAt(i);
1238        if (separator.indexOf(c) < 0) {
1239          stripped.append(c);
1240        }
1241      }
1242      return delegate.decodeTo(target, stripped);
1243    }
1244
1245    @Override
1246    @J2ktIncompatible
1247    @GwtIncompatible // Reader,InputStream
1248    public InputStream decodingStream(Reader reader) {
1249      return delegate.decodingStream(ignoringReader(reader, separator));
1250    }
1251
1252    @Override
1253    public BaseEncoding omitPadding() {
1254      return delegate.omitPadding().withSeparator(separator, afterEveryChars);
1255    }
1256
1257    @Override
1258    public BaseEncoding withPadChar(char padChar) {
1259      return delegate.withPadChar(padChar).withSeparator(separator, afterEveryChars);
1260    }
1261
1262    @Override
1263    public BaseEncoding withSeparator(String separator, int afterEveryChars) {
1264      throw new UnsupportedOperationException("Already have a separator");
1265    }
1266
1267    @Override
1268    public BaseEncoding upperCase() {
1269      return delegate.upperCase().withSeparator(separator, afterEveryChars);
1270    }
1271
1272    @Override
1273    public BaseEncoding lowerCase() {
1274      return delegate.lowerCase().withSeparator(separator, afterEveryChars);
1275    }
1276
1277    @Override
1278    public BaseEncoding ignoreCase() {
1279      return delegate.ignoreCase().withSeparator(separator, afterEveryChars);
1280    }
1281
1282    @Override
1283    public String toString() {
1284      return delegate + ".withSeparator(\"" + separator + "\", " + afterEveryChars + ")";
1285    }
1286  }
1287}