JDK

java.text.NumberFormatクラス

/*
 * Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.text;
import java.io.InvalidObjectException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.text.spi.NumberFormatProvider;
import java.util.Currency;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import sun.util.locale.provider.LocaleProviderAdapter;
import sun.util.locale.provider.LocaleServiceProviderPool;
public abstract class NumberFormat extends Format  {
    public static final int INTEGER_FIELD = 0;
    public static final int FRACTION_FIELD = 1;
    protected NumberFormat() {
    }
    @Override
    public StringBuffer format(Object number,
                               StringBuffer toAppendTo,
                               FieldPosition pos) {
        if (number instanceof Long || number instanceof Integer ||
            number instanceof Short || number instanceof Byte ||
            number instanceof AtomicInteger || number instanceof AtomicLong ||
            (number instanceof BigInteger &&
             ((BigInteger)number).bitLength() < 64)) {
            return format(((Number)number).longValue(), toAppendTo, pos);
        } else if (number instanceof Number) {
            return format(((Number)number).doubleValue(), toAppendTo, pos);
        } else {
            throw new IllegalArgumentException("Cannot format given Object as a Number");
        }
    }
    @Override
    public final Object parseObject(String source, ParsePosition pos) {
        return parse(source, pos);
    }
    public final String format(double number) {
        // Use fast-path for double result if that works
        String result = fastFormat(number);
        if (result != null)
            return result;
        return format(number, new StringBuffer(),
                      DontCareFieldPosition.INSTANCE).toString();
    }
    /*
     * fastFormat() is supposed to be implemented in concrete subclasses only.
     * Default implem always returns null.
     */
    String fastFormat(double number) { return null; }
    public final String format(long number) {
        return format(number, new StringBuffer(),
                      DontCareFieldPosition.INSTANCE).toString();
    }
    public abstract StringBuffer format(double number,
                                        StringBuffer toAppendTo,
                                        FieldPosition pos);
    public abstract StringBuffer format(long number,
                                        StringBuffer toAppendTo,
                                        FieldPosition pos);
    public abstract Number parse(String source, ParsePosition parsePosition);
    public Number parse(String source) throws ParseException {
        ParsePosition parsePosition = new ParsePosition(0);
        Number result = parse(source, parsePosition);
        if (parsePosition.index == 0) {
            throw new ParseException("Unparseable number: \"" + source + "\"",
                                     parsePosition.errorIndex);
        }
        return result;
    }
    public boolean isParseIntegerOnly() {
        return parseIntegerOnly;
    }
    public void setParseIntegerOnly(boolean value) {
        parseIntegerOnly = value;
    }
    //============== Locale Stuff =====================
    public static final NumberFormat getInstance() {
        return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, NUMBERSTYLE);
    }
    public static NumberFormat getInstance(Locale inLocale) {
        return getInstance(inLocale, null, NUMBERSTYLE);
    }
    public static final NumberFormat getNumberInstance() {
        return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, NUMBERSTYLE);
    }
    public static NumberFormat getNumberInstance(Locale inLocale) {
        return getInstance(inLocale, null, NUMBERSTYLE);
    }
    public static final NumberFormat getIntegerInstance() {
        return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, INTEGERSTYLE);
    }
    public static NumberFormat getIntegerInstance(Locale inLocale) {
        return getInstance(inLocale, null, INTEGERSTYLE);
    }
    public static final NumberFormat getCurrencyInstance() {
        return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, CURRENCYSTYLE);
    }
    public static NumberFormat getCurrencyInstance(Locale inLocale) {
        return getInstance(inLocale, null, CURRENCYSTYLE);
    }
    public static final NumberFormat getPercentInstance() {
        return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, PERCENTSTYLE);
    }
    public static NumberFormat getPercentInstance(Locale inLocale) {
        return getInstance(inLocale, null, PERCENTSTYLE);
    }
    /*public*/ final static NumberFormat getScientificInstance() {
        return getInstance(Locale.getDefault(Locale.Category.FORMAT), null, SCIENTIFICSTYLE);
    }
    /*public*/ static NumberFormat getScientificInstance(Locale inLocale) {
        return getInstance(inLocale, null, SCIENTIFICSTYLE);
    }
    public static NumberFormat getCompactNumberInstance() {
        return getInstance(Locale.getDefault(
                Locale.Category.FORMAT), NumberFormat.Style.SHORT, COMPACTSTYLE);
    }
    public static NumberFormat getCompactNumberInstance(Locale locale,
            NumberFormat.Style formatStyle) {
        Objects.requireNonNull(locale);
        Objects.requireNonNull(formatStyle);
        return getInstance(locale, formatStyle, COMPACTSTYLE);
    }
    public static Locale[] getAvailableLocales() {
        LocaleServiceProviderPool pool =
            LocaleServiceProviderPool.getPool(NumberFormatProvider.class);
        return pool.getAvailableLocales();
    }
    @Override
    public int hashCode() {
        return maximumIntegerDigits * 37 + maxFractionDigits;
        // just enough fields for a reasonable distribution
    }
    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (this == obj) {
            return true;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        NumberFormat other = (NumberFormat) obj;
        return (maximumIntegerDigits == other.maximumIntegerDigits
            && minimumIntegerDigits == other.minimumIntegerDigits
            && maximumFractionDigits == other.maximumFractionDigits
            && minimumFractionDigits == other.minimumFractionDigits
            && groupingUsed == other.groupingUsed
            && parseIntegerOnly == other.parseIntegerOnly);
    }
    @Override
    public Object clone() {
        NumberFormat other = (NumberFormat) super.clone();
        return other;
    }
    public boolean isGroupingUsed() {
        return groupingUsed;
    }
    public void setGroupingUsed(boolean newValue) {
        groupingUsed = newValue;
    }
    public int getMaximumIntegerDigits() {
        return maximumIntegerDigits;
    }
    public void setMaximumIntegerDigits(int newValue) {
        maximumIntegerDigits = Math.max(0,newValue);
        if (minimumIntegerDigits > maximumIntegerDigits) {
            minimumIntegerDigits = maximumIntegerDigits;
        }
    }
    public int getMinimumIntegerDigits() {
        return minimumIntegerDigits;
    }
    public void setMinimumIntegerDigits(int newValue) {
        minimumIntegerDigits = Math.max(0,newValue);
        if (minimumIntegerDigits > maximumIntegerDigits) {
            maximumIntegerDigits = minimumIntegerDigits;
        }
    }
    public int getMaximumFractionDigits() {
        return maximumFractionDigits;
    }
    public void setMaximumFractionDigits(int newValue) {
        maximumFractionDigits = Math.max(0,newValue);
        if (maximumFractionDigits < minimumFractionDigits) {
            minimumFractionDigits = maximumFractionDigits;
        }
    }
    public int getMinimumFractionDigits() {
        return minimumFractionDigits;
    }
    public void setMinimumFractionDigits(int newValue) {
        minimumFractionDigits = Math.max(0,newValue);
        if (maximumFractionDigits < minimumFractionDigits) {
            maximumFractionDigits = minimumFractionDigits;
        }
    }
    public Currency getCurrency() {
        throw new UnsupportedOperationException();
    }
    public void setCurrency(Currency currency) {
        throw new UnsupportedOperationException();
    }
    public RoundingMode getRoundingMode() {
        throw new UnsupportedOperationException();
    }
    public void setRoundingMode(RoundingMode roundingMode) {
        throw new UnsupportedOperationException();
    }
    // =======================privates===============================
    private static NumberFormat getInstance(Locale desiredLocale,
                                            Style formatStyle, int choice) {
        LocaleProviderAdapter adapter;
        adapter = LocaleProviderAdapter.getAdapter(NumberFormatProvider.class,
                desiredLocale);
        NumberFormat numberFormat = getInstance(adapter, desiredLocale,
                formatStyle, choice);
        if (numberFormat == null) {
            numberFormat = getInstance(LocaleProviderAdapter.forJRE(),
                    desiredLocale, formatStyle, choice);
        }
        return numberFormat;
    }
    private static NumberFormat getInstance(LocaleProviderAdapter adapter,
                                            Locale locale, Style formatStyle,
                                            int choice) {
        NumberFormatProvider provider = adapter.getNumberFormatProvider();
        NumberFormat numberFormat = null;
        switch (choice) {
        case NUMBERSTYLE:
            numberFormat = provider.getNumberInstance(locale);
            break;
        case PERCENTSTYLE:
            numberFormat = provider.getPercentInstance(locale);
            break;
        case CURRENCYSTYLE:
            numberFormat = provider.getCurrencyInstance(locale);
            break;
        case INTEGERSTYLE:
            numberFormat = provider.getIntegerInstance(locale);
            break;
        case COMPACTSTYLE:
            numberFormat = provider.getCompactNumberInstance(locale, formatStyle);
            break;
        }
        return numberFormat;
    }
    private void readObject(ObjectInputStream stream)
         throws IOException, ClassNotFoundException
    {
        stream.defaultReadObject();
        if (serialVersionOnStream < 1) {
            // Didn't have additional int fields, reassign to use them.
            maximumIntegerDigits = maxIntegerDigits;
            minimumIntegerDigits = minIntegerDigits;
            maximumFractionDigits = maxFractionDigits;
            minimumFractionDigits = minFractionDigits;
        }
        if (minimumIntegerDigits > maximumIntegerDigits ||
            minimumFractionDigits > maximumFractionDigits ||
            minimumIntegerDigits < 0 || minimumFractionDigits < 0) {
            throw new InvalidObjectException("Digit count range invalid");
        }
        serialVersionOnStream = currentSerialVersion;
    }
    private void writeObject(ObjectOutputStream stream)
         throws IOException
    {
        maxIntegerDigits = (maximumIntegerDigits > Byte.MAX_VALUE) ?
                           Byte.MAX_VALUE : (byte)maximumIntegerDigits;
        minIntegerDigits = (minimumIntegerDigits > Byte.MAX_VALUE) ?
                           Byte.MAX_VALUE : (byte)minimumIntegerDigits;
        maxFractionDigits = (maximumFractionDigits > Byte.MAX_VALUE) ?
                            Byte.MAX_VALUE : (byte)maximumFractionDigits;
        minFractionDigits = (minimumFractionDigits > Byte.MAX_VALUE) ?
                            Byte.MAX_VALUE : (byte)minimumFractionDigits;
        stream.defaultWriteObject();
    }
    // Constants used by factory methods to specify a style of format.
    private static final int NUMBERSTYLE = 0;
    private static final int CURRENCYSTYLE = 1;
    private static final int PERCENTSTYLE = 2;
    private static final int SCIENTIFICSTYLE = 3;
    private static final int INTEGERSTYLE = 4;
    private static final int COMPACTSTYLE = 5;
    private boolean groupingUsed = true;
    private byte    maxIntegerDigits = 40;
    private byte    minIntegerDigits = 1;
    private byte    maxFractionDigits = 3;    // invariant, >= minFractionDigits
    private byte    minFractionDigits = 0;
    private boolean parseIntegerOnly = false;
    // new fields for 1.2.  byte is too small for integer digits.
    private int    maximumIntegerDigits = 40;
    private int    minimumIntegerDigits = 1;
    private int    maximumFractionDigits = 3;    // invariant, >= minFractionDigits
    private int    minimumFractionDigits = 0;
    static final int currentSerialVersion = 1;
    private int serialVersionOnStream = currentSerialVersion;
    // Removed "implements Cloneable" clause.  Needs to update serialization
    // ID for backward compatibility.
    static final long serialVersionUID = -2308460125733713944L;
    //
    // class for AttributedCharacterIterator attributes
    //
    public static class Field extends Format.Field {
        // Proclaim serial compatibility with 1.4 FCS
        private static final long serialVersionUID = 7494728892700160890L;
        // table of all instances in this class, used by readResolve
        private static final Map<String, Field> instanceMap = new HashMap<>(11);
        protected Field(String name) {
            super(name);
            if (this.getClass() == NumberFormat.Field.class) {
                instanceMap.put(name, this);
            }
        }
        @Override
        protected Object readResolve() throws InvalidObjectException {
            if (this.getClass() != NumberFormat.Field.class) {
                throw new InvalidObjectException("subclass didn't correctly implement readResolve");
            }
            Object instance = instanceMap.get(getName());
            if (instance != null) {
                return instance;
            } else {
                throw new InvalidObjectException("unknown attribute name");
            }
        }
        public static final Field INTEGER = new Field("integer");
        public static final Field FRACTION = new Field("fraction");
        public static final Field EXPONENT = new Field("exponent");
        public static final Field DECIMAL_SEPARATOR =
                            new Field("decimal separator");
        public static final Field SIGN = new Field("sign");
        public static final Field GROUPING_SEPARATOR =
                            new Field("grouping separator");
        public static final Field EXPONENT_SYMBOL = new
                            Field("exponent symbol");
        public static final Field PERCENT = new Field("percent");
        public static final Field PERMILLE = new Field("per mille");
        public static final Field CURRENCY = new Field("currency");
        public static final Field EXPONENT_SIGN = new Field("exponent sign");
        public static final Field PREFIX = new Field("prefix");
        public static final Field SUFFIX = new Field("suffix");
    }
    public enum Style {
        SHORT,
        LONG
    }
}

