java.nio.Charsetクラスはrfc2278で定義されている文字セットを表すクラスである。
- forName(String charsetName)
- isSupported(String charsetName)
- availableCharsets()
- defaultCharset()
- name()
- aliases()
- displayName()
- isRegistered()
- displayName(Locale locale)
name()メソッドではインスタンス変数nameを返している。インスタンス変数nameには,”tickles a bug in oldjavac”という気になるコメントが入っている。
forName(String charsetName)name()メソッドは,文字セット名から文字セットを取得する。内部ではlookupメソッドを呼び出している。lookupメソッドではキャッシュ用の変数cache1変数の内容と比較し,キャッシュに持っていればそのオブジェクトを返し,キャッシュに存在しない場合は更に,lookup2メソッドを呼び出している。 lookup2 メソッドでは,cache2変数の内容と文字セット名で同様の処理を行い,cache2でも一致する文字セットがなかった場合は, sun.nio.cs.StandardCharsets 型のインスタンス変数standardProviderのcharsetForName(String charsetName)メソッドで文字セットを取得する。取得した文字セットオブジェクトはキャッシュに保存される。
isSupported(String charsetName)メソッドでは,forName()メソッドと同じように内部でlookup()メソッドを呼び出し,null以外の値が返された場合はtrueを返し,nullが返されたらfalseを返す。
availableCharsets()メソッドは,インナークラスExtendedProviderHolderのextendedProvidersフィールドからCharsetProviderの配列を取得する。そして,CharsetProviderから文字セットを取り出して,SrotedMapオブジェクトに格納して返す。
defaultCharset()メソッドでは,ディフォルトの文字セットを取得し,取得できなかった場合はUTF-8の文字セットを返す。
displayName()メソッドはロケールに対応した文字セット名を返すとされている。実装はインスタンス変数nameを返すものとなっている。displayName(Locale locale)メソッドでは,引数でロケールを受け取るが,実装はnameインスタンス変数を返すものとなっており,引数を取らないメソッドと同じ実装である。
* Copyright (c) 2000, 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.nio.charset;
import jdk.internal.misc.VM;
import sun.nio.cs.ThreadLocalCoders;
import sun.security.action.GetPropertyAction;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.spi.CharsetProvider;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
public abstract class Charset
implements Comparable<Charset>
{
/* — Static methods — */
private static void checkName(String s) {
int n = s.length();
if (n == 0) {
throw new IllegalCharsetNameException(s);
}
for (int i = 0; i < n; i++) {
char c = s.charAt(i);
if (c >= ‘A’ && c <= ‘Z’) continue;
if (c >= ‘a’ && c <= ‘z’) continue;
if (c >= ‘0’ && c <= ‘9’) continue;
if (c == ‘-‘ && i != 0) continue;
if (c == ‘+’ && i != 0) continue;
if (c == ‘:’ && i != 0) continue;
if (c == ‘_’ && i != 0) continue;
if (c == ‘.’ && i != 0) continue;
throw new IllegalCharsetNameException(s);
}
}
/* The standard set of charsets */
private static final CharsetProvider standardProvider
= new sun.nio.cs.StandardCharsets();
private static final String[] zeroAliases = new String[0];
// Cache of the most-recently-returned charsets,
// along with the names that were used to find them
//
private static volatile Object[] cache1; // “Level 1” cache
private static volatile Object[] cache2; // “Level 2” cache
private static void cache(String charsetName, Charset cs) {
cache2 = cache1;
cache1 = new Object[] { charsetName, cs };
}
// Creates an iterator that walks over the available providers, ignoring
// those whose lookup or instantiation causes a security exception to be
// thrown. Should be invoked with full privileges.
//
private static Iterator<CharsetProvider> providers() {
return new Iterator<>() {
ClassLoader cl = ClassLoader.getSystemClassLoader();
ServiceLoader<CharsetProvider> sl =
ServiceLoader.load(CharsetProvider.class, cl);
Iterator<CharsetProvider> i = sl.iterator();
CharsetProvider next = null;
private boolean getNext() {
while (next == null) {
try {
if (!i.hasNext())
return false;
next = i.next();
} catch (ServiceConfigurationError sce) {
if (sce.getCause() instanceof SecurityException) {
// Ignore security exceptions
continue;
}
throw sce;
}
}
return true;
}
public boolean hasNext() {
return getNext();
}
public CharsetProvider next() {
if (!getNext())
throw new NoSuchElementException();
CharsetProvider n = next;
next = null;
return n;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
// Thread-local gate to prevent recursive provider lookups
private static ThreadLocal<ThreadLocal<?>> gate =
new ThreadLocal<ThreadLocal<?>>();
private static Charset lookupViaProviders(final String charsetName) {
// The runtime startup sequence looks up standard charsets as a
// consequence of the VM’s invocation of System.initializeSystemClass
// in order to, e.g., set system properties and encode filenames. At
// that point the application class loader has not been initialized,
// however, so we can’t look for providers because doing so will cause
// that loader to be prematurely initialized with incomplete
// information.
//
if (!VM.isBooted())
return null;
if (gate.get() != null)
// Avoid recursive provider lookups
return null;
try {
gate.set(gate);
return AccessController.doPrivileged(
new PrivilegedAction<>() {
public Charset run() {
for (Iterator<CharsetProvider> i = providers();
i.hasNext();) {
CharsetProvider cp = i.next();
Charset cs = cp.charsetForName(charsetName);
if (cs != null)
return cs;
}
return null;
}
});
} finally {
gate.set(null);
}
}
/* The extended set of charsets */
private static class ExtendedProviderHolder {
static final CharsetProvider[] extendedProviders = extendedProviders();
// returns ExtendedProvider, if installed
private static CharsetProvider[] extendedProviders() {
return AccessController.doPrivileged(new PrivilegedAction<>() {
public CharsetProvider[] run() {
CharsetProvider[] cps = new CharsetProvider[1];
int n = 0;
ServiceLoader<CharsetProvider> sl =
ServiceLoader.loadInstalled(CharsetProvider.class);
for (CharsetProvider cp : sl) {
if (n + 1 > cps.length) {
cps = Arrays.copyOf(cps, cps.length << 1);
}
cps[n++] = cp;
}
return n == cps.length ? cps : Arrays.copyOf(cps, n);
}});
}
}
private static Charset lookupExtendedCharset(String charsetName) {
if (!VM.isBooted()) // see lookupViaProviders()
return null;
CharsetProvider[] ecps = ExtendedProviderHolder.extendedProviders;
for (CharsetProvider cp : ecps) {
Charset cs = cp.charsetForName(charsetName);
if (cs != null)
return cs;
}
return null;
}
private static Charset lookup(String charsetName) {
if (charsetName == null)
throw new IllegalArgumentException(“Null charset name”);
Object[] a;
if ((a = cache1) != null && charsetName.equals(a[0]))
return (Charset)a[1];
// We expect most programs to use one Charset repeatedly.
// We convey a hint to this effect to the VM by putting the
// level 1 cache miss code in a separate method.
return lookup2(charsetName);
}
private static Charset lookup2(String charsetName) {
Object[] a;
if ((a = cache2) != null && charsetName.equals(a[0])) {
cache2 = cache1;
cache1 = a;
return (Charset)a[1];
}
Charset cs;
if ((cs = standardProvider.charsetForName(charsetName)) != null ||
(cs = lookupExtendedCharset(charsetName)) != null ||
(cs = lookupViaProviders(charsetName)) != null)
{
cache(charsetName, cs);
return cs;
}
/* Only need to check the name if we didn’t find a charset for it */
checkName(charsetName);
return null;
}
public static boolean isSupported(String charsetName) {
return (lookup(charsetName) != null);
}
public static Charset forName(String charsetName) {
Charset cs = lookup(charsetName);
if (cs != null)
return cs;
throw new UnsupportedCharsetException(charsetName);
}
// Fold charsets from the given iterator into the given map, ignoring
// charsets whose names already have entries in the map.
//
private static void put(Iterator<Charset> i, Map<String,Charset> m) {
while (i.hasNext()) {
Charset cs = i.next();
if (!m.containsKey(cs.name()))
m.put(cs.name(), cs);
}
}
public static SortedMap<String,Charset> availableCharsets() {
return AccessController.doPrivileged(
new PrivilegedAction<>() {
public SortedMap<String,Charset> run() {
TreeMap<String,Charset> m =
new TreeMap<>(
String.CASE_INSENSITIVE_ORDER);
put(standardProvider.charsets(), m);
CharsetProvider[] ecps = ExtendedProviderHolder.extendedProviders;
for (CharsetProvider ecp :ecps) {
put(ecp.charsets(), m);
}
for (Iterator<CharsetProvider> i = providers(); i.hasNext();) {
CharsetProvider cp = i.next();
put(cp.charsets(), m);
}
return Collections.unmodifiableSortedMap(m);
}
});
}
private static volatile Charset defaultCharset;
public static Charset defaultCharset() {
if (defaultCharset == null) {
synchronized (Charset.class) {
String csn = GetPropertyAction
.privilegedGetProperty(“file.encoding”);
Charset cs = lookup(csn);
if (cs != null)
defaultCharset = cs;
else
defaultCharset = sun.nio.cs.UTF_8.INSTANCE;
}
}
return defaultCharset;
}
/* — Instance fields and methods — */
private final String name; // tickles a bug in oldjavac
private final String[] aliases; // tickles a bug in oldjavac
private Set<String> aliasSet = null;
protected Charset(String canonicalName, String[] aliases) {
String[] as = Objects.requireNonNullElse(aliases, zeroAliases);
// Skip checks for the standard, built-in Charsets we always load
// during initialization.
if (canonicalName != “ISO-8859-1”
&& canonicalName != “US-ASCII”
&& canonicalName != “UTF-8”) {
checkName(canonicalName);
for (int i = 0; i < as.length; i++) {
checkName(as[i]);
}
}
this.name = canonicalName;
this.aliases = as;
}
public final String name() {
return name;
}
public final Set<String> aliases() {
if (aliasSet != null)
return aliasSet;
int n = aliases.length;
HashSet<String> hs = new HashSet<>(n);
for (int i = 0; i < n; i++)
hs.add(aliases[i]);
aliasSet = Collections.unmodifiableSet(hs);
return aliasSet;
}
public String displayName() {
return name;
}
public final boolean isRegistered() {
return !name.startsWith(“X-“) && !name.startsWith(“x-“);
}
public String displayName(Locale locale) {
return name;
}
public abstract boolean contains(Charset cs);
public abstract CharsetDecoder newDecoder();
public abstract CharsetEncoder newEncoder();
public boolean canEncode() {
return true;
}
public final CharBuffer decode(ByteBuffer bb) {
try {
return ThreadLocalCoders.decoderFor(this)
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.decode(bb);
} catch (CharacterCodingException x) {
throw new Error(x); // Can’t happen
}
}
public final ByteBuffer encode(CharBuffer cb) {
try {
return ThreadLocalCoders.encoderFor(this)
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE)
.encode(cb);
} catch (CharacterCodingException x) {
throw new Error(x); // Can’t happen
}
}
public final ByteBuffer encode(String str) {
return encode(CharBuffer.wrap(str));
}
public final int compareTo(Charset that) {
return (name().compareToIgnoreCase(that.name()));
}
public final int hashCode() {
return name().hashCode();
}
public final boolean equals(Object ob) {
if (!(ob instanceof Charset))
return false;
if (this == ob)
return true;
return name.equals(((Charset)ob).name());
}
public final String toString() {
return name();
}
}