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