001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.util;
019:
020: import java.io.File;
021: import java.io.IOException;
022: import java.io.ObjectInputStream;
023: import java.io.ObjectOutputStream;
024: import java.io.ObjectStreamField;
025: import java.io.Serializable;
026: import java.security.AccessController;
027: import java.util.zip.ZipEntry;
028: import java.util.zip.ZipFile;
029:
030: import org.apache.harmony.luni.util.PriviAction;
031:
032: import com.ibm.icu.util.ULocale;
033:
034: /**
035: * Locale represents a language/country/variant combination. It is an identifier
036: * which dictates particular conventions for the presentation of information.
037: * The language codes are two letter lowercase codes as defined by ISO-639. The
038: * country codes are three letter uppercase codes as defined by ISO-3166. The
039: * variant codes are unspecified.
040: *
041: * @see ResourceBundle
042: */
043: public final class Locale implements Cloneable, Serializable {
044:
045: private static final long serialVersionUID = 9149081749638150636L;
046:
047: private static volatile Locale[] availableLocales;
048:
049: // Initialize a default which is used during static
050: // initialization of the default for the platform.
051: private static Locale defaultLocale = new Locale();
052:
053: /**
054: * Locale constant for en_CA.
055: */
056: public static final Locale CANADA = new Locale("en", "CA"); //$NON-NLS-1$ //$NON-NLS-2$
057:
058: /**
059: * Locale constant for fr_CA.
060: */
061: public static final Locale CANADA_FRENCH = new Locale("fr", "CA"); //$NON-NLS-1$ //$NON-NLS-2$
062:
063: /**
064: * Locale constant for zh_CN.
065: */
066: public static final Locale CHINA = new Locale("zh", "CN"); //$NON-NLS-1$ //$NON-NLS-2$
067:
068: /**
069: * Locale constant for zh.
070: */
071: public static final Locale CHINESE = new Locale("zh", ""); //$NON-NLS-1$//$NON-NLS-2$
072:
073: /**
074: * Locale constant for en.
075: */
076: public static final Locale ENGLISH = new Locale("en", ""); //$NON-NLS-1$ //$NON-NLS-2$
077:
078: /**
079: * Locale constant for fr_FR.
080: */
081: public static final Locale FRANCE = new Locale("fr", "FR"); //$NON-NLS-1$//$NON-NLS-2$
082:
083: /**
084: * Locale constant for fr.
085: */
086: public static final Locale FRENCH = new Locale("fr", ""); //$NON-NLS-1$//$NON-NLS-2$
087:
088: /**
089: * Locale constant for de.
090: */
091: public static final Locale GERMAN = new Locale("de", ""); //$NON-NLS-1$ //$NON-NLS-2$
092:
093: /**
094: * Locale constant for de_DE.
095: */
096: public static final Locale GERMANY = new Locale("de", "DE"); //$NON-NLS-1$ //$NON-NLS-2$
097:
098: /**
099: * Locale constant for it.
100: */
101: public static final Locale ITALIAN = new Locale("it", ""); //$NON-NLS-1$ //$NON-NLS-2$
102:
103: /**
104: * Locale constant for it_IT.
105: */
106: public static final Locale ITALY = new Locale("it", "IT"); //$NON-NLS-1$ //$NON-NLS-2$
107:
108: /**
109: * Locale constant for ja_JP.
110: */
111: public static final Locale JAPAN = new Locale("ja", "JP"); //$NON-NLS-1$//$NON-NLS-2$
112:
113: /**
114: * Locale constant for ja.
115: */
116: public static final Locale JAPANESE = new Locale("ja", ""); //$NON-NLS-1$//$NON-NLS-2$
117:
118: /**
119: * Locale constant for ko_KR.
120: */
121: public static final Locale KOREA = new Locale("ko", "KR"); //$NON-NLS-1$//$NON-NLS-2$
122:
123: /**
124: * Locale constant for ko.
125: */
126: public static final Locale KOREAN = new Locale("ko", ""); //$NON-NLS-1$//$NON-NLS-2$
127:
128: /**
129: * Locale constant for zh_CN.
130: */
131: public static final Locale PRC = new Locale("zh", "CN"); //$NON-NLS-1$//$NON-NLS-2$
132:
133: /**
134: * Locale constant for zh_CN.
135: */
136: public static final Locale SIMPLIFIED_CHINESE = new Locale(
137: "zh", "CN"); //$NON-NLS-1$//$NON-NLS-2$
138:
139: /**
140: * Locale constant for zh_TW.
141: */
142: public static final Locale TAIWAN = new Locale("zh", "TW"); //$NON-NLS-1$ //$NON-NLS-2$
143:
144: /**
145: * Locale constant for zh_TW.
146: */
147: public static final Locale TRADITIONAL_CHINESE = new Locale(
148: "zh", "TW"); //$NON-NLS-1$ //$NON-NLS-2$
149:
150: /**
151: * Locale constant for en_GB.
152: */
153: public static final Locale UK = new Locale("en", "GB"); //$NON-NLS-1$ //$NON-NLS-2$
154:
155: /**
156: * Locale constant for en_US.
157: */
158: public static final Locale US = new Locale("en", "US"); //$NON-NLS-1$//$NON-NLS-2$
159:
160: private static final PropertyPermission setLocalePermission = new PropertyPermission(
161: "user.language", "write"); //$NON-NLS-1$//$NON-NLS-2$
162:
163: static {
164: String language = AccessController
165: .doPrivileged(new PriviAction<String>(
166: "user.language", "en")); //$NON-NLS-1$ //$NON-NLS-2$
167: String region = AccessController
168: .doPrivileged(new PriviAction<String>(
169: "user.country", "US")); //$NON-NLS-1$ //$NON-NLS-2$
170: String variant = AccessController
171: .doPrivileged(new PriviAction<String>(
172: "user.variant", "")); //$NON-NLS-1$ //$NON-NLS-2$
173: defaultLocale = new Locale(language, region, variant);
174: }
175:
176: private transient String countryCode;
177: private transient String languageCode;
178: private transient String variantCode;
179:
180: private transient ULocale uLocale;
181:
182: /**
183: * Constructs a default which is used during static initialization of the
184: * default for the platform.
185: */
186: private Locale() {
187: languageCode = "en"; //$NON-NLS-1$
188: countryCode = "US"; //$NON-NLS-1$
189: variantCode = ""; //$NON-NLS-1$
190: }
191:
192: /**
193: * Constructs a new Locale using the specified language.
194: *
195: * @param language
196: *
197: */
198: public Locale(String language) {
199: this (language, "", ""); //$NON-NLS-1$//$NON-NLS-2$
200: }
201:
202: /**
203: * Constructs a new Locale using the specified language and country codes.
204: *
205: * @param language
206: * @param country
207: *
208: */
209: public Locale(String language, String country) {
210: this (language, country, ""); //$NON-NLS-1$
211: }
212:
213: /**
214: * Constructs a new Locale using the specified language, country, and
215: * variant codes.
216: *
217: * @param language
218: * @param country
219: * @param variant
220: * @throws NullPointerException
221: * if <code>language</code>, <code>country</code> or
222: * <code>variant</code> is <code>null</code>.
223: */
224: public Locale(String language, String country, String variant) {
225: if (language == null || country == null || variant == null) {
226: throw new NullPointerException();
227: }
228: if (language.length() == 0 && country.length() == 0) {
229: languageCode = "";
230: countryCode = "";
231: variantCode = variant;
232: return;
233: }
234: this .uLocale = new ULocale(language, country, variant);
235: languageCode = uLocale.getLanguage();
236: // Map new language codes to the obsolete language
237: // codes so the correct resource bundles will be used.
238: if (languageCode.equals("he")) {//$NON-NLS-1$
239: languageCode = "iw"; //$NON-NLS-1$
240: } else if (languageCode.equals("id")) {//$NON-NLS-1$
241: languageCode = "in"; //$NON-NLS-1$
242: } else if (languageCode.equals("yi")) {//$NON-NLS-1$
243: languageCode = "ji"; //$NON-NLS-1$
244: }
245:
246: // countryCode is defined in ASCII character set
247: countryCode = country.length() != 0 ? uLocale.getCountry() : "";
248:
249: // Work around for be compatible with RI
250: variantCode = variant;
251: }
252:
253: /**
254: * Answers a new Locale with the same language, country and variant codes as
255: * this Locale.
256: *
257: * @return a shallow copy of this Locale
258: *
259: * @see java.lang.Cloneable
260: */
261: @Override
262: public Object clone() {
263: try {
264: return super .clone();
265: } catch (CloneNotSupportedException e) {
266: return null;
267: }
268: }
269:
270: /**
271: * Compares the specified object to this Locale and answer if they are
272: * equal. The object must be an instance of Locale and have the same
273: * language, country and variant.
274: *
275: * @param object
276: * the object to compare with this object
277: * @return true if the specified object is equal to this Locale, false
278: * otherwise
279: *
280: * @see #hashCode
281: */
282: @Override
283: public boolean equals(Object object) {
284: if (object == this ) {
285: return true;
286: }
287: if (object instanceof Locale) {
288: Locale o = (Locale) object;
289: return languageCode.equals(o.languageCode)
290: && countryCode.equals(o.countryCode)
291: && variantCode.equals(o.variantCode);
292: }
293: return false;
294: }
295:
296: static Locale[] find(String prefix) {
297: int last = prefix.lastIndexOf('/');
298: final String thePackage = prefix.substring(0, last + 1);
299: int length = prefix.length();
300: final String classPrefix = prefix.substring(last + 1, length);
301: Set<String> result = new HashSet<String>();
302: StringTokenizer paths = new StringTokenizer(
303: System.getProperty(
304: "org.apache.harmony.boot.class.path", ""), System.getProperty( //$NON-NLS-1$ //$NON-NLS-2$
305: "path.separator", ";")); //$NON-NLS-1$//$NON-NLS-2$
306: while (paths.hasMoreTokens()) {
307: String nextToken = paths.nextToken();
308: File directory = new File(nextToken);
309: if (directory.exists()) {
310: if (directory.isDirectory()) {
311: String path;
312: try {
313: path = directory.getCanonicalPath();
314: } catch (IOException e) {
315: continue;
316: }
317: File newDir;
318: if (path.charAt(path.length() - 1) == File.separatorChar) {
319: newDir = new File(path + thePackage);
320: } else {
321: newDir = new File(path + File.separatorChar
322: + thePackage);
323: }
324: if (newDir.isDirectory()) {
325: String[] list = newDir.list();
326: for (int i = 0; i < list.length; i++) {
327: String name = list[i];
328: if (name.startsWith(classPrefix)
329: && name.endsWith(".class")) { //$NON-NLS-1$
330: result.add(name.substring(0, name
331: .length() - 6));
332: }
333: }
334: }
335:
336: } else {
337: // Handle ZIP/JAR files.
338: try {
339: ZipFile zip = new ZipFile(directory);
340: Enumeration<? extends ZipEntry> entries = zip
341: .entries();
342: while (entries.hasMoreElements()) {
343: ZipEntry e = entries.nextElement();
344: String name = e.getName();
345: if (name.startsWith(prefix)
346: && name.endsWith(".class")) {//$NON-NLS-1$
347: result.add(name.substring(last + 1,
348: name.length() - 6));
349: }
350: }
351: zip.close();
352: } catch (IOException e) {
353: // Empty
354: }
355: }
356: }
357: }
358: Locale[] locales = new Locale[result.size()];
359: int i = 0;
360: for (String name : result) {
361: int index = name.indexOf('_');
362: int nextIndex = name.indexOf('_', index + 1);
363: if (nextIndex == -1) {
364: locales[i++] = new Locale(name.substring(index + 1,
365: name.length()), ""); //$NON-NLS-1$
366: } else {
367: String language = name.substring(index + 1, nextIndex);
368: String variant;
369: if ((index = name.indexOf('_', nextIndex + 1)) == -1) {
370: variant = ""; //$NON-NLS-1$
371: index = name.length();
372: } else {
373: variant = name.substring(index + 1, name.length());
374: }
375: String country = name.substring(nextIndex + 1, index);
376: locales[i++] = new Locale(language, country, variant);
377: }
378: }
379: return locales;
380: }
381:
382: /**
383: * Gets the list of installed Locales.
384: *
385: * @return an array of Locale
386: */
387: public static Locale[] getAvailableLocales() {
388: ULocale[] ulocales = ULocale.getAvailableLocales();
389: Locale[] locales = new Locale[ulocales.length];
390: for (int i = 0; i < locales.length; i++) {
391: locales[i] = ulocales[i].toLocale();
392: }
393: return locales;
394: }
395:
396: /**
397: * Gets the country code for this Locale.
398: *
399: * @return a country code
400: */
401: public String getCountry() {
402: return countryCode;
403: }
404:
405: /**
406: * Gets the default Locale.
407: *
408: * @return the default Locale
409: */
410: public static Locale getDefault() {
411: return defaultLocale;
412: }
413:
414: /**
415: * Gets the full country name in the default Locale for the country code of
416: * this Locale. If there is no matching country name, the country code is
417: * returned.
418: *
419: * @return a country name
420: */
421: public final String getDisplayCountry() {
422: return getDisplayCountry(getDefault());
423: }
424:
425: /**
426: * Gets the full country name in the specified Locale for the country code
427: * of this Locale. If there is no matching country name, the country code is
428: * returned.
429: *
430: * @param locale
431: * the Locale
432: * @return a country name
433: */
434: public String getDisplayCountry(Locale locale) {
435: return ULocale.forLocale(this ).getDisplayCountry(
436: ULocale.forLocale(locale));
437: }
438:
439: /**
440: * Gets the full language name in the default Locale for the language code
441: * of this Locale. If there is no matching language name, the language code
442: * is returned.
443: *
444: * @return a language name
445: */
446: public final String getDisplayLanguage() {
447: return getDisplayLanguage(getDefault());
448: }
449:
450: /**
451: * Gets the full language name in the specified Locale for the language code
452: * of this Locale. If there is no matching language name, the language code
453: * is returned.
454: *
455: * @param locale
456: * the Locale
457: * @return a language name
458: */
459: public String getDisplayLanguage(Locale locale) {
460: return ULocale.forLocale(this ).getDisplayLanguage(
461: ULocale.forLocale(locale));
462: }
463:
464: /**
465: * Gets the full language, country, and variant names in the default Locale
466: * for the codes of this Locale.
467: *
468: * @return a Locale name
469: */
470: public final String getDisplayName() {
471: return getDisplayName(getDefault());
472: }
473:
474: /**
475: * Gets the full language, country, and variant names in the specified
476: * Locale for the codes of this Locale.
477: *
478: * @param locale
479: * the Locale
480: * @return a Locale name
481: */
482: public String getDisplayName(Locale locale) {
483: int count = 0;
484: StringBuffer buffer = new StringBuffer();
485: if (languageCode.length() > 0) {
486: buffer.append(getDisplayLanguage(locale));
487: count++;
488: }
489: if (countryCode.length() > 0) {
490: if (count == 1) {
491: buffer.append(" ("); //$NON-NLS-1$
492: }
493: buffer.append(getDisplayCountry(locale));
494: count++;
495: }
496: if (variantCode.length() > 0) {
497: if (count == 1) {
498: buffer.append(" ("); //$NON-NLS-1$
499: } else if (count == 2) {
500: buffer.append(","); //$NON-NLS-1$
501: }
502: buffer.append(getDisplayVariant(locale));
503: count++;
504: }
505: if (count > 1) {
506: buffer.append(")"); //$NON-NLS-1$
507: }
508: return buffer.toString();
509: }
510:
511: /**
512: * Gets the full variant name in the default Locale for the variant code of
513: * this Locale. If there is no matching variant name, the variant code is
514: * returned.
515: *
516: * @return a variant name
517: */
518: public final String getDisplayVariant() {
519: return getDisplayVariant(getDefault());
520: }
521:
522: /**
523: * Gets the full variant name in the specified Locale for the variant code
524: * of this Locale. If there is no matching variant name, the variant code is
525: * returned.
526: *
527: * @param locale
528: * the Locale
529: * @return a variant name
530: */
531: public String getDisplayVariant(Locale locale) {
532: return ULocale.forLocale(this ).getDisplayVariant(
533: ULocale.forLocale(locale));
534: }
535:
536: /**
537: * Gets the three letter ISO country code which corresponds to the country
538: * code for this Locale.
539: *
540: * @return a three letter ISO language code
541: *
542: * @exception MissingResourceException
543: * when there is no matching three letter ISO country code
544: */
545: public String getISO3Country() throws MissingResourceException {
546: return ULocale.forLocale(this ).getISO3Country();
547: }
548:
549: /**
550: * Gets the three letter ISO language code which corresponds to the language
551: * code for this Locale.
552: *
553: * @return a three letter ISO language code
554: *
555: * @exception MissingResourceException
556: * when there is no matching three letter ISO language code
557: */
558: public String getISO3Language() throws MissingResourceException {
559: return ULocale.forLocale(this ).getISO3Language();
560: }
561:
562: /**
563: * Gets the list of two letter ISO country codes which can be used as the
564: * country code for a Locale.
565: *
566: * @return an array of String
567: */
568: public static String[] getISOCountries() {
569: return ULocale.getISOCountries();
570: }
571:
572: /**
573: * Gets the list of two letter ISO language codes which can be used as the
574: * language code for a Locale.
575: *
576: * @return an array of String
577: */
578: public static String[] getISOLanguages() {
579: return ULocale.getISOLanguages();
580: }
581:
582: /**
583: * Gets the language code for this Locale.
584: *
585: * @return a language code
586: */
587: public String getLanguage() {
588: return languageCode;
589: }
590:
591: /**
592: * Gets the variant code for this Locale.
593: *
594: * @return a variant code
595: */
596: public String getVariant() {
597: return variantCode;
598: }
599:
600: /**
601: * Answers an integer hash code for the receiver. Objects which are equal
602: * answer the same value for this method.
603: *
604: * @return the receiver's hash
605: *
606: * @see #equals
607: */
608: @Override
609: public synchronized int hashCode() {
610: return countryCode.hashCode() + languageCode.hashCode()
611: + variantCode.hashCode();
612: }
613:
614: /**
615: * Sets the default Locale to the specified Locale.
616: *
617: * @param locale
618: * the new default Locale
619: *
620: * @exception SecurityException
621: * when there is a security manager which does not allow this
622: * operation
623: */
624: public synchronized static void setDefault(Locale locale) {
625: if (locale != null) {
626: SecurityManager security = System.getSecurityManager();
627: if (security != null) {
628: security.checkPermission(setLocalePermission);
629: }
630: defaultLocale = locale;
631: } else {
632: throw new NullPointerException();
633: }
634: }
635:
636: /**
637: * Answers the string representation of this Locale.
638: *
639: * @return the string representation of this Locale
640: */
641: @Override
642: public final String toString() {
643: StringBuilder result = new StringBuilder();
644: result.append(languageCode);
645: if (countryCode.length() > 0) {
646: result.append('_');
647: result.append(countryCode);
648: }
649: if (variantCode.length() > 0 && result.length() > 0) {
650: if (0 == countryCode.length()) {
651: result.append("__"); //$NON-NLS-1$
652: } else {
653: result.append('_');
654: }
655: result.append(variantCode);
656: }
657: return result.toString();
658: }
659:
660: private static final ObjectStreamField[] serialPersistentFields = {
661: new ObjectStreamField("country", String.class), //$NON-NLS-1$
662: new ObjectStreamField("hashcode", Integer.TYPE), //$NON-NLS-1$
663: new ObjectStreamField("language", String.class), //$NON-NLS-1$
664: new ObjectStreamField("variant", String.class) }; //$NON-NLS-1$
665:
666: private void writeObject(ObjectOutputStream stream)
667: throws IOException {
668: ObjectOutputStream.PutField fields = stream.putFields();
669: fields.put("country", countryCode); //$NON-NLS-1$
670: fields.put("hashcode", -1); //$NON-NLS-1$
671: fields.put("language", languageCode); //$NON-NLS-1$
672: fields.put("variant", variantCode); //$NON-NLS-1$
673: stream.writeFields();
674: }
675:
676: private void readObject(ObjectInputStream stream)
677: throws IOException, ClassNotFoundException {
678: ObjectInputStream.GetField fields = stream.readFields();
679: countryCode = (String) fields.get("country", ""); //$NON-NLS-1$//$NON-NLS-2$
680: languageCode = (String) fields.get("language", ""); //$NON-NLS-1$//$NON-NLS-2$
681: variantCode = (String) fields.get("variant", ""); //$NON-NLS-1$//$NON-NLS-2$
682: }
683: }
|