KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > osgi > util > NLS


1 /*******************************************************************************
2  * Copyright (c) 2005 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  * IBM - Initial API and implementation
10  *******************************************************************************/

11 package org.eclipse.osgi.util;
12
13 import java.io.IOException JavaDoc;
14 import java.io.InputStream JavaDoc;
15 import java.lang.reflect.Field JavaDoc;
16 import java.lang.reflect.Modifier JavaDoc;
17 import java.security.AccessController JavaDoc;
18 import java.security.PrivilegedAction JavaDoc;
19 import java.util.*;
20 import org.eclipse.osgi.framework.debug.Debug;
21 import org.eclipse.osgi.framework.log.FrameworkLog;
22 import org.eclipse.osgi.framework.log.FrameworkLogEntry;
23
24 /**
25  * Common superclass for all message bundle classes. Provides convenience
26  * methods for manipulating messages.
27  * <p>
28  * The <code>#bind</code> methods perform string substitution and should be considered a
29  * convenience and <em>not</em> a full substitute replacement for <code>MessageFormat#format</code>
30  * method calls.
31  * </p>
32  * <p>
33  * Text appearing within curly braces in the given message, will be interpreted
34  * as a numeric index to the corresponding substitution object in the given array. Calling
35  * the <code>#bind</code> methods with text that does not map to an integer will result in an
36  * {@link IllegalArgumentException}.
37  * </p>
38  * <p>
39  * Text appearing within single quotes is treated as a literal. A single quote is escaped by
40  * a preceeding single quote.
41  * </p>
42  * <p>
43  * Clients who wish to use the full substitution power of the <code>MessageFormat</code> class should
44  * call that class directly and not use these <code>#bind</code> methods.
45  * </p>
46  * <p>
47  * Clients may subclass this type.
48  * </p>
49  *
50  * @since 3.1
51  */

