| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
| LocalizedMessages |
|
| 1.7272727272727273;1,727 |
| 1 | /* | |
| 2 | * FCKeditor - The text editor for Internet - http://www.fckeditor.net | |
| 3 | * Copyright (C) 2004-2010 Frederico Caldeira Knabben | |
| 4 | * | |
| 5 | * == BEGIN LICENSE == | |
| 6 | * | |
| 7 | * Licensed under the terms of any of the following licenses at your | |
| 8 | * choice: | |
| 9 | * | |
| 10 | * - GNU General Public License Version 2 or later (the "GPL") | |
| 11 | * http://www.gnu.org/licenses/gpl.html | |
| 12 | * | |
| 13 | * - GNU Lesser General Public License Version 2.1 or later (the "LGPL") | |
| 14 | * http://www.gnu.org/licenses/lgpl.html | |
| 15 | * | |
| 16 | * - Mozilla Public License Version 1.1 or later (the "MPL") | |
| 17 | * http://www.mozilla.org/MPL/MPL-1.1.html | |
| 18 | * | |
| 19 | * == END LICENSE == | |
| 20 | */ | |
| 21 | package net.fckeditor.localization; | |
| 22 | ||
| 23 | import java.io.BufferedInputStream; | |
| 24 | import java.io.InputStream; | |
| 25 | import java.text.MessageFormat; | |
| 26 | import java.util.Collections; | |
| 27 | import java.util.Enumeration; | |
| 28 | import java.util.HashMap; | |
| 29 | import java.util.Locale; | |
| 30 | import java.util.Map; | |
| 31 | import java.util.Properties; | |
| 32 | import java.util.ResourceBundle; | |
| 33 | ||
| 34 | import javax.servlet.http.HttpServletRequest; | |
| 35 | ||
| 36 | import net.fckeditor.handlers.PropertiesLoader; | |
| 37 | import net.fckeditor.tool.Utils; | |
| 38 | ||
| 39 | import org.slf4j.Logger; | |
| 40 | import org.slf4j.LoggerFactory; | |
| 41 | ||
| 42 | /** | |
| 43 | * Provides access to localized messages (properties). | |
| 44 | * <p> | |
| 45 | * Localized messages are loaded for a particular locale from a HTTP request. | |
| 46 | * The locale is resolved by the current {@link LocaleResolver} | |
| 47 | * instance/singleton. If a locale or a bundle for a locale cannot be found, | |
| 48 | * default messages are used. | |
| 49 | * </p> | |
| 50 | * <p> | |
| 51 | * Note: Loaded messages are cached per locale, any subsequent call of the same | |
| 52 | * locale will be served by the cache instead of another resource bundle | |
| 53 | * retrieval. | |
| 54 | * </p> | |
| 55 | * | |
| 56 | * @version $Id: LocalizedMessages.java 4785 2009-12-21 20:10:28Z mosipov $ | |
| 57 | */ | |
| 58 | public class LocalizedMessages { | |
| 59 | ||
| 60 | 0 | private static final Map<Locale, LocalizedMessages> prototypes = Collections |
| 61 | .synchronizedMap(new HashMap<Locale, LocalizedMessages>()); | |
| 62 | private static final String DEFAULT_FILENAME = "default_messages.properties"; //$NON-NLS-1$ | |
| 63 | private static final String LOCAL_PROPERTIES = "fckeditor_messages"; //$NON-NLS-1$ | |
| 64 | 0 | private static final Properties defaultProperties = new Properties(); |
| 65 | ||
| 66 | private Properties properties; | |
| 67 | private static LocaleResolver localeResolver; | |
| 68 | 0 | private static final Locale NEUTRAL = new Locale(Utils.EMPTY_STRING); //$NON-NLS-1$ |
| 69 | 0 | private static final Logger logger = LoggerFactory |
| 70 | 0 | .getLogger(LocalizedMessages.class); |
| 71 | ||
| 72 | static { | |
| 73 | ||
| 74 | 0 | InputStream in = LocalizedMessages.class |
| 75 | .getResourceAsStream(DEFAULT_FILENAME); | |
| 76 | ||
| 77 | 0 | if (in == null) { |
| 78 | 0 | logger.error("{} not found", DEFAULT_FILENAME); //$NON-NLS-1$ |
| 79 | 0 | throw new RuntimeException(DEFAULT_FILENAME + " not found"); //$NON-NLS-1$ |
| 80 | } else { | |
| 81 | 0 | if (!(in instanceof BufferedInputStream)) |
| 82 | 0 | in = new BufferedInputStream(in); |
| 83 | ||
| 84 | try { | |
| 85 | 0 | defaultProperties.load(in); |
| 86 | 0 | in.close(); |
| 87 | 0 | logger.debug("{} loaded", DEFAULT_FILENAME); //$NON-NLS-1$ |
| 88 | 0 | } catch (Exception e) { |
| 89 | 0 | logger.error("Error while loading {}", DEFAULT_FILENAME); //$NON-NLS-1$ |
| 90 | 0 | throw new RuntimeException( |
| 91 | "Error while loading " + DEFAULT_FILENAME, e); //$NON-NLS-1$ | |
| 92 | 0 | } |
| 93 | } | |
| 94 | 0 | } |
| 95 | ||
| 96 | /** | |
| 97 | * Returns an instance of <code>LocalizedMessages</code> for a given | |
| 98 | * request. This method automatically determines the locale of this request | |
| 99 | * and loads the appropriate bundle. If locale is null or not available, | |
| 100 | * the default locale will be used. | |
| 101 | * | |
| 102 | * @param request | |
| 103 | * the current request instance | |
| 104 | * @return instance with localized messages | |
| 105 | */ | |
| 106 | public static LocalizedMessages getInstance(HttpServletRequest request) { | |
| 107 | ||
| 108 | 0 | if (request == null) |
| 109 | 0 | throw new NullPointerException("the request cannot be null"); |
| 110 | ||
| 111 | 0 | Locale locale = getLocaleResolverInstance().resolveLocale(request); |
| 112 | ||
| 113 | 0 | if (locale == null) |
| 114 | 0 | locale = NEUTRAL; |
| 115 | ||
| 116 | 0 | synchronized (LocalizedMessages.class) { |
| 117 | 0 | if (!prototypes.containsKey(locale)) |
| 118 | 0 | prototypes.put(locale, new LocalizedMessages(locale)); |
| 119 | 0 | } |
| 120 | ||
| 121 | // for now we don't need any cloning since the values are accessed | |
| 122 | // read-only | |
| 123 | 0 | return prototypes.get(locale); |
| 124 | ||
| 125 | } | |
| 126 | ||
| 127 | /** | |
| 128 | * Returns the locale resolver instance. The implementation class name is | |
| 129 | * provided by {@link PropertiesLoader#getLocaleResolverImpl()}. | |
| 130 | * | |
| 131 | * @return the locale resolver instance | |
| 132 | */ | |
| 133 | private synchronized static LocaleResolver getLocaleResolverInstance() { | |
| 134 | ||
| 135 | 0 | if (localeResolver == null) { |
| 136 | 0 | String className = PropertiesLoader.getLocaleResolverImpl(); |
| 137 | ||
| 138 | 0 | if (Utils.isEmpty(className)) |
| 139 | 0 | logger.error("Empty LocaleResolver implementation class name provided"); //$NON-NLS-1$ |
| 140 | else | |
| 141 | try { | |
| 142 | 0 | Class<?> clazz = Class.forName(className); |
| 143 | 0 | localeResolver = (LocaleResolver) clazz.newInstance(); |
| 144 | 0 | logger.info("LocaleResolver initialized to {}", className); //$NON-NLS-1$ |
| 145 | 0 | } catch (Throwable e) { |
| 146 | 0 | logger.error("LocaleResolver implementation {} could not be instantiated", className); //$NON-NLS-1$ |
| 147 | 0 | throw new RuntimeException("LocaleResolver implementation " + className + " could not be instantiated", e); //$NON-NLS-1$ |
| 148 | 0 | } |
| 149 | } | |
| 150 | ||
| 151 | 0 | return localeResolver; |
| 152 | } | |
| 153 | ||
| 154 | /** | |
| 155 | * Loads the localized messages for the given locale. This constructor loads | |
| 156 | * the resource bundle for this locale and only for this, in other words it | |
| 157 | * short-circuits the default resource bundle load mechanism in order to | |
| 158 | * prevent the loading of the system default locale which may result in a | |
| 159 | * completely different resource bundle. | |
| 160 | * | |
| 161 | * @param locale | |
| 162 | * the locale of the new localized messages | |
| 163 | */ | |
| 164 | 0 | private LocalizedMessages(Locale locale) { |
| 165 | ||
| 166 | 0 | properties = new Properties(defaultProperties); |
| 167 | ||
| 168 | 0 | ResourceBundle localized = null; |
| 169 | try { | |
| 170 | 0 | localized = ResourceBundle.getBundle(LOCAL_PROPERTIES, locale, |
| 171 | Thread.currentThread().getContextClassLoader()); | |
| 172 | 0 | } catch (Exception e) { |
| 173 | ; // do nothing | |
| 174 | 0 | } |
| 175 | ||
| 176 | 0 | if (localized != null |
| 177 | && localized.getLocale().getLanguage().equals( | |
| 178 | locale.getLanguage())) { | |
| 179 | 0 | Enumeration<String> keys = localized.getKeys(); |
| 180 | ||
| 181 | 0 | while (keys.hasMoreElements()) { |
| 182 | 0 | String key = keys.nextElement(); |
| 183 | 0 | properties.setProperty(key, localized.getString(key)); |
| 184 | 0 | } |
| 185 | ||
| 186 | 0 | logger.debug("Resource bundle for locale '{}' loaded", locale); //$NON-NLS-1$ |
| 187 | 0 | } else { |
| 188 | 0 | logger.debug("No resource bundle for locale '{}' found, loading default bundle", locale); //$NON-NLS-1$ |
| 189 | ||
| 190 | 0 | ResourceBundle base = null; |
| 191 | try { | |
| 192 | 0 | base = ResourceBundle.getBundle(LOCAL_PROPERTIES, NEUTRAL, |
| 193 | Thread.currentThread().getContextClassLoader()); | |
| 194 | 0 | } catch (Exception e) { |
| 195 | ; // do nothing | |
| 196 | 0 | } |
| 197 | ||
| 198 | 0 | if (base != null && base.getLocale().equals(NEUTRAL)) { |
| 199 | 0 | Enumeration<String> keys = base.getKeys(); |
| 200 | ||
| 201 | 0 | while (keys.hasMoreElements()) { |
| 202 | 0 | String key = keys.nextElement(); |
| 203 | 0 | properties.setProperty(key, base.getString(key)); |
| 204 | 0 | } |
| 205 | } | |
| 206 | ||
| 207 | } | |
| 208 | ||
| 209 | 0 | } |
| 210 | ||
| 211 | /** | |
| 212 | * Searches for the message with the specified key in this message list. | |
| 213 | * | |
| 214 | * @see Properties#getProperty(String) | |
| 215 | */ | |
| 216 | private String getMessage(String key) { | |
| 217 | 0 | return properties.getProperty(key); |
| 218 | } | |
| 219 | ||
| 220 | /** Returns localized <code>editor.compatibleBrowser.yes</code> property. */ | |
| 221 | public String getCompatibleBrowserYes() { | |
| 222 | 0 | return getMessage("editor.compatibleBrowser.yes"); //$NON-NLS-1$ |
| 223 | } | |
| 224 | ||
| 225 | /** Returns localized <code>editor.compatibleBrowser.no</code> property. */ | |
| 226 | public String getCompatibleBrowserNo() { | |
| 227 | 0 | return getMessage("editor.compatibleBrowser.no"); //$NON-NLS-1$ |
| 228 | } | |
| 229 | ||
| 230 | /** Returns localized <code>connector.fileUpload.enabled</code> property. */ | |
| 231 | public String getFileUploadEnabled() { | |
| 232 | 0 | return getMessage("connector.fileUpload.enabled"); //$NON-NLS-1$ |
| 233 | } | |
| 234 | ||
| 235 | /** Returns localized <code>connector.fileUpload.disabled</code> property. */ | |
| 236 | public String getFileUploadDisabled() { | |
| 237 | 0 | return getMessage("connector.fileUpload.disabled"); //$NON-NLS-1$ |
| 238 | } | |
| 239 | ||
| 240 | /** | |
| 241 | * Returns localized <code>connector.file_renamed_warning</code> property. | |
| 242 | * | |
| 243 | * @param newFileName | |
| 244 | * the new filename of the warning | |
| 245 | * @return localized message with new filename | |
| 246 | */ | |
| 247 | public String getFileRenamedWarning(String newFileName) { | |
| 248 | 0 | return MessageFormat.format(getMessage("connector.fileUpload.file_renamed_warning"), newFileName); //$NON-NLS-1$ |
| 249 | } | |
| 250 | ||
| 251 | /** Returns localized <code>connector.fileUpload.invalid_file_type_specified</code> property. */ | |
| 252 | public String getInvalidFileTypeSpecified() { | |
| 253 | 0 | return getMessage("connector.fileUpload.invalid_file_type_specified"); //$NON-NLS-1$ |
| 254 | } | |
| 255 | ||
| 256 | /** Returns localized <code>connector.fileUpload.write_error</code> property. */ | |
| 257 | public String getFileUploadWriteError() { | |
| 258 | 0 | return getMessage("connector.fileUpload.write_error"); //$NON-NLS-1$ |
| 259 | } | |
| 260 | ||
| 261 | /** Returns localized <code>connector.getResources.enabled</code> property. */ | |
| 262 | public String getGetResourcesEnabled() { | |
| 263 | 0 | return getMessage("connector.getResources.enabled"); //$NON-NLS-1$ |
| 264 | } | |
| 265 | ||
| 266 | /** Returns localized <code>connector.getResources.disabled</code> property. */ | |
| 267 | public String getGetResourcesDisabled() { | |
| 268 | 0 | return getMessage("connector.getResources.disabled"); //$NON-NLS-1$ |
| 269 | } | |
| 270 | ||
| 271 | /** Returns localized <code>connector.getResources.read_error</code> property. */ | |
| 272 | public String getGetResourcesReadError() { | |
| 273 | 0 | return getMessage("connector.getResources.read_error"); //$NON-NLS-1$ |
| 274 | } | |
| 275 | ||
| 276 | /** Returns localized <code>connector.createFolder.enabled</code> property. */ | |
| 277 | public String getCreateFolderEnabled() { | |
| 278 | 0 | return getMessage("connector.createFolder.enabled"); //$NON-NLS-1$ |
| 279 | } | |
| 280 | ||
| 281 | /** Returns localized <code>connector.createFolder.disabled</code> property. */ | |
| 282 | public String getCreateFolderDisabled() { | |
| 283 | 0 | return getMessage("connector.createFolder.disabled"); //$NON-NLS-1$ |
| 284 | } | |
| 285 | ||
| 286 | /** Returns localized <code>connector.invalid_command_specified</code> property. */ | |
| 287 | public String getInvalidCommandSpecified() { | |
| 288 | 0 | return getMessage("connector.invalid_command_specified"); //$NON-NLS-1$ |
| 289 | } | |
| 290 | ||
| 291 | /** Returns localized <code>connector.createFolder.folder_already_exists_error</code> property. */ | |
| 292 | public String getFolderAlreadyExistsError() { | |
| 293 | 0 | return getMessage("connector.createFolder.folder_already_exists_error"); //$NON-NLS-1$ |
| 294 | } | |
| 295 | ||
| 296 | /** Returns localized <code>connector.createFolder.invalid_new_folder_name_specified</code> property. */ | |
| 297 | public String getInvalidNewFolderNameSpecified() { | |
| 298 | 0 | return getMessage("connector.createFolder.invalid_new_folder_name_specified"); //$NON-NLS-1$ |
| 299 | } | |
| 300 | ||
| 301 | /** Returns localized <code>connector.createFolder.write_error</code> property. */ | |
| 302 | public String getCreateFolderWriteError() { | |
| 303 | 0 | return getMessage("connector.createFolder.write_error"); //$NON-NLS-1$ |
| 304 | } | |
| 305 | ||
| 306 | /** Returns localized <code>connector.invalid_resource_type_specified</code> property. */ | |
| 307 | public String getInvalidResouceTypeSpecified() { | |
| 308 | 0 | return getMessage("connector.invalid_resource_type_specified"); //$NON-NLS-1$ |
| 309 | } | |
| 310 | ||
| 311 | /** Returns localized <code>connector.invalid_current_folder_specified</code> property. */ | |
| 312 | public String getInvalidCurrentFolderSpecified() { | |
| 313 | 0 | return getMessage("connector.invalid_current_folder_specified"); //$NON-NLS-1$ |
| 314 | } | |
| 315 | ||
| 316 | } |