java.text.NumberFormatクラスは数値を書式化するための抽象クラスである。ソースコードは,1300行を超える比較的大きなクラスである。

コンストラクタはprotectedであり,サブクラスからしか呼び出せないが,中身は空である。

format(Object number, StringBuffer toAppendTo, FieldPosition pos)メソッドは,このクラスの主要なメソッドであり,数値を書式化する。数値が整数型,つまり,Long,Integer,Short,Byte,AtomicInteger,AtomicLong,BigInteger,および,64ビット未満のBigIntegerの場合は, 数値をNumber型にキャストした上で,抽象メソッド format(long number, StringBuffer toAppendTo, FieldPosition pos)へ委譲している。数値がNumber型の場合は,浮動小数点型のdoubleにキャストして抽象メソッドformat(double number, StringBuffer toAppendTo, FieldPosition pos)へ委譲している。これら以外の型の場合は,IllegalArgumentExceptionをスローしている。

parseObject(String source, ParsePosition pos)メソッドは,文字列を解析して数値を取得するものである。抽象メソッドparse( String source, ParsePosition parsePosition )へ委譲している。

format(double number)メソッドは,double型を文字列にする。まずfastFormat(double number)メソッドでフォーマットを試みるが,このクラスの fastFormat(double number) メソッドは常にnullを返す実装となっているため,この部分はサブクラスでオーバーライドしない限りは意味がない。従って,format(number, new StringBuffer(), DontCareFieldPosition. INSTANCE ) .toString()が実行されて返される。

format(long number)メソッドも同様にformat(number, new StringBuffer(), DontCareFieldPosition .INSTANCE ).toString()を呼び出すのみである。

parse(String source)メソッドは,文字列を解析して数値に変換する。解析開始位置 parsePosition はディフォルト値0を用いてparse(source, parsePosition)を実行する。

中段からは,ロケールに対応したインスタンスを取得するためのメソッドが定義されている。

  • getInstance()
  • getInstance(Locale inLocale)
  • getNumberInstance()
  • getNumberInstance(Locale inLocale)
  • getIntegerInstance()
  • getIntegerInstance(Locale inLocale)
  • getCurrencyInstance()
  • getCurrencyInstance(Locale inLocale)
  • getPercentInstance()
  • getPercentInstance(Locale inLocale)
  • getScientificInstance()
  • getScientificInstance(Locale inLocale)
  • getCompactNumberInstance()
  • getCompactNumberInstance(Locale locale, NumberFormat.Style formatStyle)

getAvailableLocales()メソッドでは,使用可能なロケールオブジェクトの配列を返す。

getCurrency()メソッドやsetCurrency(Currency currency)メソッド,getRoundingMode()メソッド,setRoundingMode(RoundingMode roundingMode)メソッドは実装されておらず,実行するとUnsupportedOperationExceptionがスローされるようになっている。

クラスの後段に各種のインスタンス変数定義が置かれている。最後の部分には,インナークラスFieldとStyleというenumが定義されている。

JDK

java.text.Formatクラス

