blob: 4f7564e30657868c0bff961954c7b238bd6d533e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.core.internal.runtime;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.*;
import org.eclipse.core.runtime.Status;
/**
* This class is used for NLS processing in plugins that can't depend on OSGi.
*
* Class responsible for loading message values from a property file
* and assigning them directly to the fields of a messages class.
*
* Copied from org.eclipse.osgi.framework.internal.core.MessageResourceBundle as of August 30, 2005.
* Debug code removed; logging is done via RuntimeLog.
*
* @since org.eclipse.equinox.common 1.0
*/
public class MessageResourceBundle {
/**
* Class which sub-classes java.util.Properties and uses the #put method
* to set field values rather than storing the values in the table.
*
* @since 3.1
*/
private static class MessagesProperties extends Properties {
private static final int MOD_EXPECTED = Modifier.PUBLIC | Modifier.STATIC;
private static final int MOD_MASK = MOD_EXPECTED | Modifier.FINAL;
private static final long serialVersionUID = 1L;
private final String bundleName;
private final Map fields;
private final boolean isAccessible;
public MessagesProperties(Map fieldMap, String bundleName, boolean isAccessible) {
super();
this.fields = fieldMap;
this.bundleName = bundleName;
this.isAccessible = isAccessible;
}
/* (non-Javadoc)
* @see java.util.Hashtable#put(java.lang.Object, java.lang.Object)
*/
public synchronized Object put(Object key, Object value) {
Object fieldObject = fields.put(key, ASSIGNED);
// if already assigned, there is nothing to do
if (fieldObject == ASSIGNED)
return null;
if (fieldObject == null) {
final String msg = "NLS unused message: " + key + " in: " + bundleName;//$NON-NLS-1$ //$NON-NLS-2$
log(SEVERITY_WARNING, msg, null);
return null;
}
final Field field = (Field) fieldObject;
//can only set value of public static non-final fields
if ((field.getModifiers() & MOD_MASK) != MOD_EXPECTED)
return null;
try {
// Check to see if we are allowed to modify the field. If we aren't (for instance
// if the class is not public) then change the accessible attribute of the field
// before trying to set the value.
if (!isAccessible)
makeAccessible(field);
// Set the value into the field. We should never get an exception here because
// we know we have a public static non-final field. If we do get an exception, silently
// log it and continue. This means that the field will (most likely) be un-initialized and
// will fail later in the code and if so then we will see both the NPE and this error.
field.set(null, value);
} catch (Exception e) {
log(SEVERITY_ERROR, "Exception setting field value.", e); //$NON-NLS-1$
}
return null;
}
}
/**
* This object is assigned to the value of a field map to indicate
* that a translated message has already been assigned to that field.
*/
static final Object ASSIGNED = new Object();
private static final String EXTENSION = ".properties"; //$NON-NLS-1$
private static String[] nlSuffixes;
static int SEVERITY_ERROR = 0x04;
static int SEVERITY_WARNING = 0x02;
/*
* Build an array of property files to search. The returned array contains
* the property fields in order from most specific to most generic.
* So, in the FR_fr locale, it will return file_fr_FR.properties, then
* file_fr.properties, and finally file.properties.
*/
private static String[] buildVariants(String root) {
if (nlSuffixes == null) {
//build list of suffixes for loading resource bundles
String nl = Locale.getDefault().toString();
ArrayList result = new ArrayList(4);
int lastSeparator;
while (true) {
result.add('_' + nl + EXTENSION);
lastSeparator = nl.lastIndexOf('_');
if (lastSeparator == -1)
break;
nl = nl.substring(0, lastSeparator);
}
//add the empty suffix last (most general)
result.add(EXTENSION);
nlSuffixes = (String[]) result.toArray(new String[result.size()]);
}
root = root.replace('.', '/');
String[] variants = new String[nlSuffixes.length];
for (int i = 0; i < variants.length; i++)
variants[i] = root + nlSuffixes[i];
return variants;
}
/*
* Change the accessibility of the specified field so we can set its value
* to be the appropriate message string.
*/
static void makeAccessible(final Field field) {
if (System.getSecurityManager() == null) {
field.setAccessible(true);
} else {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
field.setAccessible(true);
return null;
}
});
}
}
private static void computeMissingMessages(String bundleName, Class clazz, Map fieldMap, Field[] fieldArray, boolean isAccessible) {
// iterate over the fields in the class to make sure that there aren't any empty ones
final int MOD_EXPECTED = Modifier.PUBLIC | Modifier.STATIC;
final int MOD_MASK = MOD_EXPECTED | Modifier.FINAL;
final int numFields = fieldArray.length;
for (int i = 0; i < numFields; i++) {
Field field = fieldArray[i];
if ((field.getModifiers() & MOD_MASK) != MOD_EXPECTED)
continue;
//if the field has a a value assigned, there is nothing to do
if (fieldMap.get(field.getName()) == ASSIGNED)
continue;
try {
// Set a value for this empty field. We should never get an exception here because
// we know we have a public static non-final field. If we do get an exception, silently
// log it and continue. This means that the field will (most likely) be un-initialized and
// will fail later in the code and if so then we will see both the NPE and this error.
String value = "NLS missing message: " + field.getName() + " in: " + bundleName; //$NON-NLS-1$ //$NON-NLS-2$
log(SEVERITY_WARNING, value, null);
if (!isAccessible)
makeAccessible(field);
field.set(null, value);
} catch (Exception e) {
log(SEVERITY_ERROR, "Error setting the missing message value for: " + field.getName(), e); //$NON-NLS-1$
}
}
}
/**
* Load the given resource bundle using the specified class loader.
*/
public static void load(final String bundleName, Class clazz) {
final Field[] fieldArray = clazz.getDeclaredFields();
ClassLoader loader = clazz.getClassLoader();
boolean isAccessible = (clazz.getModifiers() & Modifier.PUBLIC) != 0;
//build a map of field names to Field objects
final int len = fieldArray.length;
Map fields = new HashMap(len * 2);
for (int i = 0; i < len; i++)
fields.put(fieldArray[i].getName(), fieldArray[i]);
// search the variants from most specific to most general, since
// the MessagesProperties.put method will mark assigned fields
// to prevent them from being assigned twice
final String[] variants = buildVariants(bundleName);
for (int i = 0; i < variants.length; i++) {
final InputStream input = loader.getResourceAsStream(variants[i]);
if (input == null)
continue;
try {
final MessagesProperties properties = new MessagesProperties(fields, bundleName, isAccessible);
properties.load(input);
} catch (IOException e) {
log(SEVERITY_ERROR, "Error loading " + variants[i], e); //$NON-NLS-1$
} finally {
if (input != null)
try {
input.close();
} catch (IOException e) {
// ignore
}
}
}
computeMissingMessages(bundleName, clazz, fields, fieldArray, isAccessible);
}
private static void log(int severity, String msg, Exception e) {
RuntimeLog.log(new Status(severity, IRuntimeConstants.PI_RUNTIME, 0, msg, e));
}
}