1 /*
2 * Copyright (C) 2010, Sasa Zivkov <sasa.zivkov@sap.com> and others
3 *
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Distribution License v. 1.0 which is available at
6 * https://www.eclipse.org/org/documents/edl-v10.php.
7 *
8 * SPDX-License-Identifier: BSD-3-Clause
9 */
10
11 package org.eclipse.jgit.nls;
12
13 import java.util.Locale;
14 import java.util.concurrent.ConcurrentHashMap;
15
16 import org.eclipse.jgit.errors.TranslationBundleLoadingException;
17 import org.eclipse.jgit.errors.TranslationStringMissingException;
18
19 /**
20 * The purpose of this class is to provide NLS (National Language Support)
21 * configurable per thread.
22 *
23 * <p>
24 * The {@link #setLocale(Locale)} method is used to configure locale for the
25 * calling thread. The locale setting is thread inheritable. This means that a
26 * child thread will have the same locale setting as its creator thread until it
27 * changes it explicitly.
28 *
29 * <p>
30 * Example of usage:
31 *
32 * <pre>
33 * NLS.setLocale(Locale.GERMAN);
34 * TransportText t = NLS.getBundleFor(TransportText.class);
35 * </pre>
36 */
37 public class NLS {
38 /**
39 * The root locale constant. It is defined here because the Locale.ROOT is
40 * not defined in Java 5
41 */
42 public static final Locale ROOT_LOCALE = new Locale("", "", ""); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
43
44 private static final InheritableThreadLocal<NLS> local = new InheritableThreadLocal<>();
45
46 /**
47 * Sets the locale for the calling thread.
48 * <p>
49 * The {@link #getBundleFor(Class)} method will honor this setting if it
50 * is supported by the provided resource bundle property files. Otherwise,
51 * it will use a fall back locale as described in the
52 * {@link TranslationBundle}
53 *
54 * @param locale
55 * the preferred locale
56 */
57 public static void setLocale(Locale locale) {
58 local.set(new NLS(locale));
59 }
60
61 /**
62 * Sets the JVM default locale as the locale for the calling thread.
63 * <p>
64 * Semantically this is equivalent to
65 * <code>NLS.setLocale(Locale.getDefault())</code>.
66 */
67 public static void useJVMDefaultLocale() {
68 useJVMDefaultInternal();
69 }
70
71 // TODO(ms): change signature of public useJVMDefaultLocale() in 5.0 to get
72 // rid of this internal method
73 private static NLS useJVMDefaultInternal() {
74 NLS b = new NLS(Locale.getDefault());
75 local.set(b);
76 return b;
77 }
78
79 /**
80 * Returns an instance of the translation bundle of the required type. All
81 * public String fields of the bundle instance will get their values
82 * injected as described in the
83 * {@link org.eclipse.jgit.nls.TranslationBundle}.
84 *
85 * @param type
86 * required bundle type
87 * @return an instance of the required bundle type
88 * @exception TranslationBundleLoadingException
89 * see
90 * {@link org.eclipse.jgit.errors.TranslationBundleLoadingException}
91 * @exception TranslationStringMissingException
92 * see
93 * {@link org.eclipse.jgit.errors.TranslationStringMissingException}
94 */
95 public static <T extends TranslationBundle> T getBundleFor(Class<T> type) {
96 NLS b = local.get();
97 if (b == null) {
98 b = useJVMDefaultInternal();
99 }
100 return b.get(type);
101 }
102
103 private final Locale locale;
104 private final ConcurrentHashMap<Class, TranslationBundle> map = new ConcurrentHashMap<>();
105
106 private NLS(Locale locale) {
107 this.locale = locale;
108 }
109
110 @SuppressWarnings("unchecked")
111 private <T extends TranslationBundle> T get(Class<T> type) {
112 TranslationBundle bundle = map.get(type);
113 if (bundle == null) {
114 bundle = GlobalBundleCache.lookupBundle(locale, type);
115 // There is a small opportunity for a race, which we may
116 // lose. Accept defeat and return the winner's instance.
117 TranslationBundle old = map.putIfAbsent(type, bundle);
118 if (old != null)
119 bundle = old;
120 }
121 return (T) bundle;
122 }
123 }