/*
 * Copyright (c) 1996, 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.text;
import java.io.Serializable;
public abstract class Format implements Serializable, Cloneable {
    private static final long serialVersionUID = -299282585814624189L;
    protected Format() {
    }
    public final String format (Object obj) {
        return format(obj, new StringBuffer(), new FieldPosition(0)).toString();
    }
    public abstract StringBuffer format(Object obj,
                    StringBuffer toAppendTo,
                    FieldPosition pos);
    public AttributedCharacterIterator formatToCharacterIterator(Object obj) {
        return createAttributedCharacterIterator(format(obj));
    }
    public abstract Object parseObject (String source, ParsePosition pos);
    public Object parseObject(String source) throws ParseException {
        ParsePosition pos = new ParsePosition(0);
        Object result = parseObject(source, pos);
        if (pos.index == 0) {
            throw new ParseException("Format.parseObject(String) failed",
                pos.errorIndex);
        }
        return result;
    }
    public Object clone() {
        try {
            return super.clone();
        } catch (CloneNotSupportedException e) {
            // will never happen
            throw new InternalError(e);
        }
    }
    //
    // Convenience methods for creating AttributedCharacterIterators from
    // different parameters.
    //
    AttributedCharacterIterator createAttributedCharacterIterator(String s) {
        AttributedString as = new AttributedString(s);
        return as.getIterator();
    }
    AttributedCharacterIterator createAttributedCharacterIterator(
                       AttributedCharacterIterator[] iterators) {
        AttributedString as = new AttributedString(iterators);
        return as.getIterator();
    }
    AttributedCharacterIterator createAttributedCharacterIterator(
                      String string, AttributedCharacterIterator.Attribute key,
                      Object value) {
        AttributedString as = new AttributedString(string);
        as.addAttribute(key, value);
        return as.getIterator();
    }
    AttributedCharacterIterator createAttributedCharacterIterator(
              AttributedCharacterIterator iterator,
              AttributedCharacterIterator.Attribute key, Object value) {
        AttributedString as = new AttributedString(iterator);
        as.addAttribute(key, value);
        return as.getIterator();
    }
    public static class Field extends AttributedCharacterIterator.Attribute {
        // Proclaim serial compatibility with 1.4 FCS
        private static final long serialVersionUID = 276966692217360283L;
        protected Field(String name) {
            super(name);
        }
    }
    interface FieldDelegate {
        public void formatted(Format.Field attr, Object value, int start,
                              int end, StringBuffer buffer);
        public void formatted(int fieldID, Format.Field attr, Object value,
                              int start, int end, StringBuffer buffer);
    }
}

java.text.Formatクラスは数値や日付などを文字列に書式化する各種クラスのスーパークラスとなる抽象クラスである。以下のような抽象メソッドが定義されている。

  • StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos);
  • Object parseObject (String source, ParsePosition pos);

format (Object obj)メソッドでは,上のformat()メソッドにデフォルト値を渡して委譲する。parseObject(String source)メソッドも同様にデフォルト値を上のparseObject()メソッドに渡している。

ソースコードの終段には,staticな内部クラスFieldが定義されている。このクラスはAttributedCharacterIterator.Attributeを継承している。

また,内部インタフェースとして,FieldDelegateが定義されている。

publicメソッドとしては,以下の2つが宣言されている。

  • void formatted(Format.Field attr, Object value, int start, int end, StringBuffer buffer);
  • public void formatted(int fieldID, Format.Field attr, Object value, int start, int end, StringBuffer buffer);

JDK

java.util.zip.CheckedInputStreamクラス

/*
 * Copyright (c) 1996, 2006, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.util.zip;
import java.io.FilterInputStream;
import java.io.InputStream;
import java.io.IOException;
public
class CheckedInputStream extends FilterInputStream {
    private Checksum cksum;
    public CheckedInputStream(InputStream in, Checksum cksum) {
        super(in);
        this.cksum = cksum;
    }
    public int read() throws IOException {
        int b = in.read();
        if (b != -1) {
            cksum.update(b);
        }
        return b;
    }
    public int read(byte[] buf, int off, int len) throws IOException {
        len = in.read(buf, off, len);
        if (len != -1) {
            cksum.update(buf, off, len);
        }
        return len;
    }
    public long skip(long n) throws IOException {
        byte[] buf = new byte[512];
        long total = 0;
        while (total < n) {
            long len = n – total;
            len = read(buf, 0, len < buf.length ? (int)len : buf.length);
            if (len == -1) {
                return total;
            }
            total += len;
        }
        return total;
    }
    public Checksum getChecksum() {
        return cksum;
    }
}

CheckedInputStreamクラスは,入力ストリームのチェックサムを算出しながら読み込むクラスである。java.io.FilterInputStreamクラスを継承している。Checksum型のインスタンス変数cksumが置かれている。

コンストラクタとして,CheckedInputStream((InputStream in, Checksum cksum)のみが定義されており,ディフォルトコンストラクタは定義されていない。コンストラクタで入力ストリームと,チェックサムを計算するためのChecksumインタフェースの実装クラスを渡す。

read()メソッドでは,入力ストリームから1バイトを読込み,チェックサムを更新する。read(byte[] buf, int off, int len)メソッドでは,入力ストリームから指定の長さだけバッファに読み込み,同様にチェックサムを更新する。skip(long n)メソッドは入力ストリーム中で指定したバイト数だけ読み飛ばす。getChecksum()メソッドでインスタンス変数cksmを取得できる。

JDK

java.util.zip.CRC32クラス

CRC32クラスはCRC32チェックサムを計算するChecksumインタフェースを実装したクラスである。デフォルトコンストラクタが宣言されているが,実装コードは空である。インスタンス変数としてint型のcrcが宣言されている。

reset()メソッドでは,インスタンス変数crcに0を代入している。またgetValue()メソッドでは,0xffffffffLとcrcをAND演算してcrcの4バイトをlong型で返している。unsigned int型をサポートしていないが故の苦肉の策のように読める。

update(int crc, int b)メソッドとupdateByteBuffer0(int alder, long addr, int off, int len)がこのクラスの本質的な部分であるが,nativeという修飾子がついており,ネイティブコードで実装されていることを示している。updateBytesCheck()メソッドや,updateByteBuffer()メソッドなどはこれらのネイティブメソッドを呼び出している。

ソースコードの最後にstaticイニシャライザが置かれていて,ZipUtilsクラスのstaticメソッドloadLibrary()を実行している。

/*
 * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.util.zip;
import java.nio.ByteBuffer;
import java.util.Objects;
import sun.nio.ch.DirectBuffer;
import jdk.internal.HotSpotIntrinsicCandidate;
public
class CRC32 implements Checksum {
    private int crc;
    public CRC32() {
    }
    @Override
    public void update(int b) {
        crc = update(crc, b);
    }
    @Override
    public void update(byte[] b, int off, int len) {
        if (b == null) {
            throw new NullPointerException();
        }
        if (off < 0 || len < 0 || off > b.length – len) {
            throw new ArrayIndexOutOfBoundsException();
        }
        crc = updateBytes(crc, b, off, len);
    }
    @Override
    public void update(ByteBuffer buffer) {
        int pos = buffer.position();
        int limit = buffer.limit();
        assert (pos <= limit);
        int rem = limit – pos;
        if (rem <= 0)
            return;
        if (buffer instanceof DirectBuffer) {
            crc = updateByteBuffer(crc, ((DirectBuffer)buffer).address(), pos, rem);
        } else if (buffer.hasArray()) {
            crc = updateBytes(crc, buffer.array(), pos + buffer.arrayOffset(), rem);
        } else {
            byte[] b = new byte[Math.min(buffer.remaining(), 4096)];
            while (buffer.hasRemaining()) {
                int length = Math.min(buffer.remaining(), b.length);
                buffer.get(b, 0, length);
                update(b, 0, length);
            }
        }
        buffer.position(limit);
    }
    @Override
    public void reset() {
        crc = 0;
    }
    @Override
    public long getValue() {
        return (long)crc & 0xffffffffL;
    }
    @HotSpotIntrinsicCandidate
    private static native int update(int crc, int b);
    private static int updateBytes(int crc, byte[] b, int off, int len) {
        updateBytesCheck(b, off, len);
        return updateBytes0(crc, b, off, len);
    }
    @HotSpotIntrinsicCandidate
    private static native int updateBytes0(int crc, byte[] b, int off, int len);
    private static void updateBytesCheck(byte[] b, int off, int len) {
        if (len <= 0) {
            return;  // not an error because updateBytesImpl won't execute if len <= 0
        }
        Objects.requireNonNull(b);
        if (off < 0 || off >= b.length) {
            throw new ArrayIndexOutOfBoundsException(off);
        }
        int endIndex = off + len – 1;
        if (endIndex < 0 || endIndex >= b.length) {
            throw new ArrayIndexOutOfBoundsException(endIndex);
        }
    }
    private static int updateByteBuffer(int alder, long addr,
                                        int off, int len) {
        updateByteBufferCheck(addr);
        return updateByteBuffer0(alder, addr, off, len);
    }
    @HotSpotIntrinsicCandidate
    private static native int updateByteBuffer0(int alder, long addr,
                                                int off, int len);
    private static void updateByteBufferCheck(long addr) {
        // Performs only a null check because bounds checks
        // are not easy to do on raw addresses.
        if (addr == 0L) {
            throw new NullPointerException();
        }
    }
    static {
        ZipUtils.loadLibrary();
    }
}
JDK

java.util.zip.Checksumインタフェース

java.util.zip.Checksumインタフェースはzipファイルの操作で使用するチェックサム計算用クラスのインタフェースを定義したものである。

defaultメソッドとして,update(ByteBuffer)メソッドのみが実装されている。但しこのメソッドもButeBufferからByte[]配列を取得した上で,update(byte[], int, int)メソッドを順次呼び出す仕組みになっている。

/*
 * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.util.zip;
import java.nio.ByteBuffer;
public interface Checksum {
    public void update(int b);
    default public void update(byte[] b) {
        update(b, 0, b.length);
    }
    public void update(byte[] b, int off, int len);
    default public void update(ByteBuffer buffer) {
        int pos = buffer.position();
        int limit = buffer.limit();
        assert (pos <= limit);
        int rem = limit – pos;
        if (rem <= 0) {
            return;
        }
        if (buffer.hasArray()) {
            update(buffer.array(), pos + buffer.arrayOffset(), rem);
        } else {
            byte[] b = new byte[Math.min(buffer.remaining(), 4096)];
            while (buffer.hasRemaining()) {
                int length = Math.min(buffer.remaining(), b.length);
                buffer.get(b, 0, length);
                update(b, 0, length);
            }
        }
        buffer.position(limit);
    }
    public long getValue();
    public void reset();
}
JDK

java.util.jar.JarEntryクラス

java.util.JarEntryクラスはjarファイルの中のエントリー(主にclassファイル)の情報を保持するクラスである。java.util.zip.ZipEntryクラスを継承している。

インスタンス変数は,属性を持つためのAttributes型のattr,電子証明書情報を持つためのCertificate型の配列であるcerts,そしてコード署名を持つCodeSigner型の配列signersが定義されている。

コンストラクタは次の3つが定義されており,引数をとらないディフォルトコンストラクタはない。

  • JarEntry(String name)
  • JarEntry(ZipEntry ze)
  • JarEntry(JarEntry je)

メソッドは,それぞれのインスタンス変数に対するgetterメソッドと,getRealName()というバージョン管理されている場合に実際の名前を返すためのメソッドがある。 getRealName() メソッドにはJava 10から導入されたというコメントが書かれている。

/*
 * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.util.jar;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.security.CodeSigner;
import java.security.cert.Certificate;
public
class JarEntry extends ZipEntry {
    Attributes attr;
    Certificate[] certs;
    CodeSigner[] signers;
    public JarEntry(String name) {
        super(name);
    }
    public JarEntry(ZipEntry ze) {
        super(ze);
    }
    public JarEntry(JarEntry je) {
        this((ZipEntry)je);
        this.attr = je.attr;
        this.certs = je.certs;
        this.signers = je.signers;
    }
    public Attributes getAttributes() throws IOException {
        return attr;
    }
    public Certificate[] getCertificates() {
        return certs == null ? null : certs.clone();
    }
    public CodeSigner[] getCodeSigners() {
        return signers == null ? null : signers.clone();
    }
    public String getRealName() {
        return super.getName();
    }
}
JDK

java.util.jar.JarOutputStreamクラス

java.util.jar.JarOutputStreamクラスはjarファイルにclassファイル等を書き込むOutputStreamである。java.util.zip.ZipOutputStreamクラスを継承している。

プライベートな定数として,JAR_MAGIC = 0xCAFEと定義されている。これはバイナリファイルの先頭に置かれるもので,classファイルであることを示す値である。

コンストラクタは次の2つが定義されている。1つめは,OutputStream型とManifest型の2つの引数を取る。もう一つはOutputStream型の引数のみをとる。一つ目はjarファイルのマニフェストを格納した上で,JarOutputStreamクラスのインスタンスを構築する。

  • public JarOutputStream(OutputStream out, Manifest man)
  • public JarOutputStream(OutputStream out)

putNextEntry(ZipEntry ze)メソッドは,jarファイルにZipEntry型のエントリーを追加する。最初のエントリーの場合は,JAR_MAGICの値を追加する。この時には,ソースファイルの末尾に定義されているset16(byte[] b, int off, int value)メソッドが利用されている。 set16()メソッドは,16bitのバイト配列をリトルエンディアンのCPUアーキテクチャ用にバイト配列を書き込むメソッドである。配列インデックスの小さい方に下位8ビットを,配列インデックスの大きい方に上位の8ビットを書き込む。 JAR_MAGIC に続く2バイトには0をセットしている。その後は,スーパークラスのputNextEntry()メソッドに処理を委譲する。

boolean hasMagic(byte[] edata)メソッドは,引数で渡されたバイト配列にマジックナンバー JAR_MAGICが含まれているかをチェックしている。内部で使われているget16(byte[] b, int off)メソッドは,リトルエンディアンのCPUアーキテクチャ用のバイナリファイルから16bitの値をint型で取得する。

/*
 * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.util.jar;
import java.util.zip.*;
import java.io.*;
public
class JarOutputStream extends ZipOutputStream {
    private static final int JAR_MAGIC = 0xCAFE;
    public JarOutputStream(OutputStream out, Manifest man) throws IOException {
        super(out);
        if (man == null) {
            throw new NullPointerException("man");
        }
        ZipEntry e = new ZipEntry(JarFile.MANIFEST_NAME);
        putNextEntry(e);
        man.write(new BufferedOutputStream(this));
        closeEntry();
    }
    public JarOutputStream(OutputStream out) throws IOException {
        super(out);
    }
    public void putNextEntry(ZipEntry ze) throws IOException {
        if (firstEntry) {
            // Make sure that extra field data for first JAR
            // entry includes JAR magic number id.
            byte[] edata = ze.getExtra();
            if (edata == null || !hasMagic(edata)) {
                if (edata == null) {
                    edata = new byte[4];
                } else {
                    // Prepend magic to existing extra data
                    byte[] tmp = new byte[edata.length + 4];
                    System.arraycopy(edata, 0, tmp, 4, edata.length);
                    edata = tmp;
                }
                set16(edata, 0, JAR_MAGIC); // extra field id
                set16(edata, 2, 0);         // extra field size
                ze.setExtra(edata);
            }
            firstEntry = false;
        }
        super.putNextEntry(ze);
    }
    private boolean firstEntry = true;
    /*
     * Returns true if specified byte array contains the
     * jar magic extra field id.
     */
    private static boolean hasMagic(byte[] edata) {
        try {
            int i = 0;
            while (i < edata.length) {
                if (get16(edata, i) == JAR_MAGIC) {
                    return true;
                }
                i += get16(edata, i + 2) + 4;
            }
        } catch (ArrayIndexOutOfBoundsException e) {
            // Invalid extra field data
        }
        return false;
    }
    /*
     * Fetches unsigned 16-bit value from byte array at specified offset.
     * The bytes are assumed to be in Intel (little-endian) byte order.
     */
    private static int get16(byte[] b, int off) {
        return Byte.toUnsignedInt(b[off]) | ( Byte.toUnsignedInt(b[off+1]) << 8);
    }
    /*
     * Sets 16-bit value at specified offset. The bytes are assumed to
     * be in Intel (little-endian) byte order.
     */
    private static void set16(byte[] b, int off, int value) {
        b[off+0] = (byte)value;
        b[off+1] = (byte)(value >> 8);
    }
}
JDK

