001: /*
002: * $Id: CharSetMap.java 458489 2006-01-04 09:28:14Z ivaynberg $
003: * $Revision: 458489 $
004: * $Date: 2006-01-04 10:28:14 +0100 (Wed, 04 Jan 2006) $
005: *
006: * ====================================================================
007: * Licensed under the Apache License, Version 2.0 (the "License");
008: * you may not use this file except in compliance with the License.
009: * You may obtain a copy of the License at
010: *
011: * http://www.apache.org/licenses/LICENSE-2.0
012: *
013: * Unless required by applicable law or agreed to in writing, software
014: * distributed under the License is distributed on an "AS IS" BASIS,
015: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
016: * See the License for the specific language governing permissions and
017: * limitations under the License.
018: */
019: package wicket.extensions.util.encoding;
020:
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.io.IOException;
024: import java.io.InputStream;
025: import java.util.HashMap;
026: import java.util.Hashtable;
027: import java.util.Locale;
028: import java.util.Map;
029: import java.util.Properties;
030:
031: /**
032: * This class maintains a set of mappers defining mappings between locales and
033: * the corresponding charsets. The mappings are defined as properties between
034: * locale and charset names. The definitions can be listed in property files
035: * located in user's home directory, Java home directory or the current class
036: * jar. In addition, this class maintains static default mappings and
037: * constructors support application specific mappings.
038: *
039: * This source has originally been taken from the jakarta Turbine project.
040: *
041: * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha </a>
042: */
043: public final class CharSetMap {
044: /**
045: * The default charset when nothing else is applicable.
046: */
047: public static final String DEFAULT_CHARSET = "ISO-8859-1";
048:
049: /**
050: * The name for charset mapper resources.
051: */
052: public static final String CHARSET_RESOURCE = "charset.properties";
053:
054: /**
055: * Priorities of available mappers.
056: */
057: private static final int MAP_CACHE = 0;
058: private static final int MAP_PROG = 1;
059: private static final int MAP_HOME = 2;
060: private static final int MAP_SYS = 3;
061: private static final int MAP_JAR = 4;
062: private static final int MAP_COM = 5;
063:
064: /**
065: * A common charset mapper for languages.
066: */
067: private static final Map commonMapper = new HashMap();
068:
069: static {
070: commonMapper.put("ar", "ISO-8859-6");
071: commonMapper.put("be", "ISO-8859-5");
072: commonMapper.put("bg", "ISO-8859-5");
073: commonMapper.put("ca", "ISO-8859-1");
074: commonMapper.put("cs", "ISO-8859-2");
075: commonMapper.put("da", "ISO-8859-1");
076: commonMapper.put("de", "ISO-8859-1");
077: commonMapper.put("el", "ISO-8859-7");
078: commonMapper.put("en", "ISO-8859-1");
079: commonMapper.put("es", "ISO-8859-1");
080: commonMapper.put("et", "ISO-8859-1");
081: commonMapper.put("fi", "ISO-8859-1");
082: commonMapper.put("fr", "ISO-8859-1");
083: commonMapper.put("hr", "ISO-8859-2");
084: commonMapper.put("hu", "ISO-8859-2");
085: commonMapper.put("is", "ISO-8859-1");
086: commonMapper.put("it", "ISO-8859-1");
087: commonMapper.put("iw", "ISO-8859-8");
088: commonMapper.put("ja", "Shift_JIS");
089: commonMapper.put("ko", "EUC-KR");
090: commonMapper.put("lt", "ISO-8859-2");
091: commonMapper.put("lv", "ISO-8859-2");
092: commonMapper.put("mk", "ISO-8859-5");
093: commonMapper.put("nl", "ISO-8859-1");
094: commonMapper.put("no", "ISO-8859-1");
095: commonMapper.put("pl", "ISO-8859-2");
096: commonMapper.put("pt", "ISO-8859-1");
097: commonMapper.put("ro", "ISO-8859-2");
098: commonMapper.put("ru", "ISO-8859-5");
099: commonMapper.put("sh", "ISO-8859-5");
100: commonMapper.put("sk", "ISO-8859-2");
101: commonMapper.put("sl", "ISO-8859-2");
102: commonMapper.put("sq", "ISO-8859-2");
103: commonMapper.put("sr", "ISO-8859-5");
104: commonMapper.put("sv", "ISO-8859-1");
105: commonMapper.put("tr", "ISO-8859-9");
106: commonMapper.put("uk", "ISO-8859-5");
107: commonMapper.put("zh", "GB2312");
108: commonMapper.put("zh_TW", "Big5");
109: }
110:
111: /**
112: * An array of available charset mappers.
113: */
114: private final Map mappers[] = new Map[6];
115:
116: /**
117: * Loads mappings from a stream.
118: *
119: * @param input
120: * an input stream.
121: * @return the mappings.
122: * @throws IOException
123: * for an incorrect stream.
124: */
125: protected final static Map loadStream(final InputStream input)
126: throws IOException {
127: final Properties props = new Properties();
128: props.load(input);
129: return new HashMap(props);
130: }
131:
132: /**
133: * Loads mappings from a file.
134: *
135: * @param file
136: * a file.
137: * @return the mappings.
138: * @throws IOException
139: * for an incorrect file.
140: */
141: protected final static Map loadFile(final File file)
142: throws IOException {
143: return loadStream(new FileInputStream(file));
144: }
145:
146: /**
147: * Loads mappings from a file path.
148: *
149: * @param path
150: * a file path.
151: * @return the mappings.
152: * @throws IOException
153: * for an incorrect file.
154: */
155: protected final static Map loadPath(final String path)
156: throws IOException {
157: return loadFile(new File(path));
158: }
159:
160: /**
161: * Loads mappings from a resource.
162: *
163: * @param name
164: * a resource name.
165: * @return the mappings.
166: */
167: protected final static Map loadResource(final String name) {
168: final InputStream input = CharSetMap.class
169: .getResourceAsStream(name);
170: if (input != null) {
171: try {
172: return loadStream(input);
173: } catch (IOException ex) {
174: return null;
175: }
176: }
177:
178: return null;
179: }
180:
181: /**
182: * Constructs a new charset map with default mappers.
183: */
184: public CharSetMap() {
185: String path;
186: try {
187: // Check whether the user directory contains mappings.
188: path = System.getProperty("user.home");
189: if (path != null) {
190: path = path + File.separator + CHARSET_RESOURCE;
191: mappers[MAP_HOME] = loadPath(path);
192: }
193: } catch (Exception ex) {
194: // ignore
195: }
196:
197: try {
198: // Check whether the system directory contains mappings.
199: path = System.getProperty("java.home") + File.separator
200: + "lib" + File.separator + CHARSET_RESOURCE;
201:
202: mappers[MAP_SYS] = loadPath(path);
203: } catch (Exception ex) {
204: // ignore
205: }
206:
207: // Check whether the current class jar contains mappings.
208: mappers[MAP_JAR] = loadResource("/META-INF/" + CHARSET_RESOURCE);
209:
210: // Set the common mapper to have the lowest priority.
211: mappers[MAP_COM] = commonMapper;
212:
213: // Set the cache mapper to have the highest priority.
214: mappers[MAP_CACHE] = new Hashtable();
215: }
216:
217: /**
218: * Contructs a charset map from properties.
219: *
220: * @param props
221: * charset mapping propeties.
222: */
223: public CharSetMap(final Properties props) {
224: this ();
225: mappers[MAP_PROG] = new HashMap(props);
226: }
227:
228: /**
229: * Contructs a charset map read from a stream.
230: *
231: * @param input
232: * an input stream.
233: * @throws IOException
234: * for an incorrect stream.
235: */
236: public CharSetMap(final InputStream input) throws IOException {
237: this ();
238: mappers[MAP_PROG] = loadStream(input);
239: }
240:
241: /**
242: * Contructs a charset map read from a property file.
243: *
244: * @param file
245: * a property file.
246: * @throws IOException
247: * for an incorrect property file.
248: */
249: public CharSetMap(final File file) throws IOException {
250: this ();
251: mappers[MAP_PROG] = loadFile(file);
252: }
253:
254: /**
255: * Contructs a charset map read from a property file path.
256: *
257: * @param path
258: * a property file path.
259: * @throws IOException
260: * for an incorrect property file.
261: */
262: public CharSetMap(final String path) throws IOException {
263: this ();
264: mappers[MAP_PROG] = loadPath(path);
265: }
266:
267: /**
268: * Sets a locale-charset mapping.
269: *
270: * @param key
271: * the key for the charset.
272: * @param charset
273: * the corresponding charset.
274: */
275: public final synchronized void setCharSet(final String key,
276: final String charset) {
277: HashMap mapper = (HashMap) mappers[MAP_PROG];
278: mapper = (mapper != null ? (HashMap) mapper.clone()
279: : new HashMap());
280: mapper.put(key, charset);
281: mappers[MAP_PROG] = mapper;
282: mappers[MAP_CACHE].clear();
283: }
284:
285: /**
286: * Gets the charset for a locale. First a locale specific charset is
287: * searched for, then a country specific one and lastly a language specific
288: * one. If none is found, the default charset is returned.
289: *
290: * @param locale
291: * the locale.
292: * @return the charset.
293: */
294: public final String getCharSet(final Locale locale) {
295: // Check the cache first.
296: String key = locale.toString();
297: if (key.length() == 0) {
298: key = "__" + locale.getVariant();
299: if (key.length() == 2) {
300: return DEFAULT_CHARSET;
301: }
302: }
303:
304: String charset = searchCharSet(key);
305: if (charset.length() == 0) {
306: // Not found, perform a full search and update the cache.
307: String[] items = new String[3];
308: items[2] = locale.getVariant();
309: items[1] = locale.getCountry();
310: items[0] = locale.getLanguage();
311:
312: charset = searchCharSet(items);
313: if (charset.length() == 0) {
314: charset = DEFAULT_CHARSET;
315: }
316:
317: mappers[MAP_CACHE].put(key, charset);
318: }
319:
320: return charset;
321: }
322:
323: /**
324: * Gets the charset for a locale with a variant. The search is performed in
325: * the following order: "lang"_"country"_"variant"="charset",
326: * _"counry"_"variant"="charset", "lang"__"variant"="charset",
327: * __"variant"="charset", "lang"_"country"="charset", _"country"="charset",
328: * "lang"="charset". If nothing of the above is found, the default charset
329: * is returned.
330: *
331: * @param locale
332: * the locale.
333: * @param variant
334: * a variant field.
335: * @return the charset.
336: */
337: public final String getCharSet(final Locale locale,
338: final String variant) {
339: // Check the cache first.
340: if ((variant != null) && (variant.length() > 0)) {
341: String key = locale.toString();
342: if (key.length() == 0) {
343: key = "__" + locale.getVariant();
344: if (key.length() > 2) {
345: key += '_' + variant;
346: } else {
347: key += variant;
348: }
349: } else if (locale.getCountry().length() == 0) {
350: key += "__" + variant;
351: } else {
352: key += '_' + variant;
353: }
354:
355: String charset = searchCharSet(key);
356: if (charset.length() == 0) {
357: // Not found, perform a full search and update the cache.
358: String[] items = new String[4];
359: items[3] = variant;
360: items[2] = locale.getVariant();
361: items[1] = locale.getCountry();
362: items[0] = locale.getLanguage();
363:
364: charset = searchCharSet(items);
365: if (charset.length() == 0) {
366: charset = DEFAULT_CHARSET;
367: }
368:
369: mappers[MAP_CACHE].put(key, charset);
370: }
371:
372: return charset;
373: }
374:
375: return getCharSet(locale);
376: }
377:
378: /**
379: * Gets the charset for a specified key.
380: *
381: * @param key
382: * the key for the charset.
383: * @return the found charset or the default one.
384: */
385: public final String getCharSet(final String key) {
386: final String charset = searchCharSet(key);
387: return charset.length() > 0 ? charset : DEFAULT_CHARSET;
388: }
389:
390: /**
391: * Gets the charset for a specified key.
392: *
393: * @param key
394: * the key for the charset.
395: * @param def
396: * the default charset if none is found.
397: * @return the found charset or the given default.
398: */
399: public final String getCharSet(final String key, final String def) {
400: String charset = searchCharSet(key);
401: return charset.length() > 0 ? charset : def;
402: }
403:
404: /**
405: * Searches for a charset for a specified locale.
406: *
407: * @param items
408: * an array of locale items.
409: * @return the found charset or an empty string.
410: */
411: private final String searchCharSet(final String[] items) {
412: String charset;
413: final StringBuffer sb = new StringBuffer();
414: for (int i = items.length; i > 0; i--) {
415: charset = searchCharSet(items, sb, i);
416: if (charset.length() > 0) {
417: return charset;
418: }
419:
420: sb.setLength(0);
421: }
422:
423: return "";
424: }
425:
426: /**
427: * Searches recursively for a charset for a specified locale.
428: *
429: * @param items
430: * an array of locale items.
431: * @param base
432: * a buffer of base items.
433: * @param count
434: * the number of items to go through.
435: * @return the found charset or an empty string.
436: */
437: private final String searchCharSet(final String[] items,
438: final StringBuffer base, int count) {
439: if ((--count >= 0) && (items[count] != null)
440: && (items[count].length() > 0)) {
441: String charset;
442: base.insert(0, items[count]);
443: int length = base.length();
444:
445: for (int i = count; i > 0; i--) {
446: if ((i == count) || (i <= 1)) {
447: base.insert(0, '_');
448: length++;
449: }
450:
451: charset = searchCharSet(items, base, i);
452: if (charset.length() > 0) {
453: return charset;
454: }
455:
456: base.delete(0, base.length() - length);
457: }
458:
459: return searchCharSet(base.toString());
460: }
461:
462: return "";
463: }
464:
465: /**
466: * Searches for a charset for a specified key.
467: *
468: * @param key
469: * the key for the charset.
470: * @return the found charset or an empty string.
471: */
472: private final String searchCharSet(final String key) {
473: if ((key != null) && (key.length() > 0)) {
474: // Go through mappers.
475: Map mapper;
476: String charset;
477:
478: for (int i = 0; i < mappers.length; i++) {
479: mapper = mappers[i];
480: if (mapper != null) {
481: charset = (String) mapper.get(key);
482: if (charset != null) {
483: // Update the cache.
484: if (i > MAP_CACHE) {
485: mappers[MAP_CACHE].put(key, charset);
486: }
487:
488: return charset;
489: }
490: }
491: }
492:
493: // Not found, add an empty string to the cache.
494: mappers[MAP_CACHE].put(key, "");
495: }
496:
497: return "";
498: }
499:
500: /**
501: * Sets a common locale-charset mapping.
502: *
503: * @param key
504: * the key for the charset.
505: * @param charset
506: * the corresponding charset.
507: */
508: protected final synchronized void setCommonCharSet(
509: final String key, final String charset) {
510: final HashMap mapper = (HashMap) ((HashMap) mappers[MAP_COM])
511: .clone();
512: mapper.put(key, charset);
513: mappers[MAP_COM] = mapper;
514: mappers[MAP_CACHE].clear();
515: }
516: }
|