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;
    }
}