java.util.jar.JarFileクラス

java.util.jar.JarFileクラスはJavaのjarファイルを扱うクラスであり,ZipFileクラスを継承している。jarファイルは実体はzipファイルであるので,当然の構成と言える

getManifest()メソッドは,jarファイルのManifestを取得する。entries()メソッドはjarファイルのエントリーをEnumeration<JarEntry>型で返す。また,getJarEntry(String name)メソッドは名前を指定してJarEntryを取得できる。

/*
 * Copyright (c) 1997, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.util.jar;
import jdk.internal.access.SharedSecrets;
import jdk.internal.access.JavaUtilZipFileAccess;
import sun.security.action.GetPropertyAction;
import sun.security.util.ManifestEntryVerifier;
import sun.security.util.SignatureFileVerifier;
import java.io.ByteArrayInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Function;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
public
class JarFile extends ZipFile {
    private final static Runtime.Version BASE_VERSION;
    private final static int BASE_VERSION_FEATURE;
    private final static Runtime.Version RUNTIME_VERSION;
    private final static boolean MULTI_RELEASE_ENABLED;
    private final static boolean MULTI_RELEASE_FORCED;
    private SoftReference<Manifest> manRef;
    private JarEntry manEntry;
    private JarVerifier jv;
    private boolean jvInitialized;
    private boolean verify;
    private final Runtime.Version version;  // current version
    private final int versionFeature;         // version.feature()
    private boolean isMultiRelease;         // is jar multi-release?
    // indicates if Class-Path attribute present
    private boolean hasClassPathAttribute;
    // true if manifest checked for special attributes
    private volatile boolean hasCheckedSpecialAttributes;
    private static final JavaUtilZipFileAccess JUZFA;
    static {
        // Set up JavaUtilJarAccess in SharedSecrets
        SharedSecrets.setJavaUtilJarAccess(new JavaUtilJarAccessImpl());
        // Get JavaUtilZipFileAccess from SharedSecrets
        JUZFA = SharedSecrets.getJavaUtilZipFileAccess();
        // multi-release jar file versions >= 9
        BASE_VERSION = Runtime.Version.parse(Integer.toString(8));
        BASE_VERSION_FEATURE = BASE_VERSION.feature();
        String jarVersion = GetPropertyAction.privilegedGetProperty("jdk.util.jar.version");
        int runtimeVersion = Runtime.version().feature();
        if (jarVersion != null) {
            int jarVer = Integer.parseInt(jarVersion);
            runtimeVersion = (jarVer > runtimeVersion)
                    ? runtimeVersion
                    : Math.max(jarVer, BASE_VERSION_FEATURE);
        }
        RUNTIME_VERSION = Runtime.Version.parse(Integer.toString(runtimeVersion));
        String enableMultiRelease = GetPropertyAction
                .privilegedGetProperty("jdk.util.jar.enableMultiRelease", "true");
        switch (enableMultiRelease) {
            case "true":
            default:
                MULTI_RELEASE_ENABLED = true;
                MULTI_RELEASE_FORCED = false;
                break;
            case "false":
                MULTI_RELEASE_ENABLED = false;
                MULTI_RELEASE_FORCED = false;
                break;
            case "force":
                MULTI_RELEASE_ENABLED = true;
                MULTI_RELEASE_FORCED = true;
                break;
        }
    }
    private static final String META_INF = "META-INF/";
    private static final String META_INF_VERSIONS = META_INF + "versions/";
    public static final String MANIFEST_NAME = META_INF + "MANIFEST.MF";
    public static Runtime.Version baseVersion() {
        return BASE_VERSION;
    }
    public static Runtime.Version runtimeVersion() {
        return RUNTIME_VERSION;
    }
    public JarFile(String name) throws IOException {
        this(new File(name), true, ZipFile.OPEN_READ);
    }
    public JarFile(String name, boolean verify) throws IOException {
        this(new File(name), verify, ZipFile.OPEN_READ);
    }
    public JarFile(File file) throws IOException {
        this(file, true, ZipFile.OPEN_READ);
    }
    public JarFile(File file, boolean verify) throws IOException {
        this(file, verify, ZipFile.OPEN_READ);
    }
    public JarFile(File file, boolean verify, int mode) throws IOException {
        this(file, verify, mode, BASE_VERSION);
    }
    public JarFile(File file, boolean verify, int mode, Runtime.Version version) throws IOException {
        super(file, mode);
        this.verify = verify;
        Objects.requireNonNull(version);
        if (MULTI_RELEASE_FORCED || version.feature() == RUNTIME_VERSION.feature()) {
            // This deals with the common case where the value from JarFile.runtimeVersion() is passed
            this.version = RUNTIME_VERSION;
        } else if (version.feature() <= BASE_VERSION_FEATURE) {
            // This also deals with the common case where the value from JarFile.baseVersion() is passed
            this.version = BASE_VERSION;
        } else {
            // Canonicalize
            this.version = Runtime.Version.parse(Integer.toString(version.feature()));
        }
        this.versionFeature = this.version.feature();
    }
    public final Runtime.Version getVersion() {
        return isMultiRelease() ? this.version : BASE_VERSION;
    }
    public final boolean isMultiRelease() {
        if (isMultiRelease) {
            return true;
        }
        if (MULTI_RELEASE_ENABLED) {
            try {
                checkForSpecialAttributes();
            } catch (IOException io) {
                isMultiRelease = false;
            }
        }
        return isMultiRelease;
    }
    public Manifest getManifest() throws IOException {
        return getManifestFromReference();
    }
    private Manifest getManifestFromReference() throws IOException {
        Manifest man = manRef != null ? manRef.get() : null;
        if (man == null) {
            JarEntry manEntry = getManEntry();
            // If found then load the manifest
            if (manEntry != null) {
                if (verify) {
                    byte[] b = getBytes(manEntry);
                    if (!jvInitialized) {
                        jv = new JarVerifier(b);
                    }
                    man = new Manifest(jv, new ByteArrayInputStream(b), getName());
                } else {
                    man = new Manifest(super.getInputStream(manEntry), getName());
                }
                manRef = new SoftReference<>(man);
            }
        }
        return man;
    }
    private String[] getMetaInfEntryNames() {
        return JUZFA.getMetaInfEntryNames((ZipFile)this);
    }
    public JarEntry getJarEntry(String name) {
        return (JarEntry)getEntry(name);
    }
    public ZipEntry getEntry(String name) {
        JarFileEntry je = getEntry0(name);
        if (isMultiRelease()) {
            return getVersionedEntry(name, je);
        }
        return je;
    }
    public Enumeration<JarEntry> entries() {
        return JUZFA.entries(this, JarFileEntry::new);
    }
    public Stream<JarEntry> stream() {
        return JUZFA.stream(this, JarFileEntry::new);
    }
    public Stream<JarEntry> versionedStream() {
        if (isMultiRelease()) {
            return JUZFA.entryNameStream(this).map(this::getBasename)
                                              .filter(Objects::nonNull)
                                              .distinct()
                                              .map(this::getJarEntry)
                                              .filter(Objects::nonNull);
        }
        return stream();
    }
    /*
     * Invokes {@ZipFile}'s getEntry to Return a {@code JarFileEntry} for the
     * given entry name or {@code null} if not found.
     */
    private JarFileEntry getEntry0(String name) {
        // Not using a lambda/method reference here to optimize startup time
        Function<String, JarEntry> newJarFileEntryFn = new Function<>() {
            @Override
            public JarEntry apply(String name) {
                return new JarFileEntry(name);
            }
        };
        return (JarFileEntry)JUZFA.getEntry(this, name, newJarFileEntryFn);
    }
    private String getBasename(String name) {
        if (name.startsWith(META_INF_VERSIONS)) {
            int off = META_INF_VERSIONS.length();
            int index = name.indexOf('/', off);
            try {
                // filter out dir META-INF/versions/ and META-INF/versions/*/
                // and any entry with version > 'version'
                if (index == -1 || index == (name.length() – 1) ||
                    Integer.parseInt(name, off, index, 10) > versionFeature) {
                    return null;
                }
            } catch (NumberFormatException x) {
                return null; // remove malformed entries silently
            }
            // map to its base name
            return name.substring(index + 1);
        }
        return name;
    }
    private JarEntry getVersionedEntry(String name, JarEntry je) {
        if (BASE_VERSION_FEATURE < versionFeature) {
            if (!name.startsWith(META_INF)) {
                // search for versioned entry
                int v = versionFeature;
                while (v > BASE_VERSION_FEATURE) {
                    JarFileEntry vje = getEntry0(META_INF_VERSIONS + v + "/" + name);
                    if (vje != null) {
                        return vje.withBasename(name);
                    }
                    v–;
                }
            }
        }
        return je;
    }
    // placeholder for now
    String getRealName(JarEntry entry) {
        return entry.getRealName();
    }
    private class JarFileEntry extends JarEntry {
        // 省略
    }
    /*
     * Ensures that the JarVerifier has been created if one is
     * necessary (i.e., the jar appears to be signed.) This is done as
     * a quick check to avoid processing of the manifest for unsigned
     * jars.
     */
    private void maybeInstantiateVerifier() throws IOException {
        if (jv != null) {
            return;
        }
        if (verify) {
            String[] names = getMetaInfEntryNames();
            if (names != null) {
                for (String nameLower : names) {
                    String name = nameLower.toUpperCase(Locale.ENGLISH);
                    if (name.endsWith(".DSA") ||
                        name.endsWith(".RSA") ||
                        name.endsWith(".EC") ||
                        name.endsWith(".SF")) {
                        // Assume since we found a signature-related file
                        // that the jar is signed and that we therefore
                        // need a JarVerifier and Manifest
                        getManifest();
                        return;
                    }
                }
            }
            // No signature-related files; don't instantiate a
            // verifier
            verify = false;
        }
    }
    /*
     * Initializes the verifier object by reading all the manifest
     * entries and passing them to the verifier.
     */
    private void initializeVerifier() {
        ManifestEntryVerifier mev = null;
        // Verify "META-INF/" entries…
        try {
            String[] names = getMetaInfEntryNames();
            if (names != null) {
                for (String name : names) {
                    String uname = name.toUpperCase(Locale.ENGLISH);
                    if (MANIFEST_NAME.equals(uname)
                            || SignatureFileVerifier.isBlockOrSF(uname)) {
                        JarEntry e = getJarEntry(name);
                        if (e == null) {
                            throw new JarException("corrupted jar file");
                        }
                        if (mev == null) {
                            mev = new ManifestEntryVerifier
                                (getManifestFromReference());
                        }
                        byte[] b = getBytes(e);
                        if (b != null && b.length > 0) {
                            jv.beginEntry(e, mev);
                            jv.update(b.length, b, 0, b.length, mev);
                            jv.update(-1, null, 0, 0, mev);
                        }
                    }
                }
            }
        } catch (IOException ex) {
            // if we had an error parsing any blocks, just
            // treat the jar file as being unsigned
            jv = null;
            verify = false;
            if (JarVerifier.debug != null) {
                JarVerifier.debug.println("jarfile parsing error!");
                ex.printStackTrace();
            }
        }
        // if after initializing the verifier we have nothing
        // signed, we null it out.
        if (jv != null) {
            jv.doneWithMeta();
            if (JarVerifier.debug != null) {
                JarVerifier.debug.println("done with meta!");
            }
            if (jv.nothingToVerify()) {
                if (JarVerifier.debug != null) {
                    JarVerifier.debug.println("nothing to verify!");
                }
                jv = null;
                verify = false;
            }
        }
    }
    /*
     * Reads all the bytes for a given entry. Used to process the
     * META-INF files.
     */
    private byte[] getBytes(ZipEntry ze) throws IOException {
        try (InputStream is = super.getInputStream(ze)) {
            int len = (int)ze.getSize();
            int bytesRead;
            byte[] b;
            // trust specified entry sizes when reasonably small
            if (len != -1 && len <= 65535) {
                b = new byte[len];
                bytesRead = is.readNBytes(b, 0, len);
            } else {
                b = is.readAllBytes();
                bytesRead = b.length;
            }
            if (len != -1 && len != bytesRead) {
                throw new EOFException("Expected:" + len + ", read:" + bytesRead);
            }
            return b;
        }
    }
    public synchronized InputStream getInputStream(ZipEntry ze)
        throws IOException
    {
        maybeInstantiateVerifier();
        if (jv == null) {
            return super.getInputStream(ze);
        }
        if (!jvInitialized) {
            initializeVerifier();
            jvInitialized = true;
            // could be set to null after a call to
            // initializeVerifier if we have nothing to
            // verify
            if (jv == null)
                return super.getInputStream(ze);
        }
        // wrap a verifier stream around the real stream
        return new JarVerifier.VerifierStream(
            getManifestFromReference(),
            verifiableEntry(ze),
            super.getInputStream(ze),
            jv);
    }
    private JarEntry verifiableEntry(ZipEntry ze) {
        if (ze instanceof JarFileEntry) {
            // assure the name and entry match for verification
            return ((JarFileEntry)ze).realEntry();
        }
        ze = getJarEntry(ze.getName());
        if (ze instanceof JarFileEntry) {
            return ((JarFileEntry)ze).realEntry();
        }
        return (JarEntry)ze;
    }
    // Statics for hand-coded Boyer-Moore search
    private static final byte[] CLASSPATH_CHARS =
            {'C','L','A','S','S','-','P','A','T','H', ':', ' '};
    // The bad character shift for "class-path: "
    private static final byte[] CLASSPATH_LASTOCC;
    // The good suffix shift for "class-path: "
    private static final byte[] CLASSPATH_OPTOSFT;
    private static final byte[] MULTIRELEASE_CHARS =
            {'M','U','L','T','I','-','R','E','L','E', 'A', 'S', 'E', ':',
                    ' ', 'T', 'R', 'U', 'E'};
    // The bad character shift for "multi-release: true"
    private static final byte[] MULTIRELEASE_LASTOCC;
    // The good suffix shift for "multi-release: true"
    private static final byte[] MULTIRELEASE_OPTOSFT;
    static {
        CLASSPATH_LASTOCC = new byte[65];
        CLASSPATH_OPTOSFT = new byte[12];
        CLASSPATH_LASTOCC[(int)'C' – 32] = 1;
        CLASSPATH_LASTOCC[(int)'L' – 32] = 2;
        CLASSPATH_LASTOCC[(int)'S' – 32] = 5;
        CLASSPATH_LASTOCC[(int)'-' – 32] = 6;
        CLASSPATH_LASTOCC[(int)'P' – 32] = 7;
        CLASSPATH_LASTOCC[(int)'A' – 32] = 8;
        CLASSPATH_LASTOCC[(int)'T' – 32] = 9;
        CLASSPATH_LASTOCC[(int)'H' – 32] = 10;
        CLASSPATH_LASTOCC[(int)':' – 32] = 11;
        CLASSPATH_LASTOCC[(int)' ' – 32] = 12;
        for (int i = 0; i < 11; i++) {
            CLASSPATH_OPTOSFT[i] = 12;
        }
        CLASSPATH_OPTOSFT[11] = 1;
        MULTIRELEASE_LASTOCC = new byte[65];
        MULTIRELEASE_OPTOSFT = new byte[19];
        MULTIRELEASE_LASTOCC[(int)'M' – 32] = 1;
        MULTIRELEASE_LASTOCC[(int)'I' – 32] = 5;
        MULTIRELEASE_LASTOCC[(int)'-' – 32] = 6;
        MULTIRELEASE_LASTOCC[(int)'L' – 32] = 9;
        MULTIRELEASE_LASTOCC[(int)'A' – 32] = 11;
        MULTIRELEASE_LASTOCC[(int)'S' – 32] = 12;
        MULTIRELEASE_LASTOCC[(int)':' – 32] = 14;
        MULTIRELEASE_LASTOCC[(int)' ' – 32] = 15;
        MULTIRELEASE_LASTOCC[(int)'T' – 32] = 16;
        MULTIRELEASE_LASTOCC[(int)'R' – 32] = 17;
        MULTIRELEASE_LASTOCC[(int)'U' – 32] = 18;
        MULTIRELEASE_LASTOCC[(int)'E' – 32] = 19;
        for (int i = 0; i < 17; i++) {
            MULTIRELEASE_OPTOSFT[i] = 19;
        }
        MULTIRELEASE_OPTOSFT[17] = 6;
        MULTIRELEASE_OPTOSFT[18] = 1;
    }
    private JarEntry getManEntry() {
        if (manEntry == null) {
            // First look up manifest entry using standard name
            JarEntry manEntry = getEntry0(MANIFEST_NAME);
            if (manEntry == null) {
                // If not found, then iterate through all the "META-INF/"
                // entries to find a match.
                String[] names = getMetaInfEntryNames();
                if (names != null) {
                    for (String name : names) {
                        if (MANIFEST_NAME.equals(name.toUpperCase(Locale.ENGLISH))) {
                            manEntry = getEntry0(name);
                            break;
                        }
                    }
                }
            }
            this.manEntry = manEntry;
        }
        return manEntry;
    }
    boolean hasClassPathAttribute() throws IOException {
        checkForSpecialAttributes();
        return hasClassPathAttribute;
    }
    private int match(byte[] src, byte[] b, byte[] lastOcc, byte[] optoSft) {
        int len = src.length;
        int last = b.length – len;
        int i = 0;
        next:
        while (i <= last) {
            for (int j = (len – 1); j >= 0; j–) {
                byte c = b[i + j];
                if (c >= ' ' && c <= 'z') {
                    if (c >= 'a') c -= 32; // Canonicalize
                    if (c != src[j]) {
                        // no match
                        int badShift = lastOcc[c – 32];
                        i += Math.max(j + 1 – badShift, optoSft[j]);
                        continue next;
                    }
                } else {
                    // no match, character not valid for name
                    i += len;
                    continue next;
                }
            }
            return i;
        }
        return -1;
    }
    private void checkForSpecialAttributes() throws IOException {
        if (hasCheckedSpecialAttributes) {
            return;
        }
        synchronized (this) {
            if (hasCheckedSpecialAttributes) {
                return;
            }
            JarEntry manEntry = getManEntry();
            if (manEntry != null) {
                byte[] b = getBytes(manEntry);
                hasClassPathAttribute = match(CLASSPATH_CHARS, b,
                        CLASSPATH_LASTOCC, CLASSPATH_OPTOSFT) != -1;
                // is this a multi-release jar file
                if (MULTI_RELEASE_ENABLED) {
                    int i = match(MULTIRELEASE_CHARS, b, MULTIRELEASE_LASTOCC,
                            MULTIRELEASE_OPTOSFT);
                    if (i != -1) {
                        // Read the main attributes of the manifest
                        byte[] lbuf = new byte[512];
                        Attributes attr = new Attributes();
                        attr.read(new Manifest.FastInputStream(
                            new ByteArrayInputStream(b)), lbuf);
                        isMultiRelease = Boolean.parseBoolean(
                            attr.getValue(Attributes.Name.MULTI_RELEASE));
                    }
                }
            }
            hasCheckedSpecialAttributes = true;
        }
    }
    synchronized void ensureInitialization() {
        try {
            maybeInstantiateVerifier();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        if (jv != null && !jvInitialized) {
            initializeVerifier();
            jvInitialized = true;
        }
    }
    /*
     * Returns a versioned {@code JarFileEntry} for the given entry,
     * if there is one. Otherwise returns the original entry. This
     * is invoked by the {@code entries2} for verifier.
     */
    JarEntry newEntry(JarEntry je) {
        if (isMultiRelease()) {
            return getVersionedEntry(je.getName(), je);
        }
        return je;
    }
    /*
     * Returns a versioned {@code JarFileEntry} for the given entry
     * name, if there is one. Otherwise returns a {@code JarFileEntry}
     * with the given name. It is invoked from JarVerifier's entries2
     * for {@code singers}.
     */
    JarEntry newEntry(String name) {
        if (isMultiRelease()) {
            JarEntry vje = getVersionedEntry(name, (JarEntry)null);
            if (vje != null) {
                return vje;
            }
        }
        return new JarFileEntry(name);
    }
    Enumeration<String> entryNames(CodeSource[] cs) {
        ensureInitialization();
        if (jv != null) {
            return jv.entryNames(this, cs);
        }
        /*
         * JAR file has no signed content. Is there a non-signing
         * code source?
         */
        boolean includeUnsigned = false;
        for (CodeSource c : cs) {
            if (c.getCodeSigners() == null) {
                includeUnsigned = true;
                break;
            }
        }
        if (includeUnsigned) {
            return unsignedEntryNames();
        } else {
            return Collections.emptyEnumeration();
        }
    }
    Enumeration<JarEntry> entries2() {
        ensureInitialization();
        if (jv != null) {
            return jv.entries2(this, JUZFA.entries(JarFile.this,
                                                   JarFileEntry::new));
        }
        // screen out entries which are never signed
        final var unfilteredEntries = JUZFA.entries(JarFile.this, JarFileEntry::new);
        return new Enumeration<>() {
            JarEntry entry;
            public boolean hasMoreElements() {
                if (entry != null) {
                    return true;
                }
                while (unfilteredEntries.hasMoreElements()) {
                    JarEntry je = unfilteredEntries.nextElement();
                    if (JarVerifier.isSigningRelated(je.getName())) {
                        continue;
                    }
                    entry = je;
                    return true;
                }
                return false;
            }
            public JarEntry nextElement() {
                if (hasMoreElements()) {
                    JarEntry je = entry;
                    entry = null;
                    return newEntry(je);
                }
                throw new NoSuchElementException();
            }
        };
    }
    CodeSource[] getCodeSources(URL url) {
        ensureInitialization();
        if (jv != null) {
            return jv.getCodeSources(this, url);
        }
        /*
         * JAR file has no signed content. Is there a non-signing
         * code source?
         */
        Enumeration<String> unsigned = unsignedEntryNames();
        if (unsigned.hasMoreElements()) {
            return new CodeSource[]{JarVerifier.getUnsignedCS(url)};
        } else {
            return null;
        }
    }
    private Enumeration<String> unsignedEntryNames() {
        final Enumeration<JarEntry> entries = entries();
        return new Enumeration<>() {
            String name;
            /*
             * Grab entries from ZIP directory but screen out
             * metadata.
             */
            public boolean hasMoreElements() {
                if (name != null) {
                    return true;
                }
                while (entries.hasMoreElements()) {
                    String value;
                    ZipEntry e = entries.nextElement();
                    value = e.getName();
                    if (e.isDirectory() || JarVerifier.isSigningRelated(value)) {
                        continue;
                    }
                    name = value;
                    return true;
                }
                return false;
            }
            public String nextElement() {
                if (hasMoreElements()) {
                    String value = name;
                    name = null;
                    return value;
                }
                throw new NoSuchElementException();
            }
        };
    }
    CodeSource getCodeSource(URL url, String name) {
        ensureInitialization();
        if (jv != null) {
            if (jv.eagerValidation) {
                CodeSource cs = null;
                JarEntry je = getJarEntry(name);
                if (je != null) {
                    cs = jv.getCodeSource(url, this, je);
                } else {
                    cs = jv.getCodeSource(url, name);
                }
                return cs;
            } else {
                return jv.getCodeSource(url, name);
            }
        }
        return JarVerifier.getUnsignedCS(url);
    }
    void setEagerValidation(boolean eager) {
        try {
            maybeInstantiateVerifier();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        if (jv != null) {
            jv.setEagerValidation(eager);
        }
    }
    List<Object> getManifestDigests() {
        ensureInitialization();
        if (jv != null) {
            return jv.getManifestDigests();
        }
        return new ArrayList<>();
    }
}
JDK

KeyValueHolderクラス

キーと値を保持するだけのMap.Entry<K,V>インタフェースを実装したクラスであるが,パッケージプライベートなクラスであるため,ユーザは基本的に使用できない(リフレクションでやれなくはないが,そこまでやるなら自作した方が早い)。

インスタンス変数としては,keyとvalueがある。コンストラクタはKeyValueHolder(K k, V v)だけが定義されている。

setValue(V value)メソッドを呼び出すとUnsupportedOperationExceptionがスローされる。equals(Object o)メソッドは,KeyValueHolderクラスかそのサブクラスならば,キーと値が一致すればtrue,それ以外はfalseを返す。

hashCode()メソッドではkeyのハッシュコードとvalueのハッシュコードの排他的論理和を算出して返す。toString()hashCode()メソッドではkeyとvalueを”=”で連結して返している。

/*
 * Copyright (c) 2015, 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.util;
import jdk.internal.vm.annotation.Stable;
final class KeyValueHolder<K,V> implements Map.Entry<K,V> {
    @Stable
    final K key;
    @Stable
    final V value;
    KeyValueHolder(K k, V v) {
        key = Objects.requireNonNull(k);
        value = Objects.requireNonNull(v);
    }
    @Override
    public K getKey() {
        return key;
    }
    @Override
    public V getValue() {
        return value;
    }
    @Override
    public V setValue(V value) {
        throw new UnsupportedOperationException("not supported");
    }
    @Override
    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        return key.equals(e.getKey()) && value.equals(e.getValue());
    }
    @Override
    public int hashCode() {
        return key.hashCode() ^ value.hashCode();
    }
    @Override
    public String toString() {
        return key + "=" + value;
    }
}
JDK

java.util.Base64クラス

java.util.Base64はJava8になってようやくSDKに導入されたBase64エンコーダー/デコーダーである。バイナリーデータをasciiコードの範囲内に収めるために伸長する仕様で,それほど難しい処理ではない。

インナークラスとしてエンコードを担うEncoderクラス,デコードを行うDecoderクラスが定義されており,実質的な処理はこのクラスが担っている。

Encoderクラスの中では,Base64エンコードする際に用いる文字をchar[]型の定数で定義している。 Encoderクラスのコンストラクタはprivateであり, isURL, newline, linemax, doPaddingという4つの引数をとる。 isURL はURLか否かを表すboolean型の引数である。URLの場合は”/”が使えないため,エンコードする際に用いるascii文字セットが一部異なる。newlineはchar[]の配列で,改行コードである。linemaxは1行の文字数であり,初期値は76に設定されている。doPaddingは余った領域をパディングするか否かを表すbooleanであり,trueの場合は”=”でパディングする。

encodedOutLength(int srclen, boolean throwOOME)メソッドは,アウトプットのバイト数を計算するメソッドである。改行コードの分を考慮しているため複雑な計算になってしまっているが,改行を考慮しなければ元のバイト数の約4/3倍と考えてよい。

encode(byte[] src)メソッドがsrcで渡されたバイト配列をbase64エンコードするpublicなメソッドである。上述のencodedOutLength()メソッドを使ってアウトプットのバイト数を求め,そのサイズのbyte[]配列を確保する。そしてencode0()メソッドに渡してエンコードする。

encode0メソッドが実際にデータをbase64エンコードする処理ロジックが書かれたものである。ここは完全にbase64エンコードの仕様そのものである。

/*
 * Copyright (c) 2012, 2019, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the “Classpath” exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package java.util;
import java.io.FilterOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import jdk.internal.HotSpotIntrinsicCandidate;
public class Base64 {
    private Base64() {}
    public static Encoder getEncoder() {
         return Encoder.RFC4648;
    }
    public static Encoder getUrlEncoder() {
         return Encoder.RFC4648_URLSAFE;
    }
    public static Encoder getMimeEncoder() {
        return Encoder.RFC2045;
    }
    public static Encoder getMimeEncoder(int lineLength, byte[] lineSeparator) {
         Objects.requireNonNull(lineSeparator);
         int[] base64 = Decoder.fromBase64;
         for (byte b : lineSeparator) {
             if (base64[b & 0xff] != -1)
                 throw new IllegalArgumentException(
                     “Illegal base64 line separator character 0x” + Integer.toString(b, 16));
         }
         // round down to nearest multiple of 4
         lineLength &= ~0b11;
         if (lineLength <= 0) {
             return Encoder.RFC4648;
         }
         return new Encoder(false, lineSeparator, lineLength, true);
    }
    public static Decoder getDecoder() {
         return Decoder.RFC4648;
    }
    public static Decoder getUrlDecoder() {
         return Decoder.RFC4648_URLSAFE;
    }
    public static Decoder getMimeDecoder() {
         return Decoder.RFC2045;
    }
    public static class Encoder {
        private final byte[] newline;
        private final int linemax;
        private final boolean isURL;
        private final boolean doPadding;
        private Encoder(boolean isURL, byte[] newline, int linemax, boolean doPadding) {
            this.isURL = isURL;
            this.newline = newline;
            this.linemax = linemax;
            this.doPadding = doPadding;
        }
        private static final char[] toBase64 = {
            ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, ‘H’, ‘I’, ‘J’, ‘K’, ‘L’, ‘M’,
            ‘N’, ‘O’, ‘P’, ‘Q’, ‘R’, ‘S’, ‘T’, ‘U’, ‘V’, ‘W’, ‘X’, ‘Y’, ‘Z’,
            ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’, ‘i’, ‘j’, ‘k’, ‘l’, ‘m’,
            ‘n’, ‘o’, ‘p’, ‘q’, ‘r’, ‘s’, ‘t’, ‘u’, ‘v’, ‘w’, ‘x’, ‘y’, ‘z’,
            ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘+’, ‘/’
        };
        private static final char[] toBase64URL = {
            ‘A’, ‘B’, ‘C’, ‘D’, ‘E’, ‘F’, ‘G’, ‘H’, ‘I’, ‘J’, ‘K’, ‘L’, ‘M’,
            ‘N’, ‘O’, ‘P’, ‘Q’, ‘R’, ‘S’, ‘T’, ‘U’, ‘V’, ‘W’, ‘X’, ‘Y’, ‘Z’,
            ‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’, ‘i’, ‘j’, ‘k’, ‘l’, ‘m’,
            ‘n’, ‘o’, ‘p’, ‘q’, ‘r’, ‘s’, ‘t’, ‘u’, ‘v’, ‘w’, ‘x’, ‘y’, ‘z’,
            ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’, ‘-‘, ‘_’
        };
        private static final int MIMELINEMAX = 76;
        private static final byte[] CRLF = new byte[] {‘\r’, ‘\n’};
        static final Encoder RFC4648 = new Encoder(false, null, -1, true);
        static final Encoder RFC4648_URLSAFE = new Encoder(true, null, -1, true);
        static final Encoder RFC2045 = new Encoder(false, CRLF, MIMELINEMAX, true);
        private final int encodedOutLength(int srclen, boolean throwOOME) {
            int len = 0;
            try {
                if (doPadding) {
                    len = Math.multiplyExact(4, (Math.addExact(srclen, 2) / 3));
                } else {
                    int n = srclen % 3;
                    len = Math.addExact(Math.multiplyExact(4, (srclen / 3)), (n == 0 ? 0 : n + 1));
                }
                if (linemax > 0) {                             // line separators
                    len = Math.addExact(len, (len – 1) / linemax * newline.length);
                }
            } catch (ArithmeticException ex) {
                if (throwOOME) {
                    throw new OutOfMemoryError(“Encoded size is too large”);
                } else {
                    // let the caller know that encoded bytes length
                    // is too large
                    len = -1;
                }
            }
            return len;
        }
        public byte[] encode(byte[] src) {
            int len = encodedOutLength(src.length, true);          // dst array size
            byte[] dst = new byte[len];
            int ret = encode0(src, 0, src.length, dst);
            if (ret != dst.length)
                 return Arrays.copyOf(dst, ret);
            return dst;
        }
        public int encode(byte[] src, byte[] dst) {
            int len = encodedOutLength(src.length, false);         // dst array size
            if (dst.length < len || len == -1)
                throw new IllegalArgumentException(
                    “Output byte array is too small for encoding all input bytes”);
            return encode0(src, 0, src.length, dst);
        }
        @SuppressWarnings(“deprecation”)
        public String encodeToString(byte[] src) {
            byte[] encoded = encode(src);
            return new String(encoded, 0, 0, encoded.length);
        }
        public ByteBuffer encode(ByteBuffer buffer) {
            int len = encodedOutLength(buffer.remaining(), true);
            byte[] dst = new byte[len];
            int ret = 0;
            if (buffer.hasArray()) {
                ret = encode0(buffer.array(),
                              buffer.arrayOffset() + buffer.position(),
                              buffer.arrayOffset() + buffer.limit(),
                              dst);
                buffer.position(buffer.limit());
            } else {
                byte[] src = new byte[buffer.remaining()];
                buffer.get(src);
                ret = encode0(src, 0, src.length, dst);
            }
            if (ret != dst.length)
                 dst = Arrays.copyOf(dst, ret);
            return ByteBuffer.wrap(dst);
        }
        public OutputStream wrap(OutputStream os) {
            Objects.requireNonNull(os);
            return new EncOutputStream(os, isURL ? toBase64URL : toBase64,
                                       newline, linemax, doPadding);
        }
        public Encoder withoutPadding() {
            if (!doPadding)
                return this;
            return new Encoder(isURL, newline, linemax, false);
        }
        @HotSpotIntrinsicCandidate
        private void encodeBlock(byte[] src, int sp, int sl, byte[] dst, int dp, boolean isURL) {
            char[] base64 = isURL ? toBase64URL : toBase64;
            for (int sp0 = sp, dp0 = dp ; sp0 < sl; ) {
                int bits = (src[sp0++] & 0xff) << 16 |
                           (src[sp0++] & 0xff) <<  8 |
                           (src[sp0++] & 0xff);
                dst[dp0++] = (byte)base64[(bits >>> 18) & 0x3f];
                dst[dp0++] = (byte)base64[(bits >>> 12) & 0x3f];
                dst[dp0++] = (byte)base64[(bits >>> 6)  & 0x3f];
                dst[dp0++] = (byte)base64[bits & 0x3f];
            }
        }
        private int encode0(byte[] src, int off, int end, byte[] dst) {
            char[] base64 = isURL ? toBase64URL : toBase64;
            int sp = off;
            int slen = (end – off) / 3 * 3;
            int sl = off + slen;
            if (linemax > 0 && slen  > linemax / 4 * 3)
                slen = linemax / 4 * 3;
            int dp = 0;
            while (sp < sl) {
                int sl0 = Math.min(sp + slen, sl);
                encodeBlock(src, sp, sl0, dst, dp, isURL);
                int dlen = (sl0 – sp) / 3 * 4;
                dp += dlen;
                sp = sl0;
                if (dlen == linemax && sp < end) {
                    for (byte b : newline){
                        dst[dp++] = b;
                    }
                }
            }
            if (sp < end) {               // 1 or 2 leftover bytes
                int b0 = src[sp++] & 0xff;
                dst[dp++] = (byte)base64[b0 >> 2];
                if (sp == end) {
                    dst[dp++] = (byte)base64[(b0 << 4) & 0x3f];
                    if (doPadding) {
                        dst[dp++] = ‘=’;
                        dst[dp++] = ‘=’;
                    }
                } else {
                    int b1 = src[sp++] & 0xff;
                    dst[dp++] = (byte)base64[(b0 << 4) & 0x3f | (b1 >> 4)];
                    dst[dp++] = (byte)base64[(b1 << 2) & 0x3f];
                    if (doPadding) {
                        dst[dp++] = ‘=’;
                    }
                }
            }
            return dp;
        }
    }
    public static class Decoder {
        private final boolean isURL;
        private final boolean isMIME;
        private Decoder(boolean isURL, boolean isMIME) {
            this.isURL = isURL;
            this.isMIME = isMIME;
        }
        private static final int[] fromBase64 = new int[256];
        static {
            Arrays.fill(fromBase64, -1);
            for (int i = 0; i < Encoder.toBase64.length; i++)
                fromBase64[Encoder.toBase64[i]] = i;
            fromBase64[‘=’] = -2;
        }
        private static final int[] fromBase64URL = new int[256];
        static {
            Arrays.fill(fromBase64URL, -1);
            for (int i = 0; i < Encoder.toBase64URL.length; i++)
                fromBase64URL[Encoder.toBase64URL[i]] = i;
            fromBase64URL[‘=’] = -2;
        }
        static final Decoder RFC4648         = new Decoder(false, false);
        static final Decoder RFC4648_URLSAFE = new Decoder(true, false);
        static final Decoder RFC2045         = new Decoder(false, true);
        public byte[] decode(byte[] src) {
            byte[] dst = new byte[decodedOutLength(src, 0, src.length)];
            int ret = decode0(src, 0, src.length, dst);
            if (ret != dst.length) {
                dst = Arrays.copyOf(dst, ret);
            }
            return dst;
        }
        public byte[] decode(String src) {
            return decode(src.getBytes(StandardCharsets.ISO_8859_1));
        }
        public int decode(byte[] src, byte[] dst) {
            int len = decodedOutLength(src, 0, src.length);
            if (dst.length < len || len == -1)
                throw new IllegalArgumentException(
                    “Output byte array is too small for decoding all input bytes”);
            return decode0(src, 0, src.length, dst);
        }
        public ByteBuffer decode(ByteBuffer buffer) {
            int pos0 = buffer.position();
            try {
                byte[] src;
                int sp, sl;
                if (buffer.hasArray()) {
                    src = buffer.array();
                    sp = buffer.arrayOffset() + buffer.position();
                    sl = buffer.arrayOffset() + buffer.limit();
                    buffer.position(buffer.limit());
                } else {
                    src = new byte[buffer.remaining()];
                    buffer.get(src);
                    sp = 0;
                    sl = src.length;
                }
                byte[] dst = new byte[decodedOutLength(src, sp, sl)];
                return ByteBuffer.wrap(dst, 0, decode0(src, sp, sl, dst));
            } catch (IllegalArgumentException iae) {
                buffer.position(pos0);
                throw iae;
            }
        }
        public InputStream wrap(InputStream is) {
            Objects.requireNonNull(is);
            return new DecInputStream(is, isURL ? fromBase64URL : fromBase64, isMIME);
        }
        private int decodedOutLength(byte[] src, int sp, int sl) {
            int[] base64 = isURL ? fromBase64URL : fromBase64;
            int paddings = 0;
            int len = sl – sp;
            if (len == 0)
                return 0;
            if (len < 2) {
                if (isMIME && base64[0] == -1)
                    return 0;
                throw new IllegalArgumentException(
                    “Input byte[] should at least have 2 bytes for base64 bytes”);
            }
            if (isMIME) {
                // scan all bytes to fill out all non-alphabet. a performance
                // trade-off of pre-scan or Arrays.copyOf
                int n = 0;
                while (sp < sl) {
                    int b = src[sp++] & 0xff;
                    if (b == ‘=’) {
                        len -= (sl – sp + 1);
                        break;
                    }
                    if ((b = base64[b]) == -1)
                        n++;
                }
                len -= n;
            } else {
                if (src[sl – 1] == ‘=’) {
                    paddings++;
                    if (src[sl – 2] == ‘=’)
                        paddings++;
                }
            }
            if (paddings == 0 && (len & 0x3) !=  0)
                paddings = 4 – (len & 0x3);
            // If len is near to Integer.MAX_VALUE, (len + 3)
            // can possibly overflow, perform this operation as
            // long and cast it back to integer when the value comes under
            // integer limit. The final value will always be in integer
            // limits
            return 3 * (int) ((len + 3L) / 4) – paddings;
        }
        private int decode0(byte[] src, int sp, int sl, byte[] dst) {
            int[] base64 = isURL ? fromBase64URL : fromBase64;
            int dp = 0;
            int bits = 0;
            int shiftto = 18;       // pos of first byte of 4-byte atom
            while (sp < sl) {
                if (shiftto == 18 && sp + 4 < sl) {       // fast path
                    int sl0 = sp + ((sl – sp) & ~0b11);
                    while (sp < sl0) {
                        int b1 = base64[src[sp++] & 0xff];
                        int b2 = base64[src[sp++] & 0xff];
                        int b3 = base64[src[sp++] & 0xff];
                        int b4 = base64[src[sp++] & 0xff];
                        if ((b1 | b2 | b3 | b4) < 0) {    // non base64 byte
                            sp -= 4;
                            break;
                        }
                        int bits0 = b1 << 18 | b2 << 12 | b3 << 6 | b4;
                        dst[dp++] = (byte)(bits0 >> 16);
                        dst[dp++] = (byte)(bits0 >>  8);
                        dst[dp++] = (byte)(bits0);
                    }
                    if (sp >= sl)
                        break;
                }
                int b = src[sp++] & 0xff;
                if ((b = base64[b]) < 0) {
                    if (b == -2) {         // padding byte ‘=’
                        // =     shiftto==18 unnecessary padding
                        // x=    shiftto==12 a dangling single x
                        // x     to be handled together with non-padding case
                        // xx=   shiftto==6&&sp==sl missing last =
                        // xx=y  shiftto==6 last is not =
                        if (shiftto == 6 && (sp == sl || src[sp++] != ‘=’) ||
                            shiftto == 18) {
                            throw new IllegalArgumentException(
                                “Input byte array has wrong 4-byte ending unit”);
                        }
                        break;
                    }
                    if (isMIME)    // skip if for rfc2045
                        continue;
                    else
                        throw new IllegalArgumentException(
                            “Illegal base64 character ” +
                            Integer.toString(src[sp – 1], 16));
                }
                bits |= (b << shiftto);
                shiftto -= 6;
                if (shiftto < 0) {
                    dst[dp++] = (byte)(bits >> 16);
                    dst[dp++] = (byte)(bits >>  8);
                    dst[dp++] = (byte)(bits);
                    shiftto = 18;
                    bits = 0;
                }
            }
            // reached end of byte array or hit padding ‘=’ characters.
            if (shiftto == 6) {
                dst[dp++] = (byte)(bits >> 16);
            } else if (shiftto == 0) {
                dst[dp++] = (byte)(bits >> 16);
                dst[dp++] = (byte)(bits >>  8);
            } else if (shiftto == 12) {
                // dangling single “x”, incorrectly encoded.
                throw new IllegalArgumentException(
                    “Last unit does not have enough valid bits”);
            }
            // anything left is invalid, if is not MIME.
            // if MIME, ignore all non-base64 character
            while (sp < sl) {
                if (isMIME && base64[src[sp++] & 0xff] < 0)
                    continue;
                throw new IllegalArgumentException(
                    “Input byte array has incorrect ending byte at ” + sp);
            }
            return dp;
        }
    }
    /*
     * An output stream for encoding bytes into the Base64.
     */
    private static class EncOutputStream extends FilterOutputStream {
        private int leftover = 0;
        private int b0, b1, b2;
        private boolean closed = false;
        private final char[] base64;    // byte->base64 mapping
        private final byte[] newline;   // line separator, if needed
        private final int linemax;
        private final boolean doPadding;// whether or not to pad
        private int linepos = 0;
        private byte[] buf;
        EncOutputStream(OutputStream os, char[] base64,
                        byte[] newline, int linemax, boolean doPadding) {
            super(os);
            this.base64 = base64;
            this.newline = newline;
            this.linemax = linemax;
            this.doPadding = doPadding;
            this.buf = new byte[linemax <= 0 ? 8124 : linemax];
        }
        @Override
        public void write(int b) throws IOException {
            byte[] buf = new byte[1];
            buf[0] = (byte)(b & 0xff);
            write(buf, 0, 1);
        }
        private void checkNewline() throws IOException {
            if (linepos == linemax) {
                out.write(newline);
                linepos = 0;
            }
        }
        private void writeb4(char b1, char b2, char b3, char b4) throws IOException {
            buf[0] = (byte)b1;
            buf[1] = (byte)b2;
            buf[2] = (byte)b3;
            buf[3] = (byte)b4;
            out.write(buf, 0, 4);
        }
        @Override
        public void write(byte[] b, int off, int len) throws IOException {
            if (closed)
                throw new IOException(“Stream is closed”);
            if (off < 0 || len < 0 || len > b.length – off)
                throw new ArrayIndexOutOfBoundsException();
            if (len == 0)
                return;
            if (leftover != 0) {
                if (leftover == 1) {
                    b1 = b[off++] & 0xff;
                    len–;
                    if (len == 0) {
                        leftover++;
                        return;
                    }
                }
                b2 = b[off++] & 0xff;
                len–;
                checkNewline();
                writeb4(base64[b0 >> 2],
                        base64[(b0 << 4) & 0x3f | (b1 >> 4)],
                        base64[(b1 << 2) & 0x3f | (b2 >> 6)],
                        base64[b2 & 0x3f]);
                linepos += 4;
            }
            int nBits24 = len / 3;
            leftover = len – (nBits24 * 3);
            while (nBits24 > 0) {
                checkNewline();
                int dl = linemax <= 0 ? buf.length : buf.length – linepos;
                int sl = off + Math.min(nBits24, dl / 4) * 3;
                int dp = 0;
                for (int sp = off; sp < sl; ) {
                    int bits = (b[sp++] & 0xff) << 16 |
                               (b[sp++] & 0xff) <<  8 |
                               (b[sp++] & 0xff);
                    buf[dp++] = (byte)base64[(bits >>> 18) & 0x3f];
                    buf[dp++] = (byte)base64[(bits >>> 12) & 0x3f];
                    buf[dp++] = (byte)base64[(bits >>> 6)  & 0x3f];
                    buf[dp++] = (byte)base64[bits & 0x3f];
                }
                out.write(buf, 0, dp);
                off = sl;
                linepos += dp;
                nBits24 -= dp / 4;
            }
            if (leftover == 1) {
                b0 = b[off++] & 0xff;
            } else if (leftover == 2) {
                b0 = b[off++] & 0xff;
                b1 = b[off++] & 0xff;
            }
        }
        @Override
        public void close() throws IOException {
            if (!closed) {
                closed = true;
                if (leftover == 1) {
                    checkNewline();
                    out.write(base64[b0 >> 2]);
                    out.write(base64[(b0 << 4) & 0x3f]);
                    if (doPadding) {
                        out.write(‘=’);
                        out.write(‘=’);
                    }
                } else if (leftover == 2) {
                    checkNewline();
                    out.write(base64[b0 >> 2]);
                    out.write(base64[(b0 << 4) & 0x3f | (b1 >> 4)]);
                    out.write(base64[(b1 << 2) & 0x3f]);
                    if (doPadding) {
                       out.write(‘=’);
                    }
                }
                leftover = 0;
                out.close();
            }
        }
    }
    /*
     * An input stream for decoding Base64 bytes
     */
    private static class DecInputStream extends InputStream {
        private final InputStream is;
        private final boolean isMIME;
        private final int[] base64;      // base64 -> byte mapping
        private int bits = 0;            // 24-bit buffer for decoding
        private int nextin = 18;         // next available “off” in “bits” for input;
                                         // -> 18, 12, 6, 0
        private int nextout = -8;        // next available “off” in “bits” for output;
                                         // -> 8, 0, -8 (no byte for output)
        private boolean eof = false;
        private boolean closed = false;
        DecInputStream(InputStream is, int[] base64, boolean isMIME) {
            this.is = is;
            this.base64 = base64;
            this.isMIME = isMIME;
        }
        private byte[] sbBuf = new byte[1];
        @Override
        public int read() throws IOException {
            return read(sbBuf, 0, 1) == -1 ? -1 : sbBuf[0] & 0xff;
        }
        private int eof(byte[] b, int off, int len, int oldOff)
            throws IOException
        {
            eof = true;
            if (nextin != 18) {
                if (nextin == 12)
                    throw new IOException(“Base64 stream has one un-decoded dangling byte.”);
                // treat ending xx/xxx without padding character legal.
                // same logic as v == ‘=’ below
                b[off++] = (byte)(bits >> (16));
                if (nextin == 0) {           // only one padding byte
                    if (len == 1) {          // no enough output space
                        bits >>= 8;          // shift to lowest byte
                        nextout = 0;
                    } else {
                        b[off++] = (byte) (bits >>  8);
                    }
                }
            }
            return off == oldOff ? -1 : off – oldOff;
        }
        private int padding(byte[] b, int off, int len, int oldOff)
            throws IOException
        {
            // =     shiftto==18 unnecessary padding
            // x=    shiftto==12 dangling x, invalid unit
            // xx=   shiftto==6 && missing last ‘=’
            // xx=y  or last is not ‘=’
            if (nextin == 18 || nextin == 12 ||
                nextin == 6 && is.read() != ‘=’) {
                throw new IOException(“Illegal base64 ending sequence:” + nextin);
            }
            b[off++] = (byte)(bits >> (16));
            if (nextin == 0) {           // only one padding byte
                if (len == 1) {          // no enough output space
                    bits >>= 8;          // shift to lowest byte
                    nextout = 0;
                } else {
                    b[off++] = (byte) (bits >>  8);
                }
            }
            eof = true;
            return off – oldOff;
        }
        @Override
        public int read(byte[] b, int off, int len) throws IOException {
            if (closed)
                throw new IOException(“Stream is closed”);
            if (eof && nextout < 0)    // eof and no leftover
                return -1;
            if (off < 0 || len < 0 || len > b.length – off)
                throw new IndexOutOfBoundsException();
            int oldOff = off;
            while (nextout >= 0) {       // leftover output byte(s) in bits buf
                if (len == 0)
                    return off – oldOff;
                b[off++] = (byte)(bits >> nextout);
                len–;
                nextout -= 8;
            }
            bits = 0;
            while (len > 0) {
                int v = is.read();
                if (v == -1) {
                    return eof(b, off, len, oldOff);
                }
                if ((v = base64[v]) < 0) {
                    if (v == -2) {       // padding byte(s)
                        return padding(b, off, len, oldOff);
                    }
                    if (v == -1) {
                        if (!isMIME)
                            throw new IOException(“Illegal base64 character ” +
                                Integer.toString(v, 16));
                        continue;        // skip if for rfc2045
                    }
                    // neve be here
                }
                bits |= (v << nextin);
                if (nextin == 0) {
                    nextin = 18;         // clear for next in
                    b[off++] = (byte)(bits >> 16);
                    if (len == 1) {
                        nextout = 8;    // 2 bytes left in bits
                        break;
                    }
                    b[off++] = (byte)(bits >> 8);
                    if (len == 2) {
                        nextout = 0;    // 1 byte left in bits
                        break;
                    }
                    b[off++] = (byte)bits;
                    len -= 3;
                    bits = 0;
                } else {
                    nextin -= 6;
                }
            }
            return off – oldOff;
        }
        @Override
        public int available() throws IOException {
            if (closed)
                throw new IOException(“Stream is closed”);
            return is.available();   // TBD:
        }
        @Override
        public void close() throws IOException {
            if (!closed) {
                closed = true;
                is.close();
            }
        }
    }
}