1 | /* |
2 | Copyright (C) 2002-2004 MySQL AB |
3 | |
4 | This program is free software; you can redistribute it and/or modify |
5 | it under the terms of version 2 of the GNU General Public License as |
6 | published by the Free Software Foundation. |
7 | |
8 | There are special exceptions to the terms and conditions of the GPL |
9 | as it is applied to this software. View the full text of the |
10 | exception in file EXCEPTIONS-CONNECTOR-J in the directory of this |
11 | software distribution. |
12 | |
13 | This program is distributed in the hope that it will be useful, |
14 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | GNU General Public License for more details. |
17 | |
18 | You should have received a copy of the GNU General Public License |
19 | along with this program; if not, write to the Free Software |
20 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
21 | |
22 | |
23 | |
24 | */ |
25 | package com.mysql.jdbc; |
26 | |
27 | import java.io.UnsupportedEncodingException; |
28 | |
29 | import java.sql.SQLException; |
30 | import java.util.HashMap; |
31 | import java.util.Map; |
32 | |
33 | /** |
34 | * Converter for char[]->byte[] and byte[]->char[] for single-byte character |
35 | * sets. |
36 | * |
37 | * Much faster (5-6x) than the built-in solution that ships with the JVM, even |
38 | * with JDK-1.4.x and NewIo. |
39 | * |
40 | * @author Mark Matthews |
41 | */ |
42 | public class SingleByteCharsetConverter { |
43 | |
44 | private static final int BYTE_RANGE = (1 + Byte.MAX_VALUE) - Byte.MIN_VALUE; |
45 | private static byte[] allBytes = new byte[BYTE_RANGE]; |
46 | private static final Map CONVERTER_MAP = new HashMap(); |
47 | |
48 | private final static byte[] EMPTY_BYTE_ARRAY = new byte[0]; |
49 | |
50 | // The initial charToByteMap, with all char mappings mapped |
51 | // to (byte) '?', so that unknown characters are mapped to '?' |
52 | // instead of '\0' (which means end-of-string to MySQL). |
53 | private static byte[] unknownCharsMap = new byte[65536]; |
54 | |
55 | static { |
56 | for (int i = Byte.MIN_VALUE; i <= Byte.MAX_VALUE; i++) { |
57 | allBytes[i - Byte.MIN_VALUE] = (byte) i; |
58 | } |
59 | |
60 | for (int i = 0; i < unknownCharsMap.length; i++) { |
61 | unknownCharsMap[i] = (byte) '?'; // use something 'sane' for |
62 | // unknown chars |
63 | } |
64 | } |
65 | |
66 | // ~ Instance fields |
67 | // -------------------------------------------------------- |
68 | |
69 | /** |
70 | * Get a converter for the given encoding name |
71 | * |
72 | * @param encodingName |
73 | * the Java character encoding name |
74 | * |
75 | * @return a converter for the given encoding name |
76 | * @throws UnsupportedEncodingException |
77 | * if the character encoding is not supported |
78 | */ |
79 | public static synchronized SingleByteCharsetConverter getInstance( |
80 | String encodingName, Connection conn) |
81 | throws UnsupportedEncodingException, SQLException { |
82 | SingleByteCharsetConverter instance = (SingleByteCharsetConverter) CONVERTER_MAP |
83 | .get(encodingName); |
84 | |
85 | if (instance == null) { |
86 | instance = initCharset(encodingName); |
87 | } |
88 | |
89 | return instance; |
90 | } |
91 | |
92 | /** |
93 | * Initialize the shared instance of a converter for the given character |
94 | * encoding. |
95 | * |
96 | * @param javaEncodingName |
97 | * the Java name for the character set to initialize |
98 | * @return a converter for the given character set |
99 | * @throws UnsupportedEncodingException |
100 | * if the character encoding is not supported |
101 | */ |
102 | public static SingleByteCharsetConverter initCharset(String javaEncodingName) |
103 | throws UnsupportedEncodingException, SQLException { |
104 | if (CharsetMapping.isMultibyteCharset(javaEncodingName)) { |
105 | return null; |
106 | } |
107 | |
108 | SingleByteCharsetConverter converter = new SingleByteCharsetConverter( |
109 | javaEncodingName); |
110 | |
111 | CONVERTER_MAP.put(javaEncodingName, converter); |
112 | |
113 | return converter; |
114 | } |
115 | |
116 | // ~ Constructors |
117 | // ----------------------------------------------------------- |
118 | |
119 | /** |
120 | * Convert the byte buffer from startPos to a length of length to a string |
121 | * using the default platform encoding. |
122 | * |
123 | * @param buffer |
124 | * the bytes to convert |
125 | * @param startPos |
126 | * the index to start at |
127 | * @param length |
128 | * the number of bytes to convert |
129 | * @return the String representation of the given bytes |
130 | */ |
131 | public static String toStringDefaultEncoding(byte[] buffer, int startPos, |
132 | int length) { |
133 | return new String(buffer, startPos, length); |
134 | } |
135 | |
136 | // ~ Methods |
137 | // ---------------------------------------------------------------- |
138 | |
139 | private char[] byteToChars = new char[BYTE_RANGE]; |
140 | |
141 | private byte[] charToByteMap = new byte[65536]; |
142 | |
143 | /** |
144 | * Prevent instantiation, called out of static method initCharset(). |
145 | * |
146 | * @param encodingName |
147 | * a JVM character encoding |
148 | * @throws UnsupportedEncodingException |
149 | * if the JVM does not support the encoding |
150 | */ |
151 | private SingleByteCharsetConverter(String encodingName) |
152 | throws UnsupportedEncodingException { |
153 | String allBytesString = new String(allBytes, 0, BYTE_RANGE, |
154 | encodingName); |
155 | int allBytesLen = allBytesString.length(); |
156 | |
157 | System.arraycopy(unknownCharsMap, 0, this.charToByteMap, 0, |
158 | this.charToByteMap.length); |
159 | |
160 | for (int i = 0; i < BYTE_RANGE && i < allBytesLen; i++) { |
161 | char c = allBytesString.charAt(i); |
162 | this.byteToChars[i] = c; |
163 | this.charToByteMap[c] = allBytes[i]; |
164 | } |
165 | } |
166 | |
167 | public final byte[] toBytes(char[] c) { |
168 | if (c == null) { |
169 | return null; |
170 | } |
171 | |
172 | int length = c.length; |
173 | byte[] bytes = new byte[length]; |
174 | |
175 | for (int i = 0; i < length; i++) { |
176 | bytes[i] = this.charToByteMap[c[i]]; |
177 | } |
178 | |
179 | return bytes; |
180 | } |
181 | |
182 | public final byte[] toBytes(char[] chars, int offset, int length) { |
183 | if (chars == null) { |
184 | return null; |
185 | } |
186 | |
187 | if (length == 0) { |
188 | return EMPTY_BYTE_ARRAY; |
189 | } |
190 | |
191 | byte[] bytes = new byte[length]; |
192 | |
193 | for (int i = 0; (i < length); i++) { |
194 | bytes[i] = this.charToByteMap[chars[i + offset]]; |
195 | } |
196 | |
197 | return bytes; |
198 | } |
199 | |
200 | /** |
201 | * Convert the given string to an array of bytes. |
202 | * |
203 | * @param s |
204 | * the String to convert |
205 | * @return the bytes that make up the String |
206 | */ |
207 | public final byte[] toBytes(String s) { |
208 | if (s == null) { |
209 | return null; |
210 | } |
211 | |
212 | int length = s.length(); |
213 | byte[] bytes = new byte[length]; |
214 | |
215 | for (int i = 0; i < length; i++) { |
216 | bytes[i] = this.charToByteMap[s.charAt(i)]; |
217 | } |
218 | |
219 | return bytes; |
220 | } |
221 | |
222 | /** |
223 | * Convert the given string to an array of bytes. |
224 | * |
225 | * @param s |
226 | * the String to convert |
227 | * @param offset |
228 | * the offset to start at |
229 | * @param length |
230 | * length (max) to convert |
231 | * |
232 | * @return the bytes that make up the String |
233 | */ |
234 | public final byte[] toBytes(String s, int offset, int length) { |
235 | if (s == null) { |
236 | return null; |
237 | } |
238 | |
239 | if (length == 0) { |
240 | return EMPTY_BYTE_ARRAY; |
241 | } |
242 | |
243 | byte[] bytes = new byte[length]; |
244 | |
245 | for (int i = 0; (i < length); i++) { |
246 | char c = s.charAt(i + offset); |
247 | bytes[i] = this.charToByteMap[c]; |
248 | } |
249 | |
250 | return bytes; |
251 | } |
252 | |
253 | /** |
254 | * Convert the byte buffer to a string using this instance's character |
255 | * encoding. |
256 | * |
257 | * @param buffer |
258 | * the bytes to convert to a String |
259 | * @return the converted String |
260 | */ |
261 | public final String toString(byte[] buffer) { |
262 | return toString(buffer, 0, buffer.length); |
263 | } |
264 | |
265 | /** |
266 | * Convert the byte buffer from startPos to a length of length to a string |
267 | * using this instance's character encoding. |
268 | * |
269 | * @param buffer |
270 | * the bytes to convert |
271 | * @param startPos |
272 | * the index to start at |
273 | * @param length |
274 | * the number of bytes to convert |
275 | * @return the String representation of the given bytes |
276 | */ |
277 | public final String toString(byte[] buffer, int startPos, int length) { |
278 | char[] charArray = new char[length]; |
279 | int readpoint = startPos; |
280 | |
281 | for (int i = 0; i < length; i++) { |
282 | charArray[i] = this.byteToChars[buffer[readpoint] - Byte.MIN_VALUE]; |
283 | readpoint++; |
284 | } |
285 | |
286 | return new String(charArray); |
287 | } |
288 | } |