/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.wicket.extensions.util.encoding; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; /** * This class maintains a set of mappers defining mappings between locales and the corresponding * charsets. The mappings are defined as properties between locale and charset names. The * definitions can be listed in property files located in user's home directory, Java home directory * or the current class jar. In addition, this class maintains static default mappings and * constructors support application specific mappings. * * This source has originally been taken from the jakarta Turbine project. * * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha </a> */ public final class CharSetMap { /** * The default charset when nothing else is applicable. */ public static final String DEFAULT_CHARSET = "ISO-8859-1"; /** * The name for charset mapper resources. */ public static final String CHARSET_RESOURCE = "charset.properties"; /** * Priorities of available mappers. */ private static final int MAP_CACHE = 0; private static final int MAP_PROG = 1; private static final int MAP_HOME = 2; private static final int MAP_SYS = 3; private static final int MAP_JAR = 4; private static final int MAP_COM = 5; /** * A common charset mapper for languages. */ private static final Map<String, String> commonMapper = new HashMap<>(); static { commonMapper.put("ar", "ISO-8859-6"); commonMapper.put("be", "ISO-8859-5"); commonMapper.put("bg", "ISO-8859-5"); commonMapper.put("ca", "ISO-8859-1"); commonMapper.put("cs", "ISO-8859-2"); commonMapper.put("da", "ISO-8859-1"); commonMapper.put("de", "ISO-8859-1"); commonMapper.put("el", "ISO-8859-7"); commonMapper.put("en", "ISO-8859-1"); commonMapper.put("es", "ISO-8859-1"); commonMapper.put("et", "ISO-8859-1"); commonMapper.put("fi", "ISO-8859-1"); commonMapper.put("fr", "ISO-8859-1"); commonMapper.put("hr", "ISO-8859-2"); commonMapper.put("hu", "ISO-8859-2"); commonMapper.put("is", "ISO-8859-1"); commonMapper.put("it", "ISO-8859-1"); commonMapper.put("iw", "ISO-8859-8"); commonMapper.put("ja", "Shift_JIS"); commonMapper.put("ko", "EUC-KR"); commonMapper.put("lt", "ISO-8859-2"); commonMapper.put("lv", "ISO-8859-2"); commonMapper.put("mk", "ISO-8859-5"); commonMapper.put("nl", "ISO-8859-1"); commonMapper.put("no", "ISO-8859-1"); commonMapper.put("pl", "ISO-8859-2"); commonMapper.put("pt", "ISO-8859-1"); commonMapper.put("ro", "ISO-8859-2"); commonMapper.put("ru", "ISO-8859-5"); commonMapper.put("sh", "ISO-8859-5"); commonMapper.put("sk", "ISO-8859-2"); commonMapper.put("sl", "ISO-8859-2"); commonMapper.put("sq", "ISO-8859-2"); commonMapper.put("sr", "ISO-8859-5"); commonMapper.put("sv", "ISO-8859-1"); commonMapper.put("tr", "ISO-8859-9"); commonMapper.put("uk", "ISO-8859-5"); commonMapper.put("zh", "GB2312"); commonMapper.put("zh_TW", "Big5"); } /** * A collection of available charset mappers. */ private final List<Map<String, String>> mappers = new ArrayList<>(); { for (int i = 0; i < MAP_COM; i++) { mappers.add(null); } } /** * Loads mappings from a stream. * * @param input * an input stream. * @return the mappings. * @throws IOException * for an incorrect stream. */ protected static Map<String, String> loadStream(final InputStream input) throws IOException { return createMap(input); } private static Map<String, String> createMap(final InputStream input) throws IOException { final Properties props = new Properties(); props.load(input); return createMap(props); } private static Map<String, String> createMap(final Properties props) { HashMap<String, String> map = new HashMap<>(); for (Object key : props.keySet()) { String keyString = (String)key; map.put(keyString, props.getProperty(keyString)); } return map; } /** * Loads mappings from a file. * * @param file * a file. * @return the mappings. * @throws IOException * for an incorrect file. */ protected static Map<String, String> loadFile(final File file) throws IOException { return loadStream(new FileInputStream(file)); } /** * Loads mappings from a file path. * * @param path * a file path. * @return the mappings. * @throws IOException * for an incorrect file. */ protected static Map<String, String> loadPath(final String path) throws IOException { return loadFile(new File(path)); } /** * Loads mappings from a resource. * * @param name * a resource name. * @return the mappings. */ protected static Map<String, String> loadResource(final String name) { final InputStream input = CharSetMap.class.getResourceAsStream(name); if (input != null) { try { return loadStream(input); } catch (IOException ex) { return null; } } return null; } /** * Constructs a new charset map with default mappers. */ public CharSetMap() { String path; try { // Check whether the user directory contains mappings. path = System.getProperty("user.home"); if (path != null) { path = path + File.separator + CHARSET_RESOURCE; mappers.add(MAP_HOME, loadPath(path)); } } catch (Exception ex) { // ignore } try { // Check whether the system directory contains mappings. path = System.getProperty("java.home") + File.separator + "lib" + File.separator + CHARSET_RESOURCE; mappers.add(MAP_SYS, loadPath(path)); } catch (Exception ex) { // ignore } // Check whether the current class jar contains mappings. mappers.add(MAP_JAR, loadResource("/META-INF/" + CHARSET_RESOURCE)); // Set the common mapper to have the lowest priority. mappers.add(MAP_COM, commonMapper); // Set the cache mapper to have the highest priority. mappers.add(MAP_CACHE, new Hashtable<String, String>()); } /** * Constructs a charset map from properties. * * @param props * charset mapping properties. */ public CharSetMap(final Properties props) { this(); mappers.add(MAP_PROG, createMap(props)); } /** * Constructs a charset map read from a stream. * * @param input * an input stream. * @throws IOException * for an incorrect stream. */ public CharSetMap(final InputStream input) throws IOException { this(); mappers.add(MAP_PROG, loadStream(input)); } /** * Constructs a charset map read from a property file. * * @param file * a property file. * @throws IOException * for an incorrect property file. */ public CharSetMap(final File file) throws IOException { this(); mappers.add(MAP_PROG, loadFile(file)); } /** * Constructs a charset map read from a property file path. * * @param path * a property file path. * @throws IOException * for an incorrect property file. */ public CharSetMap(final String path) throws IOException { this(); mappers.add(MAP_PROG, loadPath(path)); } /** * Sets a locale-charset mapping. * * @param key * the key for the charset. * @param charset * the corresponding charset. */ @SuppressWarnings({ "unchecked" }) public final synchronized void setCharSet(final String key, final String charset) { HashMap<String, String> mapper = (HashMap<String, String>)mappers.get(MAP_PROG); if (mapper != null) { mapper = (HashMap<String, String>)mapper.clone(); } else { mapper = new HashMap<>(); } mapper.put(key, charset); mappers.add(MAP_PROG, mapper); mappers.get(MAP_CACHE).clear(); } /** * Gets the charset for a locale. First a locale specific charset is searched for, then a * country specific one and lastly a language specific one. If none is found, the default * charset is returned. * * @param locale * the locale. * @return the charset. */ public final String getCharSet(final Locale locale) { // Check the cache first. String key = locale.toString(); if (key.length() == 0) { key = "__" + locale.getVariant(); if (key.length() == 2) { return DEFAULT_CHARSET; } } String charset = searchCharSet(key); if (charset.length() == 0) { // Not found, perform a full search and update the cache. String[] items = new String[3]; items[2] = locale.getVariant(); items[1] = locale.getCountry(); items[0] = locale.getLanguage(); charset = searchCharSet(items); if (charset.length() == 0) { charset = DEFAULT_CHARSET; } mappers.get(MAP_CACHE).put(key, charset); } return charset; } /** * Gets the charset for a locale with a variant. The search is performed in the following order: * "lang"_"country"_"variant"="charset", _"country"_"variant"="charset", * "lang"__"variant"="charset", __"variant"="charset", "lang"_"country"="charset", * _"country"="charset", "lang"="charset". If nothing of the above is found, the default charset * is returned. * * @param locale * the locale. * @param variant * a variant field. * @return the charset. */ public final String getCharSet(final Locale locale, final String variant) { // Check the cache first. if ((variant != null) && (variant.length() > 0)) { String key = locale.toString(); if (key.length() == 0) { key = "__" + locale.getVariant(); if (key.length() > 2) { key += '_' + variant; } else { key += variant; } } else if (locale.getCountry().length() == 0) { key += "__" + variant; } else { key += '_' + variant; } String charset = searchCharSet(key); if (charset.length() == 0) { // Not found, perform a full search and update the cache. String[] items = new String[4]; items[3] = variant; items[2] = locale.getVariant(); items[1] = locale.getCountry(); items[0] = locale.getLanguage(); charset = searchCharSet(items); if (charset.length() == 0) { charset = DEFAULT_CHARSET; } mappers.get(MAP_CACHE).put(key, charset); } return charset; } return getCharSet(locale); } /** * Gets the charset for a specified key. * * @param key * the key for the charset. * @return the found charset or the default one. */ public final String getCharSet(final String key) { final String charset = searchCharSet(key); return charset.length() > 0 ? charset : DEFAULT_CHARSET; } /** * Gets the charset for a specified key. * * @param key * the key for the charset. * @param def * the default charset if none is found. * @return the found charset or the given default. */ public final String getCharSet(final String key, final String def) { String charset = searchCharSet(key); return charset.length() > 0 ? charset : def; } /** * Searches for a charset for a specified locale. * * @param items * an array of locale items. * @return the found charset or an empty string. */ private final String searchCharSet(final String[] items) { String charset; final StringBuilder sb = new StringBuilder(); for (int i = items.length; i > 0; i--) { charset = searchCharSet(items, sb, i); if (charset.length() > 0) { return charset; } sb.setLength(0); } return ""; } /** * Searches recursively for a charset for a specified locale. * * @param items * an array of locale items. * @param base * a buffer of base items. * @param count * the number of items to go through. * @return the found charset or an empty string. */ private final String searchCharSet(final String[] items, final StringBuilder base, int count) { if ((--count >= 0) && (items[count] != null) && (items[count].length() > 0)) { String charset; base.insert(0, items[count]); int length = base.length(); for (int i = count; i > 0; i--) { if ((i == count) || (i <= 1)) { base.insert(0, '_'); length++; } charset = searchCharSet(items, base, i); if (charset.length() > 0) { return charset; } base.delete(0, base.length() - length); } return searchCharSet(base.toString()); } return ""; } /** * Searches for a charset for a specified key. * * @param key * the key for the charset. * @return the found charset or an empty string. */ private final String searchCharSet(final String key) { if ((key != null) && (key.length() > 0)) { // Go through mappers. Map<String, String> mapper; String charset; for (int i = 0; i < mappers.size(); i++) { mapper = mappers.get(i); if (mapper != null) { charset = mapper.get(key); if (charset != null) { // Update the cache. if (i > MAP_CACHE) { mappers.get(MAP_CACHE).put(key, charset); } return charset; } } } // Not found, add an empty string to the cache. mappers.get(MAP_CACHE).put(key, ""); } return ""; } /** * Sets a common locale-charset mapping. * * @param key * the key for the charset. * @param charset * the corresponding charset. */ @SuppressWarnings({ "unchecked" }) protected final synchronized void setCommonCharSet(final String key, final String charset) { HashMap<String, String> map = (HashMap<String, String>)mappers.get(MAP_COM); final HashMap<String, String> mapper = (HashMap<String, String>)map.clone(); mapper.put(key, charset); mappers.add(MAP_COM, mapper); mappers.get(MAP_CACHE).clear(); } }