// jTDS JDBC Driver for Microsoft SQL Server and Sybase // Copyright (C) 2004 The jTDS Project // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library 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 // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA // package net.sourceforge.jtds.jdbc; import java.sql.SQLException; import java.util.HashMap; import java.util.Properties; import java.util.Enumeration; import java.io.InputStream; import java.io.IOException; import net.sourceforge.jtds.util.Logger; /** * Loads and stores information about character sets. Static fields and methods * are concerned with loading, storing and retrieval of all character set * information, while non-static fields and methods describe a particular * character set (Java charset name and whether it's a multi-byte charset). * <p> * <b>Note:</b> Only one <code>CharsetInfo</code> instance exists per charset. * This allows simple equality comparisons between instances retrieved with any * of the <code>get</code> methods. * * @author Alin Sinpalean * @version $Id: CharsetInfo.java,v 1.5 2007-07-08 17:28:23 bheineman Exp $ */ public final class CharsetInfo { // // Static fields and methods // /** Name of the <code>Charsets.properties</code> resource. */ private static final String CHARSETS_RESOURCE_NAME = "net/sourceforge/jtds/jdbc/Charsets.properties"; /** Server charset to Java charset map. */ private static final HashMap charsets = new HashMap(); /** Locale id to Java charset map. */ private static final HashMap lcidToCharsetMap = new HashMap(); /** Sort order to Java charset map. */ private static final CharsetInfo[] sortToCharsetMap = new CharsetInfo[256]; static { // Load character set mappings InputStream stream = null; try { // getContextClassLoader needed to ensure driver // works with Tomcat class loading rules. ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (classLoader != null) { stream = classLoader.getResourceAsStream( CHARSETS_RESOURCE_NAME); } if (stream == null) { // The doPrivileged() call stops the SecurityManager from // checking further in the stack trace whether all callers have // the permission to load Charsets.properties stream = (InputStream) java.security.AccessController.doPrivileged( new java.security.PrivilegedAction() { public Object run() { ClassLoader loader = CharsetInfo.class.getClassLoader(); // getClassLoader() may return null if the class was loaded by // the bootstrap ClassLoader if (loader == null) { loader = ClassLoader.getSystemClassLoader(); } return loader.getResourceAsStream( CHARSETS_RESOURCE_NAME); } } ); } if (stream != null) { Properties tmp = new Properties(); tmp.load(stream); HashMap instances = new HashMap(); for (Enumeration e = tmp.propertyNames(); e.hasMoreElements();) { String key = (String) e.nextElement(); CharsetInfo value = new CharsetInfo(tmp.getProperty(key)); // Ensure only one CharsetInfo instance exists per charset CharsetInfo prevInstance = (CharsetInfo) instances.get( value.getCharset()); if (prevInstance != null) { if (prevInstance.isWideChars() != value.isWideChars()) { throw new IllegalStateException( "Inconsistent Charsets.properties"); } value = prevInstance; } if (key.startsWith("LCID_")) { Integer lcid = new Integer(key.substring(5)); lcidToCharsetMap.put(lcid, value); } else if (key.startsWith("SORT_")) { sortToCharsetMap[Integer.parseInt(key.substring(5))] = value; } else { charsets.put(key, value); } } } else { Logger.println("Can't load Charsets.properties"); } } catch (IOException e) { // Can't load properties file for some reason Logger.logException(e); } finally { if (stream!=null) { try { stream.close(); } catch (Exception e) { // errors can be ignored } } } } /** * Retrieves the <code>CharsetInfo</code> instance asociated with the * specified server charset. * * @param serverCharset the server-specific character set name * @return the associated <code>CharsetInfo</code> */ public static CharsetInfo getCharset(String serverCharset) { return (CharsetInfo) charsets.get(serverCharset.toUpperCase()); } /** * Retrieves the <code>CharsetInfo</code> instance asociated with the * specified LCID. * * @param lcid the server LCID * @return the associated <code>CharsetInfo</code> */ public static CharsetInfo getCharsetForLCID(int lcid) { return (CharsetInfo) lcidToCharsetMap.get(new Integer(lcid)); } /** * Retrieves the <code>CharsetInfo</code> instance asociated with the * specified sort order. * * @param sortOrder the server sort order * @return the associated <code>CharsetInfo</code> */ public static CharsetInfo getCharsetForSortOrder(int sortOrder) { return sortToCharsetMap[sortOrder]; } /** * Retrieves the <code>CharsetInfo</code> instance asociated with the * specified collation. * * @param collation the server LCID * @return the associated <code>CharsetInfo</code> */ public static CharsetInfo getCharset(byte[] collation) throws SQLException { CharsetInfo charset; if (collation[4] != 0) { // The charset is determined by the sort order charset = getCharsetForSortOrder((int) collation[4] & 0xFF); } else { // The charset is determined by the LCID charset = getCharsetForLCID( ((int) collation[2] & 0x0F) << 16 | ((int) collation[1] & 0xFF) << 8 | ((int) collation[0] & 0xFF)); } if (charset == null) { throw new SQLException( Messages.get("error.charset.nocollation", Support.toHex(collation)), "2C000"); } return charset; } // // Non-static fields and methods // /** The Java character set name. */ private final String charset; /** Indicates whether current charset is wide (ie multi-byte). */ private final boolean wideChars; /** * Constructs a <code>CharsetInfo</code> object from a character set * descriptor of the form: charset preceded by a numeric value indicating * whether it's a multibyte character set (>1) or not (1) and a vertical * bar (|), eg "1|Cp1252" or "2|MS936". * * @param descriptor the charset descriptor */ public CharsetInfo(String descriptor) { wideChars = !"1".equals(descriptor.substring(0, 1)); charset = descriptor.substring(2); } /** * Retrieves the charset name. */ public String getCharset() { return charset; } /** * Retrieves whether the caracter set is wide (ie multi-byte). */ public boolean isWideChars() { return wideChars; } public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof CharsetInfo)) { return false; } final CharsetInfo charsetInfo = (CharsetInfo) o; if (!charset.equals(charsetInfo.charset)) { return false; } return true; } public int hashCode() { return charset.hashCode(); } public String toString() { return charset; } }