001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle; 021 022import java.io.IOException; 023import java.io.InputStreamReader; 024import java.io.Reader; 025import java.net.URL; 026import java.net.URLConnection; 027import java.nio.charset.StandardCharsets; 028import java.text.MessageFormat; 029import java.util.Locale; 030import java.util.MissingResourceException; 031import java.util.PropertyResourceBundle; 032import java.util.ResourceBundle; 033import java.util.ResourceBundle.Control; 034 035import com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil; 036 037/** 038 * Represents a message that can be localised. The translations come from 039 * message.properties files. The underlying implementation uses 040 * java.text.MessageFormat. 041 */ 042public class LocalizedMessage { 043 044 /** The locale to localise messages to. **/ 045 private static Locale sLocale = Locale.getDefault(); 046 047 /** Name of the resource bundle to get messages from. **/ 048 private final String bundle; 049 050 /** Class of the source for this message. */ 051 private final Class<?> sourceClass; 052 053 /** 054 * Key for the message format. 055 **/ 056 private final String key; 057 058 /** 059 * Arguments for java.text.MessageFormat, that is why type is Object[]. 060 * 061 * <p>Note: Changing types from Object[] will be huge breaking compatibility, as Module 062 * messages use some type formatting already, so better to keep it as Object[]. 063 * </p> 064 */ 065 private final Object[] args; 066 067 /** 068 * Creates a new {@code LocalizedMessage} instance. 069 * 070 * @param bundle resource bundle name 071 * @param sourceClass the Class that is the source of the message 072 * @param key the key to locate the translation. 073 * @param args arguments for the translation. 074 */ 075 public LocalizedMessage(String bundle, Class<?> sourceClass, String key, 076 Object... args) { 077 this.bundle = bundle; 078 this.sourceClass = sourceClass; 079 this.key = key; 080 if (args == null) { 081 this.args = null; 082 } 083 else { 084 this.args = UnmodifiableCollectionUtil.copyOfArray(args, args.length); 085 } 086 } 087 088 /** 089 * Sets a locale to use for localization. 090 * 091 * @param locale the locale to use for localization 092 */ 093 public static void setLocale(Locale locale) { 094 if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) { 095 sLocale = Locale.ROOT; 096 } 097 else { 098 sLocale = locale; 099 } 100 } 101 102 /** 103 * Gets the translated message. 104 * 105 * @return the translated message. 106 */ 107 public String getMessage() { 108 String result; 109 try { 110 // Important to use the default class loader, and not the one in 111 // the GlobalProperties object. This is because the class loader in 112 // the GlobalProperties is specified by the user for resolving 113 // custom classes. 114 final ResourceBundle resourceBundle = getBundle(); 115 final String pattern = resourceBundle.getString(key); 116 final MessageFormat formatter = new MessageFormat(pattern, Locale.ROOT); 117 result = formatter.format(args); 118 } 119 catch (final MissingResourceException ignored) { 120 // If the Check author didn't provide i18n resource bundles 121 // and logs audit event messages directly, this will return 122 // the author's original message 123 final MessageFormat formatter = new MessageFormat(key, Locale.ROOT); 124 result = formatter.format(args); 125 } 126 return result; 127 } 128 129 /** 130 * Obtain the ResourceBundle. Uses the classloader 131 * of the class emitting this message, to be sure to get the correct 132 * bundle. 133 * 134 * @return a ResourceBundle. 135 */ 136 private ResourceBundle getBundle() { 137 return ResourceBundle.getBundle(bundle, sLocale, sourceClass.getClassLoader(), 138 new Utf8Control()); 139 } 140 141 /** 142 * Custom ResourceBundle.Control implementation which allows explicitly read 143 * the properties files as UTF-8. 144 */ 145 public static class Utf8Control extends Control { 146 147 @Override 148 public ResourceBundle newBundle(String baseName, Locale locale, String format, 149 ClassLoader loader, boolean reload) throws IOException { 150 // The below is a copy of the default implementation. 151 final String bundleName = toBundleName(baseName, locale); 152 final String resourceName = toResourceName(bundleName, "properties"); 153 final URL url = loader.getResource(resourceName); 154 ResourceBundle resourceBundle = null; 155 if (url != null) { 156 final URLConnection connection = url.openConnection(); 157 if (connection != null) { 158 connection.setUseCaches(!reload); 159 try (Reader streamReader = new InputStreamReader(connection.getInputStream(), 160 StandardCharsets.UTF_8)) { 161 // Only this line is changed to make it read property files as UTF-8. 162 resourceBundle = new PropertyResourceBundle(streamReader); 163 } 164 } 165 } 166 return resourceBundle; 167 } 168 169 } 170 171}