1 /*
2  * Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.tools.javac.util;
27 
28 import com.sun.tools.javac.api.Messages;
29 
30 import java.lang.ref.SoftReference;
31 import java.util.ResourceBundle;
32 import java.util.MissingResourceException;
33 import java.text.MessageFormat;
34 import java.util.HashMap;
35 import java.util.Locale;
36 import java.util.Map;
37 
38 import com.sun.tools.javac.api.DiagnosticFormatter;
39 import com.sun.tools.javac.util.JCDiagnostic.Factory;
40 import com.sun.tools.javac.resources.CompilerProperties.Errors;
41 
42 /**
43  *  Support for formatted localized messages.
44  *
45  *  <p><b>This is NOT part of any supported API.
46  *  If you write code that depends on this, you do so at your own risk.
47  *  This code and its internal interfaces are subject to change or
48  *  deletion without notice.</b>
49  */
50 public class JavacMessages implements Messages {
51     /** The context key for the JavacMessages object. */
52     public static final Context.Key<JavacMessages> messagesKey = new Context.Key<>();
53 
54     /** Get the JavacMessages instance for this context. */
instance(Context context)55     public static JavacMessages instance(Context context) {
56         JavacMessages instance = context.get(messagesKey);
57         if (instance == null)
58             instance = new JavacMessages(context);
59         return instance;
60     }
61 
62     private Map<Locale, SoftReference<List<ResourceBundle>>> bundleCache;
63 
64     private List<ResourceBundleHelper> bundleHelpers;
65 
66     private Locale currentLocale;
67     private List<ResourceBundle> currentBundles;
68 
69     private DiagnosticFormatter<JCDiagnostic> diagFormatter;
70     private JCDiagnostic.Factory diagFactory;
71 
getCurrentLocale()72     public Locale getCurrentLocale() {
73         return currentLocale;
74     }
75 
setCurrentLocale(Locale locale)76     public void setCurrentLocale(Locale locale) {
77         if (locale == null) {
78             locale = Locale.getDefault();
79         }
80         this.currentBundles = getBundles(locale);
81         this.currentLocale = locale;
82     }
83 
84     Context context;
85 
86     /** Creates a JavacMessages object.
87      */
JavacMessages(Context context)88     public JavacMessages(Context context) {
89         this(defaultBundleName, context.get(Locale.class));
90         this.context = context;
91         context.put(messagesKey, this);
92         Options options = Options.instance(context);
93         boolean rawDiagnostics = options.isSet("rawDiagnostics");
94         this.diagFormatter = rawDiagnostics ? new RawDiagnosticFormatter(options) :
95                                                   new BasicDiagnosticFormatter(options, this);
96     }
97 
98     /** Creates a JavacMessages object.
99      * @param bundleName the name to identify the resource bundle of localized messages.
100      */
JavacMessages(String bundleName)101     public JavacMessages(String bundleName) throws MissingResourceException {
102         this(bundleName, null);
103     }
104 
105     /** Creates a JavacMessages object.
106      * @param bundleName the name to identify the resource bundle of localized messages.
107      */
JavacMessages(String bundleName, Locale locale)108     public JavacMessages(String bundleName, Locale locale) throws MissingResourceException {
109         bundleHelpers = List.nil();
110         bundleCache = new HashMap<>();
111         add(bundleName);
112         setCurrentLocale(locale);
113     }
114 
JavacMessages()115     public JavacMessages() throws MissingResourceException {
116         this(defaultBundleName);
117     }
118 
119     @Override
add(String bundleName)120     public void add(String bundleName) throws MissingResourceException {
121         add(locale -> ResourceBundle.getBundle(bundleName, locale));
122     }
123 
add(ResourceBundleHelper ma)124     public void add(ResourceBundleHelper ma) {
125         bundleHelpers = bundleHelpers.prepend(ma);
126         if (!bundleCache.isEmpty())
127             bundleCache.clear();
128         currentBundles = null;
129     }
130 
getBundles(Locale locale)131     public List<ResourceBundle> getBundles(Locale locale) {
132         if (locale == currentLocale && currentBundles != null)
133             return currentBundles;
134         SoftReference<List<ResourceBundle>> bundles = bundleCache.get(locale);
135         List<ResourceBundle> bundleList = bundles == null ? null : bundles.get();
136         if (bundleList == null) {
137             bundleList = List.nil();
138             for (ResourceBundleHelper helper : bundleHelpers) {
139                 try {
140                     ResourceBundle rb = helper.getResourceBundle(locale);
141                     bundleList = bundleList.prepend(rb);
142                 } catch (MissingResourceException e) {
143                     throw new InternalError("Cannot find requested resource bundle for locale " +
144                             locale, e);
145                 }
146             }
147             bundleCache.put(locale, new SoftReference<>(bundleList));
148         }
149         return bundleList;
150     }
151 
152     /** Gets the localized string corresponding to a key, formatted with a set of args.
153      */
getLocalizedString(String key, Object... args)154     public String getLocalizedString(String key, Object... args) {
155         return getLocalizedString(currentLocale, key, args);
156     }
157 
getLocalizedString(JCDiagnostic.DiagnosticInfo diagInfo)158     public String getLocalizedString(JCDiagnostic.DiagnosticInfo diagInfo) {
159         return getLocalizedString(currentLocale, diagInfo);
160     }
161 
162     @Override
getLocalizedString(Locale l, String key, Object... args)163     public String getLocalizedString(Locale l, String key, Object... args) {
164         if (l == null)
165             l = getCurrentLocale();
166         return getLocalizedString(getBundles(l), key, args);
167     }
168 
getLocalizedString(Locale l, JCDiagnostic.DiagnosticInfo diagInfo)169     public String getLocalizedString(Locale l, JCDiagnostic.DiagnosticInfo diagInfo) {
170         if (l == null)
171             l = getCurrentLocale();
172         return getLocalizedString(getBundles(l), diagInfo);
173     }
174 
175     /* Static access:
176      * javac has a firmly entrenched notion of a default message bundle
177      * which it can access from any static context. This is used to get
178      * easy access to simple localized strings.
179      */
180 
181     private static final String defaultBundleName = "com.sun.tools.javac.resources.compiler";
182     private static ResourceBundle defaultBundle;
183     private static JavacMessages defaultMessages;
184 
185 
186     /**
187      * Returns a localized string from the compiler's default bundle.
188      */
189     // used to support legacy Log.getLocalizedString
getDefaultLocalizedString(String key, Object... args)190     static String getDefaultLocalizedString(String key, Object... args) {
191         return getLocalizedString(List.of(getDefaultBundle()), key, args);
192     }
193 
194     // used to support legacy static Diagnostic.fragment
195     @Deprecated
getDefaultMessages()196     static JavacMessages getDefaultMessages() {
197         if (defaultMessages == null)
198             defaultMessages = new JavacMessages(defaultBundleName);
199         return defaultMessages;
200     }
201 
getDefaultBundle()202     public static ResourceBundle getDefaultBundle() {
203         try {
204             if (defaultBundle == null)
205                 defaultBundle = ResourceBundle.getBundle(defaultBundleName);
206             return defaultBundle;
207         }
208         catch (MissingResourceException e) {
209             throw new Error("Fatal: Resource for compiler is missing", e);
210         }
211     }
212 
getLocalizedString(List<ResourceBundle> bundles, String key, Object... args)213     static private String getLocalizedString(List<ResourceBundle> bundles,
214                                              String key,
215                                              Object... args) {
216        String msg = null;
217        for (List<ResourceBundle> l = bundles; l.nonEmpty() && msg == null; l = l.tail) {
218            ResourceBundle rb = l.head;
219            try {
220                msg = rb.getString(key);
221            }
222            catch (MissingResourceException e) {
223                // ignore, try other bundles in list
224            }
225        }
226        if (msg == null) {
227            msg = "compiler message file broken: key=" + key +
228                " arguments={0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}";
229        }
230        return MessageFormat.format(msg, args);
231     }
232 
getLocalizedString(List<ResourceBundle> bundles, JCDiagnostic.DiagnosticInfo diagInfo)233     private String getLocalizedString(List<ResourceBundle> bundles, JCDiagnostic.DiagnosticInfo diagInfo) {
234         String msg = null;
235         for (List<ResourceBundle> l = bundles; l.nonEmpty() && msg == null; l = l.tail) {
236             ResourceBundle rb = l.head;
237             try {
238                 msg = rb.getString(diagInfo.key());
239             }
240             catch (MissingResourceException e) {
241                 // ignore, try other bundles in list
242             }
243         }
244         if (msg == null) {
245             msg = "compiler message file broken: key=" + diagInfo.key() +
246                 " arguments={0}, {1}, {2}, {3}, {4}, {5}, {6}, {7}";
247         }
248         if (diagInfo == Errors.Error) {
249             return MessageFormat.format(msg, new Object[0]);
250         } else {
251             return diagFormatter.format(getDiagFactory().create(DiagnosticSource.NO_SOURCE, null, diagInfo),
252                     getCurrentLocale());
253         }
254     }
255 
getDiagFactory()256     JCDiagnostic.Factory getDiagFactory() {
257         if (diagFactory == null) {
258             this.diagFactory = JCDiagnostic.Factory.instance(context);
259         }
260         return diagFactory;
261     }
262 
263     /**
264      * This provides a way for the JavacMessager to retrieve a
265      * ResourceBundle from another module such as jdk.javadoc.
266      */
267     public interface ResourceBundleHelper {
268         /**
269          * Gets the ResourceBundle.
270          * @param locale the requested bundle's locale
271          * @return ResourceBundle
272          */
getResourceBundle(Locale locale)273         ResourceBundle getResourceBundle(Locale locale);
274     }
275 }
276