1 /******************************************************************************* 2 * Copyright (c) 2000, 2016 IBM Corporation and others. 3 * 4 * This program and the accompanying materials 5 * are made available under the terms of the Eclipse Public License 2.0 6 * which accompanies this distribution, and is available at 7 * https://www.eclipse.org/legal/epl-2.0/ 8 * 9 * SPDX-License-Identifier: EPL-2.0 10 * 11 * Contributors: 12 * IBM Corporation - initial API and implementation 13 *******************************************************************************/ 14 package org.eclipse.jdt.internal.compiler.util; 15 16 import java.io.IOException; 17 import java.io.InputStream; 18 import java.lang.reflect.Field; 19 import java.lang.reflect.Modifier; 20 import java.text.MessageFormat; 21 import java.util.ArrayList; 22 import java.util.HashMap; 23 import java.util.Locale; 24 import java.util.Map; 25 import java.util.Properties; 26 27 @SuppressWarnings({"rawtypes", "unchecked"}) 28 public final class Messages { 29 private static class MessagesProperties extends Properties { 30 31 private static final int MOD_EXPECTED = Modifier.PUBLIC | Modifier.STATIC; 32 private static final int MOD_MASK = MOD_EXPECTED | Modifier.FINAL; 33 private static final long serialVersionUID = 1L; 34 35 private final Map fields; 36 MessagesProperties(Field[] fieldArray, String bundleName)37 public MessagesProperties(Field[] fieldArray, String bundleName) { 38 super(); 39 final int len = fieldArray.length; 40 this.fields = new HashMap(len * 2); 41 for (int i = 0; i < len; i++) { 42 this.fields.put(fieldArray[i].getName(), fieldArray[i]); 43 } 44 } 45 46 @Override put(Object key, Object value)47 public synchronized Object put(Object key, Object value) { 48 try { 49 Field field = (Field) this.fields.get(key); 50 if (field == null) { 51 return null; 52 } 53 //can only set value of public static non-final fields 54 if ((field.getModifiers() & MOD_MASK) != MOD_EXPECTED) 55 return null; 56 // Set the value into the field. We should never get an exception here because 57 // we know we have a public static non-final field. If we do get an exception, silently 58 // log it and continue. This means that the field will (most likely) be un-initialized and 59 // will fail later in the code and if so then we will see both the NPE and this error. 60 try { 61 field.set(null, value); 62 } catch (Exception e) { 63 // ignore 64 } 65 } catch (SecurityException e) { 66 // ignore 67 } 68 return null; 69 } 70 } 71 72 73 private static String[] nlSuffixes; 74 private static final String EXTENSION = ".properties"; //$NON-NLS-1$ 75 76 private static final String BUNDLE_NAME = "org.eclipse.jdt.internal.compiler.messages";//$NON-NLS-1$ 77 Messages()78 private Messages() { 79 // Do not instantiate 80 } 81 82 public static String compilation_unresolvedProblem; 83 public static String compilation_unresolvedProblems; 84 public static String compilation_request; 85 public static String compilation_loadBinary; 86 public static String compilation_process; 87 public static String compilation_write; 88 public static String compilation_done; 89 public static String compilation_units; 90 public static String compilation_unit; 91 public static String compilation_internalError; 92 public static String compilation_beginningToCompile; 93 public static String compilation_processing; 94 public static String output_isFile; 95 public static String output_notValidAll; 96 public static String output_notValid; 97 public static String problem_noSourceInformation; 98 public static String problem_atLine; 99 public static String abort_invalidAttribute; 100 public static String abort_invalidExceptionAttribute; 101 public static String abort_invalidOpcode; 102 public static String abort_missingCode; 103 public static String abort_againstSourceModel; 104 public static String abort_externaAnnotationFile; 105 public static String accept_cannot; 106 public static String parser_incorrectPath; 107 public static String parser_moveFiles; 108 public static String parser_syntaxRecovery; 109 public static String parser_regularParse; 110 public static String parser_missingFile; 111 public static String parser_corruptedFile; 112 public static String parser_endOfFile; 113 public static String parser_endOfConstructor; 114 public static String parser_endOfMethod; 115 public static String parser_endOfInitializer; 116 public static String ast_missingCode; 117 public static String constant_cannotCastedInto; 118 public static String constant_cannotConvertedTo; 119 public static String abort_againstPreviewNotAllowed; 120 121 static { initializeMessages(BUNDLE_NAME, Messages.class)122 initializeMessages(BUNDLE_NAME, Messages.class); 123 } 124 125 /** 126 * Bind the given message's substitution locations with the given string values. 127 * 128 * @param message the message to be manipulated 129 * @return the manipulated String 130 */ bind(String message)131 public static String bind(String message) { 132 return bind(message, null); 133 } 134 135 /** 136 * Bind the given message's substitution locations with the given string values. 137 * 138 * @param message the message to be manipulated 139 * @param binding the object to be inserted into the message 140 * @return the manipulated String 141 */ bind(String message, Object binding)142 public static String bind(String message, Object binding) { 143 return bind(message, new Object[] {binding}); 144 } 145 146 /** 147 * Bind the given message's substitution locations with the given string values. 148 * 149 * @param message the message to be manipulated 150 * @param binding1 An object to be inserted into the message 151 * @param binding2 A second object to be inserted into the message 152 * @return the manipulated String 153 */ bind(String message, Object binding1, Object binding2)154 public static String bind(String message, Object binding1, Object binding2) { 155 return bind(message, new Object[] {binding1, binding2}); 156 } 157 158 /** 159 * Bind the given message's substitution locations with the given string values. 160 * 161 * @param message the message to be manipulated 162 * @param bindings An array of objects to be inserted into the message 163 * @return the manipulated String 164 */ bind(String message, Object[] bindings)165 public static String bind(String message, Object[] bindings) { 166 return MessageFormat.format(message, bindings); 167 } 168 169 /* 170 * Build an array of directories to search 171 */ buildVariants(String root)172 private static String[] buildVariants(String root) { 173 if (nlSuffixes == null) { 174 //build list of suffixes for loading resource bundles 175 String nl = Locale.getDefault().toString(); 176 ArrayList result = new ArrayList(4); 177 int lastSeparator; 178 while (true) { 179 result.add('_' + nl + EXTENSION); 180 lastSeparator = nl.lastIndexOf('_'); 181 if (lastSeparator == -1) 182 break; 183 nl = nl.substring(0, lastSeparator); 184 } 185 //add the empty suffix last (most general) 186 result.add(EXTENSION); 187 nlSuffixes = (String[]) result.toArray(new String[result.size()]); 188 } 189 root = root.replace('.', '/'); 190 String[] variants = new String[nlSuffixes.length]; 191 for (int i = 0; i < variants.length; i++) 192 variants[i] = root + nlSuffixes[i]; 193 return variants; 194 } initializeMessages(String bundleName, Class clazz)195 public static void initializeMessages(String bundleName, Class clazz) { 196 // load the resource bundle and set the fields 197 final Field[] fields = clazz.getDeclaredFields(); 198 load(bundleName, clazz.getClassLoader(), fields); 199 200 // iterate over the fields in the class to make sure that there aren't any empty ones 201 final int MOD_EXPECTED = Modifier.PUBLIC | Modifier.STATIC; 202 final int MOD_MASK = MOD_EXPECTED | Modifier.FINAL; 203 final int numFields = fields.length; 204 for (int i = 0; i < numFields; i++) { 205 Field field = fields[i]; 206 if ((field.getModifiers() & MOD_MASK) != MOD_EXPECTED) 207 continue; 208 try { 209 // Set the value into the field if its empty. We should never get an exception here because 210 // we know we have a public static non-final field. If we do get an exception, silently 211 // log it and continue. This means that the field will (most likely) be un-initialized and 212 // will fail later in the code and if so then we will see both the NPE and this error. 213 if (field.get(clazz) == null) { 214 String value = "Missing message: " + field.getName() + " in: " + bundleName; //$NON-NLS-1$ //$NON-NLS-2$ 215 field.set(null, value); 216 } 217 } catch (IllegalArgumentException | IllegalAccessException e) { 218 // ignore 219 } 220 } 221 } 222 /** 223 * Load the given resource bundle using the specified class loader. 224 */ load(final String bundleName, final ClassLoader loader, final Field[] fields)225 public static void load(final String bundleName, final ClassLoader loader, final Field[] fields) { 226 final String[] variants = buildVariants(bundleName); 227 // search the dirs in reverse order so the cascading defaults is set correctly 228 for (int i = variants.length; --i >= 0;) { 229 InputStream input = (loader == null) 230 ? ClassLoader.getSystemResourceAsStream(variants[i]) 231 : loader.getResourceAsStream(variants[i]); 232 if (input == null) continue; 233 try { 234 final MessagesProperties properties = new MessagesProperties(fields, bundleName); 235 properties.load(input); 236 } catch (IOException e) { 237 // ignore 238 } finally { 239 try { 240 input.close(); 241 } catch (IOException e) { 242 // ignore 243 } 244 } 245 } 246 } 247 } 248