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