org.eclipse.osgi.util.NLS Maven / Gradle / Ivy
Show all versions of aspectjtools Show documentation
/*******************************************************************************
* Copyright (c) 2005, 2022 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM - Initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.util;
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.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import org.eclipse.osgi.framework.log.FrameworkLog;
import org.eclipse.osgi.framework.log.FrameworkLogEntry;
import org.eclipse.osgi.internal.util.SupplementDebug;
/**
* Common superclass for all message bundle classes. Provides convenience
* methods for manipulating messages.
*
* The #bind
methods perform string substitution and should be considered a
* convenience and not a full substitute replacement for MessageFormat#format
* method calls.
*
*
* Text appearing within curly braces in the given message, will be interpreted
* as a numeric index to the corresponding substitution object in the given array. Calling
* the #bind
methods with text that does not map to an integer will result in an
* {@link IllegalArgumentException}.
*
*
* Text appearing within single quotes is treated as a literal. A single quote is escaped by
* a preceeding single quote.
*
*
* Clients who wish to use the full substitution power of the MessageFormat
class should
* call that class directly and not use these #bind
methods.
*
*
* Clients may subclass this type.
*
*
* @since 3.1
*/
public abstract class NLS {
private static final Object[] EMPTY_ARGS = new Object[0];
private static final String EXTENSION = ".properties"; //$NON-NLS-1$
private static String[] nlSuffixes;
private static final String PROP_WARNINGS = "osgi.nls.warnings"; //$NON-NLS-1$
private static final String IGNORE = "ignore"; //$NON-NLS-1$
private static final boolean ignoreWarnings = AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Boolean run() {
return IGNORE.equals(System.getProperty(PROP_WARNINGS));
}
});
/*
* NOTE do not change the name of this field; it is set by the Framework using reflection
*/
private static FrameworkLog frameworkLog;
static final int SEVERITY_ERROR = 0x04;
static final int SEVERITY_WARNING = 0x02;
/*
* 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();
/**
* Creates a new NLS instance.
*/
protected NLS() {
super();
}
/**
* Bind the given message's substitution locations with the given string value.
*
* @param message the message to be manipulated
* @param binding the object to be inserted into the message
* @return the manipulated String
* @throws IllegalArgumentException if the text appearing within curly braces in the given message does not map to an integer
*/
public static String bind(String message, Object binding) {
return internalBind(message, null, String.valueOf(binding), null);
}
/**
* Bind the given message's substitution locations with the given string values.
*
* @param message the message to be manipulated
* @param binding1 An object to be inserted into the message
* @param binding2 A second object to be inserted into the message
* @return the manipulated String
* @throws IllegalArgumentException if the text appearing within curly braces in the given message does not map to an integer
*/
public static String bind(String message, Object binding1, Object binding2) {
return internalBind(message, null, String.valueOf(binding1), String.valueOf(binding2));
}
/**
* Bind the given message's substitution locations with the given string values.
*
* @param message the message to be manipulated
* @param bindings An array of objects to be inserted into the message
* @return the manipulated String
* @throws IllegalArgumentException if the text appearing within curly braces in the given message does not map to an integer
*/
public static String bind(String message, Object[] bindings) {
return internalBind(message, bindings, null, null);
}
/**
* Initialize the given class with the values from the message properties specified by the
* base name. The base name specifies a fully qualified base name to a message properties file,
* including the package where the message properties file is located. The class loader of the
* specified class will be used to load the message properties resources.
*
* For example, if the locale is set to en_US and org.eclipse.example.nls.messages
* is used as the base name then the following resources will be searched using the class
* loader of the specified class:
*
*
* org/eclipse/example/nls/messages_en_US.properties
* org/eclipse/example/nls/messages_en.properties
* org/eclipse/example/nls/messages.properties
*
*
* @param baseName the base name of a fully qualified message properties file.
* @param clazz the class where the constants will exist
*/
public static void initializeMessages(final String baseName, final Class> clazz) {
if (System.getSecurityManager() == null) {
load(baseName, clazz);
return;
}
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Void run() {
load(baseName, clazz);
return null;
}
});
}
/*
* Perform the string substitution on the given message with the specified args.
* See the class comment for exact details.
*/
private static String internalBind(String message, Object[] args, String argZero, String argOne) {
if (message == null)
return "No message available."; //$NON-NLS-1$
if (args == null || args.length == 0)
args = EMPTY_ARGS;
int length = message.length();
//estimate correct size of string buffer to avoid growth
int bufLen = length + (args.length * 5);
if (argZero != null)
bufLen += argZero.length() - 3;
if (argOne != null)
bufLen += argOne.length() - 3;
StringBuilder buffer = new StringBuilder(bufLen < 0 ? 0 : bufLen);
for (int i = 0; i < length; i++) {
char c = message.charAt(i);
switch (c) {
case '{' :
int index = message.indexOf('}', i);
// if we don't have a matching closing brace then...
if (index == -1) {
buffer.append(c);
break;
}
i++;
if (i >= length) {
buffer.append(c);
break;
}
// look for a substitution
int number = -1;
try {
number = Integer.parseInt(message.substring(i, index));
} catch (NumberFormatException e) {
throw new IllegalArgumentException(e);
}
if (number == 0 && argZero != null)
buffer.append(argZero);
else if (number == 1 && argOne != null)
buffer.append(argOne);
else {
if (number >= args.length || number < 0) {
buffer.append(""); //$NON-NLS-1$
i = index;
break;
}
buffer.append(args[number]);
}
i = index;
break;
case '\'' :
// if a single quote is the last char on the line then skip it
int nextIndex = i + 1;
if (nextIndex >= length) {
buffer.append(c);
break;
}
char next = message.charAt(nextIndex);
// if the next char is another single quote then write out one
if (next == '\'') {
i++;
buffer.append(c);
break;
}
// otherwise we want to read until we get to the next single quote
index = message.indexOf('\'', nextIndex);
// if there are no more in the string, then skip it
if (index == -1) {
buffer.append(c);
break;
}
// otherwise write out the chars inside the quotes
buffer.append(message, nextIndex, index);
i = index;
break;
default :
buffer.append(c);
}
}
return buffer.toString();
}
/*
* 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();
List result = new ArrayList<>(4);
int lastSeparator;
while (true) {
result.add('_' + nl + EXTENSION);
String additional = getAdditionalSuffix(nl);
if (additional != null) {
result.add('_' + additional + EXTENSION);
}
lastSeparator = nl.lastIndexOf('_');
if (lastSeparator == -1)
break;
nl = nl.substring(0, lastSeparator);
}
//add the empty suffix last (most general)
result.add(EXTENSION);
nlSuffixes = 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;
}
/*
* This is a fix due to https://bugs.eclipse.org/bugs/show_bug.cgi?id=579215
* Ideally, this needs to be removed once the Eclipse minimum support moves to Java 17
*/
private static String getAdditionalSuffix(String nl) {
String additional = null;
if (nl != null) {
if ("he".equals(nl)) { //$NON-NLS-1$
additional = "iw"; //$NON-NLS-1$
} else if (nl.startsWith("he_")) { //$NON-NLS-1$
additional = "iw_" + nl.substring(3); //$NON-NLS-1$
}
}
return additional;
}
private static void computeMissingMessages(String bundleName, Class> clazz, Map