001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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.Arrays; 030import java.util.Locale; 031import java.util.MissingResourceException; 032import java.util.PropertyResourceBundle; 033import java.util.ResourceBundle; 034import java.util.ResourceBundle.Control; 035 036/** 037 * Represents a message that can be localised. The translations come from 038 * message.properties files. The underlying implementation uses 039 * java.text.MessageFormat. 040 */ 041public class LocalizedMessage { 042 043 /** The locale to localise messages to. **/ 044 private static Locale sLocale = Locale.getDefault(); 045 046 /** Name of the resource bundle to get messages from. **/ 047 private final String bundle; 048 049 /** Class of the source for this message. */ 050 private final Class<?> sourceClass; 051 052 /** 053 * Key for the message format. 054 **/ 055 private final String key; 056 057 /** 058 * Arguments for java.text.MessageFormat, that is why type is Object[]. 059 * 060 * <p>Note: Changing types from Object[] will be huge breaking compatibility, as Module 061 * messages use some type formatting already, so better to keep it as Object[]. 062 * </p> 063 */ 064 private final Object[] args; 065 066 /** 067 * Creates a new {@code LocalizedMessage} instance. 068 * 069 * @param bundle resource bundle name 070 * @param sourceClass the Class that is the source of the message 071 * @param key the key to locate the translation. 072 * @param args arguments for the translation. 073 */ 074 public LocalizedMessage(String bundle, Class<?> sourceClass, String key, 075 Object... args) { 076 this.bundle = bundle; 077 this.sourceClass = sourceClass; 078 this.key = key; 079 if (args == null) { 080 this.args = null; 081 } 082 else { 083 this.args = Arrays.copyOf(args, args.length); 084 } 085 } 086 087 /** 088 * Sets a locale to use for localization. 089 * 090 * @param locale the locale to use for localization 091 */ 092 public static void setLocale(Locale locale) { 093 if (Locale.ENGLISH.getLanguage().equals(locale.getLanguage())) { 094 sLocale = Locale.ROOT; 095 } 096 else { 097 sLocale = locale; 098 } 099 } 100 101 /** 102 * Gets the translated message. 103 * 104 * @return the translated message. 105 */ 106 public String getMessage() { 107 String result; 108 try { 109 // Important to use the default class loader, and not the one in 110 // the GlobalProperties object. This is because the class loader in 111 // the GlobalProperties is specified by the user for resolving 112 // custom classes. 113 final ResourceBundle resourceBundle = getBundle(); 114 final String pattern = resourceBundle.getString(key); 115 final MessageFormat formatter = new MessageFormat(pattern, Locale.ROOT); 116 result = formatter.format(args); 117 } 118 catch (final MissingResourceException ignored) { 119 // If the Check author didn't provide i18n resource bundles 120 // and logs audit event messages directly, this will return 121 // the author's original message 122 final MessageFormat formatter = new MessageFormat(key, Locale.ROOT); 123 result = formatter.format(args); 124 } 125 return result; 126 } 127 128 /** 129 * Obtain the ResourceBundle. Uses the classloader 130 * of the class emitting this message, to be sure to get the correct 131 * bundle. 132 * 133 * @return a ResourceBundle. 134 */ 135 private ResourceBundle getBundle() { 136 return ResourceBundle.getBundle(bundle, sLocale, sourceClass.getClassLoader(), 137 new Utf8Control()); 138 } 139 140 /** 141 * <p> 142 * Custom ResourceBundle.Control implementation which allows explicitly read 143 * the properties files as UTF-8. 144 * </p> 145 */ 146 public static class Utf8Control extends Control { 147 148 @Override 149 public ResourceBundle newBundle(String baseName, Locale locale, String format, 150 ClassLoader loader, boolean reload) throws IOException { 151 // The below is a copy of the default implementation. 152 final String bundleName = toBundleName(baseName, locale); 153 final String resourceName = toResourceName(bundleName, "properties"); 154 final URL url = loader.getResource(resourceName); 155 ResourceBundle resourceBundle = null; 156 if (url != null) { 157 final URLConnection connection = url.openConnection(); 158 if (connection != null) { 159 connection.setUseCaches(!reload); 160 try (Reader streamReader = new InputStreamReader(connection.getInputStream(), 161 StandardCharsets.UTF_8)) { 162 // Only this line is changed to make it read property files as UTF-8. 163 resourceBundle = new PropertyResourceBundle(streamReader); 164 } 165 } 166 } 167 return resourceBundle; 168 } 169 170 } 171 172}