52 public abstract class NLS {
53
54     private static final Object JavaDoc[] EMPTY_ARGS = new Object JavaDoc[0];
55     private static final String JavaDoc EXTENSION = ".properties"; //$NON-NLS-1$
56
private static String JavaDoc[] nlSuffixes;
57     /*
58      * NOTE do not change the name of this field; it is set by the Framework using reflection
59      */

60     private static FrameworkLog frameworkLog;
61
62     static final int SEVERITY_ERROR = 0x04;
63     static final int SEVERITY_WARNING = 0x02;
64     /*
65      * This object is assigned to the value of a field map to indicate
66      * that a translated message has already been assigned to that field.
67      */

68     static final Object JavaDoc ASSIGNED = new Object JavaDoc();
69
70     /**
71      * Creates a new NLS instance.
72      */

73     protected NLS() {
74         super();
75     }
76
77     /**
78      * Bind the given message's substitution locations with the given string value.
79      *
80      * @param message the message to be manipulated
81      * @param binding the object to be inserted into the message
82      * @return the manipulated String
83      */

84     public static String JavaDoc bind(String JavaDoc message, Object JavaDoc binding) {
85         return internalBind(message, null, String.valueOf(binding), null);
86     }
87
88     /**
89      * Bind the given message's substitution locations with the given string values.
90      *
91      * @param message the message to be manipulated
92      * @param binding1 An object to be inserted into the message
93      * @param binding2 A second object to be inserted into the message
94      * @return the manipulated String
95      */

96     public static String JavaDoc bind(String JavaDoc message, Object JavaDoc binding1, Object JavaDoc binding2) {
97         return internalBind(message, null, String.valueOf(binding1), String.valueOf(binding2));
98     }
99
100     /**
101      * Bind the given message's substitution locations with the given string values.
102      *
103      * @param message the message to be manipulated
104      * @param bindings An array of objects to be inserted into the message
105      * @return the manipulated String
106      */

107     public static String JavaDoc bind(String JavaDoc message, Object JavaDoc[] bindings) {
108         return internalBind(message, bindings, null, null);
109     }
110
111     /**
112      * Initialize the given class with the values from the specified message bundle.
113      *
114      * @param bundleName fully qualified path of the class name
115      * @param clazz the class where the constants will exist
116      */

117     public static void initializeMessages(final String JavaDoc bundleName, final Class JavaDoc clazz) {
118         if (System.getSecurityManager() == null) {
119             load(bundleName, clazz);
120             return;
121         }
122         AccessController.doPrivileged(new PrivilegedAction JavaDoc() {
123             public Object JavaDoc run() {
124                 load(bundleName, clazz);
125                 return null;
126             }
127         });
128     }
129
130     /*
131      * Perform the string substitution on the given message with the specified args.
132      * See the class comment for exact details.
133      */

134     private static String JavaDoc internalBind(String JavaDoc message, Object JavaDoc[] args, String JavaDoc argZero, String JavaDoc argOne) {
135         if (message == null)
136             return "No message available."; //$NON-NLS-1$
137
if (args == null || args.length == 0)
138             args = EMPTY_ARGS;
139
140         int length = message.length();
141         //estimate correct size of string buffer to avoid growth
142
int bufLen = length + (args.length * 5);
143         if (argZero != null)
144             bufLen += argZero.length() - 3;
145         if (argOne != null)
146             bufLen += argOne.length() - 3;
147         StringBuffer JavaDoc buffer = new StringBuffer JavaDoc(bufLen);
148         for (int i = 0; i < length; i++) {
149             char c = message.charAt(i);
150             switch (c) {
151                 case '{' :
152                     int index = message.indexOf('}', i);
153                     // if we don't have a matching closing brace then...
154
if (index == -1) {
155                         buffer.append(c);
156                         break;
157                     }
158                     i++;
159                     if (i >= length) {
160                         buffer.append(c);
161                         break;
162                     }
163                     // look for a substitution
164
int number = -1;
165                     try {
166                         number = Integer.parseInt(message.substring(i, index));
167                     } catch (NumberFormatException JavaDoc e) {
168                         throw new IllegalArgumentException JavaDoc();
169                     }
170                     if (number == 0 && argZero != null)
171                         buffer.append(argZero);
172                     else if (number == 1 && argOne != null)
173                         buffer.append(argOne);
174                     else {
175                         if (number >= args.length || number < 0) {
176                             buffer.append("<missing argument>"); //$NON-NLS-1$
177
i = index;
178                             break;
179                         }
180                         buffer.append(args[number]);
181                     }
182                     i = index;
183                     break;
184                 case '\'' :
185                     // if a single quote is the last char on the line then skip it
186
int nextIndex = i + 1;
187                     if (nextIndex >= length) {
188                         buffer.append(c);
189                         break;
190                     }
191                     char next = message.charAt(nextIndex);
192                     // if the next char is another single quote then write out one
193
if (next == '\'') {
194                         i++;
195                         buffer.append(c);
196                         break;
197                     }
198                     // otherwise we want to read until we get to the next single quote
199
index = message.indexOf('\'', nextIndex);
200                     // if there are no more in the string, then skip it
201
if (index == -1) {
202                         buffer.append(c);
203                         break;
204                     }
205                     // otherwise write out the chars inside the quotes
206
buffer.append(message.substring(nextIndex, index));
207                     i = index;
208                     break;
209                 default :
210                     buffer.append(c);
211             }
212         }
213         return buffer.toString();
214     }
215
216     /*
217      * Build an array of property files to search. The returned array contains
218      * the property fields in order from most specific to most generic.
219      * So, in the FR_fr locale, it will return file_fr_FR.properties, then
220      * file_fr.properties, and finally file.properties.
221      */

222     private static String JavaDoc[] buildVariants(String JavaDoc root) {
223         if (nlSuffixes == null) {
224             //build list of suffixes for loading resource bundles
225
String JavaDoc nl = Locale.getDefault().toString();
226             ArrayList result = new ArrayList(4);
227             int lastSeparator;
228             while (true) {
229                 result.add('_' + nl + EXTENSION);
230                 lastSeparator = nl.lastIndexOf('_');
231                 if (lastSeparator == -1)
232                     break;
233                 nl = nl.substring(0, lastSeparator);
234             }
235             //add the empty suffix last (most general)
236
result.add(EXTENSION);
237             nlSuffixes = (String JavaDoc[]) result.toArray(new String JavaDoc[result.size()]);
238         }
239         root = root.replace('.', '/');
240         String JavaDoc[] variants = new String JavaDoc[nlSuffixes.length];
241         for (int i = 0; i < variants.length; i++)
242             variants[i] = root + nlSuffixes[i];
243         return variants;
244     }
245
246     private static void computeMissingMessages(String JavaDoc bundleName, Class JavaDoc clazz, Map fieldMap, Field JavaDoc[] fieldArray, boolean isAccessible) {
247         // iterate over the fields in the class to make sure that there aren't any empty ones
248
final int MOD_EXPECTED = Modifier.PUBLIC | Modifier.STATIC;
249         final int MOD_MASK = MOD_EXPECTED | Modifier.FINAL;
250         final int numFields = fieldArray.length;
251         for (int i = 0; i < numFields; i++) {
252             Field JavaDoc field = fieldArray[i];
253             if ((field.getModifiers() & MOD_MASK) != MOD_EXPECTED)
254                 continue;
255             //if the field has a a value assigned, there is nothing to do
256
if (fieldMap.get(field.getName()) == ASSIGNED)
257                 continue;
258             try {
259                 // Set a value for this empty field. We should never get an exception here because
260
// we know we have a public static non-final field. If we do get an exception, silently
261
// log it and continue. This means that the field will (most likely) be un-initialized and
262
// will fail later in the code and if so then we will see both the NPE and this error.
263
String JavaDoc value = "NLS missing message: " + field.getName() + " in: " + bundleName; //$NON-NLS-1$ //$NON-NLS-2$
264
if (Debug.DEBUG_MESSAGE_BUNDLES)
265                     System.out.println(value);
266                 log(SEVERITY_WARNING, value, null);
267                 if (!isAccessible)
268                     field.setAccessible(true);
269                 field.set(null, value);
270             } catch (Exception JavaDoc e) {
271                 log(SEVERITY_ERROR, "Error setting the missing message value for: " + field.getName(), e); //$NON-NLS-1$
272
}
273         }
274     }
275
276     /*
277      * Load the given resource bundle using the specified class loader.
278      */

279     static void load(final String JavaDoc bundleName, Class JavaDoc clazz) {
280         long start = System.currentTimeMillis();
281         final Field JavaDoc[] fieldArray = clazz.getDeclaredFields();
282         ClassLoader JavaDoc loader = clazz.getClassLoader();
283
284         boolean isAccessible = (clazz.getModifiers() & Modifier.PUBLIC) != 0;
285
286         //build a map of field names to Field objects
287
final int len = fieldArray.length;
288         Map fields = new HashMap(len * 2);
289         for (int i = 0; i < len; i++)
290             fields.put(fieldArray[i].getName(), fieldArray[i]);
291
292         // search the variants from most specific to most general, since
293
// the MessagesProperties.put method will mark assigned fields
294
// to prevent them from being assigned twice
295
final String JavaDoc[] variants = buildVariants(bundleName);
296         for (int i = 0; i < variants.length; i++) {
297             // loader==null if we're launched off the Java boot classpath
298
final InputStream JavaDoc input = loader==null ? ClassLoader.getSystemResourceAsStream(variants[i]) : loader.getResourceAsStream(variants[i]);
299             if (input == null)
300                 continue;
301             try {
302                 final MessagesProperties properties = new MessagesProperties(fields, bundleName, isAccessible);
303                 properties.load(input);
304             } catch (IOException JavaDoc e) {
305                 log(SEVERITY_ERROR, "Error loading " + variants[i], e); //$NON-NLS-1$
306
} finally {
307                 if (input != null)
308                     try {
309                         input.close();
310                     } catch (IOException JavaDoc e) {
311                         // ignore
312
}
313             }
314         }
315         computeMissingMessages(bundleName, clazz, fields, fieldArray, isAccessible);
316         if (Debug.DEBUG_MESSAGE_BUNDLES)
317             System.out.println("Time to load message bundle: " + bundleName + " was " + (System.currentTimeMillis() - start) + "ms."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
318
}
319     
320     /*
321      * The method adds a log entry based on the error message and exception.
322      * The output is written to the System.err.
323      *
324      * This method is only expected to be called if there is a problem in
325      * the NLS mechanism. As a result, translation facility is not available
326      * here and messages coming out of this log are generally not translated.
327      *
328      * @param severity - severity of the message (SEVERITY_ERROR or SEVERITY_WARNING)
329      * @param message - message to log
330      * @param e - exception to log
331      */

332     static void log(int severity, String JavaDoc message, Exception JavaDoc e) {
333         if (frameworkLog != null) {
334             frameworkLog.log(new FrameworkLogEntry("org.eclipse.osgi", severity, 1, message, 0, e, null)); //$NON-NLS-1$
335
return;
336         }
337         String JavaDoc statusMsg;
338         switch (severity) {
339             case SEVERITY_ERROR :
340                 statusMsg = "Error: "; //$NON-NLS-1$
341
break;
342             case SEVERITY_WARNING :
343             // intentionally fall through:
344
default :
345                 statusMsg = "Warning: "; //$NON-NLS-1$
346
}
347         if (message != null)
348             statusMsg += message;
349         if (e != null)
350             statusMsg += ": " + e.getMessage(); //$NON-NLS-1$
351
System.err.println(statusMsg);
352         if (e != null)
353             e.printStackTrace();
354     }
355
356     /*
357      * Class which sub-classes java.util.Properties and uses the #put method
358      * to set field values rather than storing the values in the table.
359      */

360     private static class MessagesProperties extends Properties {
361
362         private static final int MOD_EXPECTED = Modifier.PUBLIC | Modifier.STATIC;
363         private static final int MOD_MASK = MOD_EXPECTED | Modifier.FINAL;
364         private static final long serialVersionUID = 1L;
365
366         private final String JavaDoc bundleName;
367         private final Map fields;
368         private final boolean isAccessible;
369
370         public MessagesProperties(Map fieldMap, String JavaDoc bundleName, boolean isAccessible) {
371             super();
372             this.fields = fieldMap;
373             this.bundleName = bundleName;
374             this.isAccessible = isAccessible;
375         }
376
377         /* (non-Javadoc)
378          * @see java.util.Hashtable#put(java.lang.Object, java.lang.Object)
379          */

380         public synchronized Object JavaDoc put(Object JavaDoc key, Object JavaDoc value) {
381             Object JavaDoc fieldObject = fields.put(key, ASSIGNED);
382             // if already assigned, there is nothing to do
383
if (fieldObject == ASSIGNED)
384                 return null;
385             if (fieldObject == null) {
386                 final String JavaDoc msg = "NLS unused message: " + key + " in: " + bundleName;//$NON-NLS-1$ //$NON-NLS-2$
387
if (Debug.DEBUG_MESSAGE_BUNDLES)
388                     System.out.println(msg);
389                 log(SEVERITY_WARNING, msg, null);
390                 return null;
391             }
392             final Field JavaDoc field = (Field JavaDoc) fieldObject;
393             //can only set value of public static non-final fields
394
if ((field.getModifiers() & MOD_MASK) != MOD_EXPECTED)
395                 return null;
396             try {
397                 // Check to see if we are allowed to modify the field. If we aren't (for instance
398
// if the class is not public) then change the accessible attribute of the field
399
// before trying to set the value.
400
if (!isAccessible)
401                     field.setAccessible(true);
402                 // Set the value into the field. We should never get an exception here because
403
// we know we have a public static non-final field. If we do get an exception, silently
404
// log it and continue. This means that the field will (most likely) be un-initialized and
405
// will fail later in the code and if so then we will see both the NPE and this error.
406
field.set(null, value);
407             } catch (Exception JavaDoc e) {
408                 log(SEVERITY_ERROR, "Exception setting field value.", e); //$NON-NLS-1$
409
}
410             return null;
411         }
412     }
413 }
Popular Tags