alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

What this is

This file is included in the DevDaily.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Other links

The source code

/*
 *                 Sun Public License Notice
 * 
 * The contents of this file are subject to the Sun Public License
 * Version 1.0 (the "License"). You may not use this file except in
 * compliance with the License. A copy of the License is available at
 * http://www.sun.com/
 * 
 * The Original Code is NetBeans. The Initial Developer of the Original
 * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.openide.util;

import java.io.*;
import java.lang.ref.*;
import java.net.URL;
import java.util.*;
import java.util.jar.Attributes;

import org.openide.ErrorManager;

/** Convenience class permitting easy loading of localized resources of various sorts.
* Extends the functionality of the default Java resource support, and interacts
* better with class loaders in a multiple-loader system.
* 

Example usage: *

* package com.mycom;
* public class Foo {
*   // Search for tag Foo_theMessage in /com/mycom/Bundle.properties:
*   private static String theMessage = {@link NbBundle#getMessage(Class, String) NbBundle.getMessage} (Foo.class, "Foo_theMessage");
*   // Might also look in /com/mycom/Bundle_de.properties, etc.
* }
* 
* * @author Petr Hamernik, Jaroslav Tulach, Jesse Glick */ public class NbBundle extends Object { /** * Do not call. * @deprecated There is no reason to instantiate or subclass this class. * All methods in it are static. */ public NbBundle () {} private static final boolean USE_DEBUG_LOADER = Boolean.getBoolean ("org.openide.util.NbBundle.DEBUG"); // NOI18N private static String brandingToken = null; /** Get the current branding token. * @return the branding, or null for none */ public static String getBranding () { return brandingToken; } /** Set the current branding token. * The permitted format, as a regular expression: *
/^[a-z][a-z0-9]*(_[a-z][a-z0-9]*)*$/
* @param bt the new branding, or null to clear * @throws IllegalArgumentException if in an incorrect format */ public static void setBranding (String bt) throws IllegalArgumentException { // [PENDING] check its format here acc. to above regex brandingToken = bt; } /** * Cache of URLs for localized files. * Keeps only weak references to the class loaders. * @see "#9275" */ private static final Map localizedFileCache = new WeakHashMap(); // Map> /** Get a localized file in the default locale with the default class loader. *

Note that use of this call is similar to using the URL protocol nbresloc * (which is in fact implemented using the fuller form of the method). *

The extension may be null, in which case no final dot will be appended. * If it is the empty string, the resource will end in a dot. * @param baseName base name of file, as dot-separated path (e.g. some.dir.File) * @param ext extension of file (or null) * @return URL of matching localized file * @throws MissingResourceException if not found */ public static synchronized URL getLocalizedFile(String baseName, String ext) throws MissingResourceException { return getLocalizedFile(baseName, ext, Locale.getDefault(), getLoader()); } /** Get a localized file with the default class loader. * @param baseName base name of file, as dot-separated path (e.g. some.dir.File) * @param ext extension of file (or null) * @param locale locale of file * @return URL of matching localized file * @throws MissingResourceException if not found */ public static synchronized URL getLocalizedFile(String baseName, String ext, Locale locale) throws MissingResourceException { return getLocalizedFile(baseName, ext, locale, getLoader()); } /** Get a localized file. * @param baseName base name of file, as dot-separated path (e.g. some.dir.File) * @param ext extension of file (or null) * @param locale locale of file * @param loader class loader to use * @return URL of matching localized file * @throws MissingResourceException if not found */ public static synchronized URL getLocalizedFile(String baseName, String ext, Locale locale, ClassLoader loader) throws MissingResourceException { // [PENDING] in the future, could maybe do something neat if // USE_DEBUG_LOADER and ext is "html" or "txt" etc... URL lookup = null; Iterator it = new LocaleIterator (locale); String cachePrefix = "["+Integer.toString(loader.hashCode())+"]"; // NOI18N List cacheCandidates = new ArrayList(10); // List String baseNameSlashes = baseName.replace('.', '/'); Map perLoaderCache = (Map)localizedFileCache.get(loader); if (perLoaderCache == null) { localizedFileCache.put(loader, perLoaderCache = new HashMap()); } // #31008: better use of domain cache priming. // [PENDING] remove this hack in case the domain cache is precomputed URL baseVariant; String path; if (ext != null) { path = baseNameSlashes + '.' + ext; } else { path = baseNameSlashes; } lookup = (URL)perLoaderCache.get(path); if (lookup == null) { baseVariant = loader.getResource(path); } else { // who cares? already in cache anyway baseVariant = null; } while (it.hasNext ()) { String suffix = (String)it.next(); if (ext != null) { path = baseNameSlashes + suffix + '.' + ext; } else { path = baseNameSlashes + suffix; } lookup = (URL)perLoaderCache.get(path); if (lookup != null) break; cacheCandidates.add(path); if (suffix.length() == 0) { lookup = baseVariant; } else { lookup = loader.getResource (path); } if (lookup != null) break; } if (lookup == null) { path = baseName.replace ('.', '/'); if (ext != null) path += '.' + ext; throw new MissingResourceException("Cannot find localized resource " + path + " in " + loader, loader.toString(), path); // NOI18N } else { // Note that this is not 100% accurate. If someone calls gLF on something // with a locale/branding combo such as _brand_ja, and the answer is found // as _ja, then a subsequent call with param _brand will find this _ja // version - since the localizing iterator does *not* have the property that // each subsequent item is more general than the previous. However, this // situation is very unlikely, so consider this close enough. it = cacheCandidates.iterator(); while (it.hasNext()) { perLoaderCache.put(it.next(), lookup); } return lookup; } } /** Find a localized value for a given key and locale. * Scans through a map to find * the most localized match possible. For example: *

    *   findLocalizedValue (hashTable, "keyName", new Locale ("cs_CZ"))
    * 
*

This would return the first non-null value obtained from the following tests: *

    *
  • hashTable.get ("keyName_cs_CZ") *
  • hashTable.get ("keyName_cs") *
  • hashTable.get ("keyName") *
* * @param table mapping from localized strings to objects * @param key the key to look for * @param locale the locale to use * @return the localized object or null if no key matches */ public static Object getLocalizedValue (Map table, String key, Locale locale) { if (table instanceof Attributes) { throw new IllegalArgumentException ("Please do not use a java.util.jar.Attributes for NbBundle.getLocalizedValue " + // NOI18N "without using the special form that works properly with Attributes.Name's as keys."); // NOI18N } Iterator it = new LocaleIterator (locale); while (it.hasNext ()) { String physicalKey = key + (String) it.next (); Object v = table.get (physicalKey); if (v != null) { // ok if (USE_DEBUG_LOADER && (v instanceof String)) { // Not read from a bundle, but still localized somehow: return ((String) v) + " (?:" + physicalKey + ")"; // NOI18N } else { return v; } } } return null; } /** Find a localized value for a given key in the default system locale. * * @param table mapping from localized strings to objects * @param key the key to look for * @return the localized object or null if no key matches * @see #getLocalizedValue(Map,String,Locale) */ public static Object getLocalizedValue (Map table, String key) { return getLocalizedValue (table, key, Locale.getDefault ()); } /** Find a localized value in a JAR manifest. * @param attr the manifest attributes * @param key the key to look for (case-insensitive) * @param locale the locale to use * @return the value if found, else null */ public static String getLocalizedValue (Attributes attr, Attributes.Name key, Locale locale) { return (String)getLocalizedValue (attr2Map (attr), key.toString ().toLowerCase (Locale.US), locale); } /** Find a localized value in a JAR manifest in the default system locale. * @param attr the manifest attributes * @param key the key to look for (case-insensitive) * @return the value if found, else null */ public static String getLocalizedValue (Attributes attr, Attributes.Name key) { // Yes, US locale is intentional! The attribute name may only be ASCII anyway. // It is necessary to lowercase it *as ASCII* as in Turkish 'I' does not go to 'i'! return (String)getLocalizedValue (attr2Map (attr), key.toString ().toLowerCase (Locale.US)); } private static class AttributesMap extends HashMap { private Attributes attrs; public AttributesMap (Attributes attrs) { super (7); this.attrs = attrs; } public Object get (Object obj) { Attributes.Name an; try { an = new Attributes.Name ((String)obj); } catch (IllegalArgumentException iae) { // Robustness, and workaround for reported MRJ locale bug: ErrorManager em = ErrorManager.getDefault(); em.annotate (iae, ErrorManager.WARNING, (String) obj, getMessage (NbBundle.class, "EXC_bad_attributes_name", obj, Locale.getDefault ().toString ()), null, null); em.notify (iae); return null; } return attrs.getValue (an); } } /** Necessary because Attributes implements Map; however this is dangerous! * The keys are Attributes.Name's, not Strings. * Also manifest lookups should not be case-sensitive. * (Though the locale suffix still will be!) */ private static Map attr2Map (Attributes attr) { return new AttributesMap (attr); } // ---- LOADING RESOURCE BUNDLES ---- /** * Get a resource bundle with the default class loader and locale. * Caution: {@link #getBundle(Class)} is generally * safer when used from a module as this method relies on the module's * classloader to currently be part of the system classloader. The * IDE does add enabled modules to this classloader, however calls to * this variant of the method made in {@link org.openide.modules.ModuleInstall#validate}, * or made soon after a module is uninstalled (due to background threads) * could fail unexpectedly. * @param baseName bundle basename * @return the resource bundle * @exception MissingResourceException if the bundle does not exist */ public static final ResourceBundle getBundle(String baseName) throws MissingResourceException { return getBundle(baseName, Locale.getDefault(), getLoader()); } /** Get a resource bundle in the same package as the provided class, * with the default locale and the class' own classloader. * This is the usual style of invocation. * * @param clazz the class to take the package name from * @return the resource bundle * @exception MissingResourceException if the bundle does not exist */ public static ResourceBundle getBundle (Class clazz) throws MissingResourceException { String name = findName (clazz); return getBundle(name, Locale.getDefault(), clazz.getClassLoader()); } /** Finds package name for given class */ private static String findName (Class clazz) { String pref = clazz.getName (); int last = pref.lastIndexOf ('.'); if (last >= 0) { pref = pref.substring (0, last + 1); return pref + "Bundle"; // NOI18N } else { // base package, search for bundle return "Bundle"; // NOI18N } } /** * Get a resource bundle with the default class loader. * @param baseName bundle basename * @param locale the locale to use * @return the resource bundle * @exception MissingResourceException if the bundle does not exist */ public static final ResourceBundle getBundle(String baseName, Locale locale) throws MissingResourceException { return getBundle(baseName, locale, getLoader()); } /** Get a resource bundle the hard way. * @param baseName bundle basename * @param locale the locale to use * @param loader the class loader to use * @return the resource bundle * @exception MissingResourceException if the bundle does not exist */ public static final ResourceBundle getBundle(String baseName, Locale locale, ClassLoader loader) throws MissingResourceException { if (USE_DEBUG_LOADER) loader = DebugLoader.get (loader); // Could more simply use ResourceBundle.getBundle (plus some special logic // with MergedBundle to handle branding) instead of manually finding bundles. // However this code is faster and has some other desirable properties. // Cf. #13847. ResourceBundle b = getBundleFast(baseName, locale, loader); if (b != null) { return b; } else { MissingResourceException e = new MissingResourceException("No such bundle " + baseName, baseName, null); // NOI18N if (Lookup.getDefault().lookup(ClassLoader.class) == null) { ErrorManager.getDefault().annotate(e, ErrorManager.UNKNOWN, "Class loader not yet initialized in lookup", null, null, null); // NOI18N } else { ErrorManager.getDefault().annotate(e, ErrorManager.UNKNOWN, "Offending classloader: " + loader, null, null, null); // NOI18N } throw e; } } /** * Cache of resource bundles. */ private static final Map bundleCache = new WeakHashMap(); // Map>> /** * Get a resource bundle by name. * Like {@link ResourceBundle#getBundle(String,Locale,ClassLoader)} but faster, * and also understands branding. * First looks for .properties-based bundles, then .class-based. * @param name the base name of the bundle, e.g. org.netbeans.modules.foo.Bundle * @param locale the locale to use * @param loader a class loader to search in * @return a resource bundle (locale- and branding-merged), or null if not found */ private static ResourceBundle getBundleFast(String name, Locale locale, ClassLoader loader) { Map m; synchronized (bundleCache) { m = (Map)bundleCache.get(loader); // Map> if (m == null) { bundleCache.put(loader, m = new HashMap()); } } //A minor optimization to cut down on StringBuffer allocations - OptimizeIt //showed the commented out code below was a major source of them. This //just does the same thing with a char array - Tim String localeStr = locale.toString(); char[] k = new char[name.length() + (brandingToken != null ? brandingToken.length() : 1) + 2 + localeStr.length()]; name.getChars(0, name.length(), k, 0); k[name.length()] = '/'; //NOI18N int pos = name.length()+1; if (brandingToken == null) { k[pos]='-'; //NOI18N pos++; } else { brandingToken.getChars(0, brandingToken.length(), k, pos); pos += brandingToken.length(); } k[pos] = '/'; //NOI18N pos++; localeStr.getChars(0, localeStr.length(), k, pos); String key = new String(k); /* String key = name + '/' + (brandingToken != null ? brandingToken : "-") + '/' + locale; // NOI18N */ synchronized (m) { Object o = m.get(key); ResourceBundle b = (o != null) ? (ResourceBundle)((Reference)o).get() : null; if (b != null) { return b; } else { b = loadBundle(name, locale, loader); if (b != null) { m.put(key, new TimedSoftReference(b, m, key)); } else { // Used to cache misses as well, to make the negative test faster. // However this caused problems: see #31578. } return b; } } } /** * Load a resource bundle (without caching). * @param name the base name of the bundle, e.g. org.netbeans.modules.foo.Bundle * @param locale the locale to use * @param loader a class loader to search in * @return a resource bundle (locale- and branding-merged), or null if not found */ private static ResourceBundle loadBundle(String name, Locale locale, ClassLoader loader) { String sname = name.replace('.', '/'); Iterator it = new LocaleIterator(locale); LinkedList l = new LinkedList(); while (it.hasNext()) { l.addFirst(it.next()); } it = l.iterator(); Properties p = new Properties(); boolean first = true; while (it.hasNext()) { String res = sname + (String)it.next() + ".properties"; InputStream is = loader.getResourceAsStream(res); if (is != null) { //System.err.println("Loading " + res); try { try { p.load(is); } finally { is.close(); } } catch (IOException e) { ErrorManager.getDefault().notify(ErrorManager.WARNING, e); return null; } } else if (first) { // No base *.properties. Try *.class. // Note that you may not mix *.properties w/ *.class this way. return loadBundleClass(name, sname, locale, l, loader); } first = false; } return new PBundle(p, locale); } /** * A resource bundle based on .properties files (or any map). */ private static final class PBundle extends ResourceBundle { private final Map m; // Map private final Locale locale; /** * Create a new bundle based on a map. * @param m a map from resources keys to values (typically both strings) * @param locale the locale it represents (informational) */ public PBundle(Map m, Locale locale) { this.m = m; this.locale = locale; } public Enumeration getKeys() { return Collections.enumeration(m.keySet()); } protected Object handleGetObject(String key) { return m.get(key); } public Locale getLocale() { return locale; } } /** * Load a class-based resource bundle. * @param name the base name of the bundle, e.g. org.netbeans.modules.foo.Bundle * @param sname the name with slashes, e.g. org/netbeans/modules/foo/Bundle * @param locale the locale to use * @param suffixes a list of suffixes to apply to the bundle name, in increasing order of specificity * @param loader a class loader to search in * @return a resource bundle (merged according to the suffixes), or null if not found */ private static ResourceBundle loadBundleClass(String name, String sname, Locale locale, List suffixes, ClassLoader l) { if (l.getResource(sname + ".class") == null) { // NOI18N // No chance - no base bundle. Don't waste time catching CNFE. return null; } ResourceBundle master = null; Iterator it = suffixes.iterator(); while (it.hasNext()) { try { Class c = Class.forName(name + (String)it.next(), true, l); ResourceBundle b = (ResourceBundle)c.newInstance(); if (master == null) { master = b; } else { master = new MergedBundle(locale, b, master); } } catch (ClassNotFoundException cnfe) { // fine - ignore } catch (Exception e) { ErrorManager.getDefault().notify(ErrorManager.WARNING, e); } catch (LinkageError e) { ErrorManager.getDefault().notify(ErrorManager.WARNING, e); } } return master; } /** Special resource bundle which delegates to two others. * Ideally could just set the parent on the first, but this is protected, so... */ private static class MergedBundle extends ResourceBundle { private Locale loc; private ResourceBundle sub1, sub2; /** * Create a new bundle delegating to two others. * @param loc the locale it represents (informational) * @param sub1 one delegate (taking precedence over the other in case of overlap) * @param sub2 the other (weaker) delegate */ public MergedBundle (Locale loc, ResourceBundle sub1, ResourceBundle sub2) { this.loc = loc; this.sub1 = sub1; this.sub2 = sub2; } public Locale getLocale () { return loc; } public Enumeration getKeys () { return Enumerations.removeDuplicates ( Enumerations.concat (sub1.getKeys (), sub2.getKeys ()) ); } protected Object handleGetObject (String key) throws MissingResourceException { try { return sub1.getObject (key); } catch (MissingResourceException mre) { // Ignore exception, and... return sub2.getObject (key); } } } // // Helper methods to simplify localization of messages // /** Finds a localized string in a bundle. * @param clazz the class to use to locate the bundle * @param resName name of the resource to look for * @return the string associated with the resource * @throws MissingResourceException if either the bundle or the string cannot be found */ public static String getMessage (Class clazz, String resName) throws MissingResourceException { return getBundle (clazz).getString(resName); } /** Finds a localized string in a bundle and formats the message * by passing requested parameters. * * @param clazz the class to use to locate the bundle * @param resName name of the resource to look for * @param param1 the argument to use when formatting the message * @return the string associated with the resource * @throws MissingResourceException if either the bundle or the string cannot be found * @see java.text.MessageFormat#format(String,Object[]) */ public static String getMessage ( Class clazz, String resName, Object param1 ) throws MissingResourceException { return getMessage (clazz, resName, new Object[] { param1 }); } /** Finds a localized string in a bundle and formats the message * by passing requested parameters. * * @param clazz the class to use to locate the bundle * @param resName name of the resource to look for * @param param1 the argument to use when formatting the message * @param param2 the second argument to use for formatting * @return the string associated with the resource * @throws MissingResourceException if either the bundle or the string cannot be found * @see java.text.MessageFormat#format(String,Object[]) */ public static String getMessage ( Class clazz, String resName, Object param1, Object param2 ) throws MissingResourceException { return getMessage (clazz, resName, new Object[] { param1, param2 }); } /** Finds a localized string in a bundle and formats the message * by passing requested parameters. * * @param clazz the class to use to locate the bundle * @param resName name of the resource to look for * @param param1 the argument to use when formatting the message * @param param2 the second argument to use for formatting * @param param3 the third argument to use for formatting * @return the string associated with the resource * @throws MissingResourceException if either the bundle or the string cannot be found * @see java.text.MessageFormat#format(String,Object[]) */ public static String getMessage ( Class clazz, String resName, Object param1, Object param2, Object param3 ) throws MissingResourceException { return getMessage (clazz, resName, new Object[] { param1, param2, param3 }); } /** Finds a localized string in a bundle and formats the message * by passing requested parameters. * * @param clazz the class to use to locate the bundle * @param resName name of the resource to look for * @param arr array of parameters to use for formatting the message * @return the string associated with the resource * @throws MissingResourceException if either the bundle or the string cannot be found * @see java.text.MessageFormat#format(String,Object[]) */ public static String getMessage ( Class clazz, String resName, Object[] arr ) throws MissingResourceException { return java.text.MessageFormat.format ( getMessage (clazz, resName), arr ); } /** @return default class loader which is used, when we don't have * any other class loader. (in function getBundle(String), getLocalizedFile(String), * and so on... */ private static ClassLoader getLoader() { ClassLoader c = (ClassLoader)Lookup.getDefault ().lookup (ClassLoader.class); return c != null ? c : ClassLoader.getSystemClassLoader (); } /** Get a list of all suffixes used to search for localized resources. * Based on the default locale and branding, returns the list of suffixes * which various NbBundle methods use as the search order. * For example, you might get a sequence such as: *
    *
  1. "_branding_de" *
  2. "_branding" *
  3. "_de" *
  4. "" *
* @return a read-only iterator of type String * @since 1.1.5 */ public static Iterator getLocalizingSuffixes () { return new LocaleIterator (Locale.getDefault ()); } /** This class (enumeration) gives all localized sufixes using nextElement * method. It goes through given Locale and continues through Locale.getDefault() * Example 1: * Locale.getDefault().toString() -> "_en_US" * you call new LocaleIterator(new Locale("cs", "CZ")); * ==> You will gets: "_cs_CZ", "_cs", "", "_en_US", "_en" * * Example 2: * Locale.getDefault().toString() -> "_cs_CZ" * you call new LocaleIterator(new Locale("cs", "CZ")); * ==> You will gets: "_cs_CZ", "_cs", "" * * If there is a branding token in effect, you will get it too as an extra * prefix, taking precedence, e.g. for the token "f4jce": * * "_f4jce_cs_CZ", "_f4jce_cs", "_f4jce", "_f4jce_en_US", "_f4jce_en", "_cs_CZ", "_cs", "", "_en_US", "_en" * * Branding tokens with underscores are broken apart naturally: so e.g. * branding "f4j_ce" looks first for "f4j_ce" branding, then "f4j" branding, then none. */ private static class LocaleIterator extends Object implements Iterator { /** this flag means, if default locale is in progress */ private boolean defaultInProgress = false; /** this flag means, if empty sufix was exported yet */ private boolean empty = false; /** current locale, and initial locale */ private Locale locale, initLocale; /** current sufix which will be returned in next calling nextElement */ private String current; /** the branding string in use */ private String branding; /** Creates new LocaleIterator for given locale. * @param locale given Locale */ public LocaleIterator(Locale locale) { this.locale = this.initLocale = locale; if (locale.equals(Locale.getDefault())) { defaultInProgress = true; } current = '_' + locale.toString(); if (brandingToken == null) branding = null; else branding = "_" + brandingToken; // NOI18N //System.err.println("Constructed: " + this); } /** @return next sufix. * @exception NoSuchElementException if there is no more locale sufix. */ public Object next () throws NoSuchElementException { if (current == null) throw new NoSuchElementException(); final String ret; if (branding == null) { ret = current; } else { ret = branding + current; } int lastUnderbar = current.lastIndexOf('_'); if (lastUnderbar == 0) { if (empty) reset (); else { current = ""; // NOI18N empty = true; } } else { if (lastUnderbar == -1) { if (defaultInProgress) reset (); else { // [PENDING] stuff with trying the default locale // after the real one does not actually seem to work... locale = Locale.getDefault(); current = '_' + locale.toString(); defaultInProgress = true; } } else { current = current.substring(0, lastUnderbar); } } //System.err.println("Returning: `" + ret + "' from: " + this); return ret; } /** Finish a series. * If there was a branding prefix, restart without that prefix * (or with a shorter prefix); else finish. */ private void reset () { if (branding != null) { current = '_' + initLocale.toString (); int idx = branding.lastIndexOf ('_'); if (idx == 0) branding = null; else branding = branding.substring (0, idx); empty = false; } else { current = null; } } /** Tests if there is any sufix.*/ public boolean hasNext () { return (current != null); } public void remove () throws UnsupportedOperationException { throw new UnsupportedOperationException (); } } // end of LocaleIterator /** * Do not use. * @param loaderFinder ignored * @deprecated Useless. */ public static void setClassLoaderFinder (ClassLoaderFinder loaderFinder) { throw new Error (); } /** * Do not use. * @deprecated Useless. */ public static interface ClassLoaderFinder { /** * Do not use. * @return nothing * @deprecated Useless. */ public ClassLoader find (); } /** Classloader whose special trick is inserting debug information * into any *.properties files it loads. */ private static final class DebugLoader extends ClassLoader { /** global bundle index, each loaded bundle gets its own */ private static int count = 0; /** indices of known bundles; needed since DebugLoader's can be collected * when softly reachable, but this should be transparent to the user */ private static final Map knownIDs = new HashMap (); // Map /** cache of existing debug loaders for regular loaders */ private static final Map existing = new WeakHashMap (); // Map> private static int getID (String name) { synchronized (knownIDs) { Integer i = (Integer) knownIDs.get (name); if (i == null) { i = new Integer (++count); knownIDs.put (name, i); System.err.println ("NbBundle trace: #" + i + " = " + name); // NOI18N } return i.intValue (); } } public static ClassLoader get (ClassLoader normal) { //System.err.println("Lookup: normal=" + normal); synchronized (existing) { Reference r = (Reference) existing.get (normal); if (r != null) { ClassLoader dl = (ClassLoader) r.get (); if (dl != null) { //System.err.println("\tcache hit"); return dl; } else { //System.err.println("\tcollected ref"); } } else { //System.err.println("\tnot in cache"); } ClassLoader dl = new DebugLoader (normal); existing.put (normal, new WeakReference (dl)); return dl; } } private DebugLoader (ClassLoader cl) { super (cl); //System.err.println ("new DebugLoader: cl=" + cl); } public InputStream getResourceAsStream (String name) { InputStream base = super.getResourceAsStream (name); if (base == null) return null; if (name.endsWith (".properties")) { // NOI18N int id = getID (name); //System.err.println ("\tthis=" + this + " parent=" + getParent ()); boolean loc = name.indexOf ("/Bundle.") != -1 || name.indexOf ("/Bundle_") != -1; // NOI18N return new DebugInputStream (base, id, loc); } else { return base; } } // [PENDING] getResource not overridden; but ResourceBundle uses getResourceAsStream anyhow /** Wrapper input stream which parses the text as it goes and adds annotations. * Resource-bundle values are annotated with their current line number and also * the supplied it, so e.g. if in the original input stream on line 50 we have: * somekey=somevalue * so in the wrapper stream (id 123) this line will read: * somekey=somevalue (123:50) * Since you see on stderr what #123 is, you can then pinpoint where any bundle key * originally came from, assuming NbBundle loaded it from a *.properties file. * @see {@link Properties#load} for details on the syntax of *.properties files. */ private static final class DebugInputStream extends InputStream { private final InputStream base; private final int id; private final boolean localizable; /** current line number */ private int line = 0; /** state transition diagram constants */ private static final int WAITING_FOR_KEY = 0, IN_COMMENT = 1, IN_KEY = 2, IN_KEY_BACKSLASH = 3, AFTER_KEY = 4, WAITING_FOR_VALUE = 5, IN_VALUE = 6, IN_VALUE_BACKSLASH = 7; /** current state in state machine */ private int state = WAITING_FOR_KEY; /** if true, the last char was a CR, waiting to see if we get a NL too */ private boolean twixtCrAndNl = false; /** if non-null, a string to serve up before continuing (length must be > 0) */ private String toInsert = null; /** if true, the next value encountered should be localizable if normally it would not be, or vice-versa */ private boolean reverseLocalizable = false; /** text of currently read comment, including leading comment character */ private StringBuffer lastComment = null; /** Create a new InputStream which will annotate resource bundles. * Bundles named Bundle*.properties will be treated as localizable by default, * and so annotated; other bundles will be treated as nonlocalizable and not annotated. * Messages can be individually marked as localizable or not to override this default, * in accordance with some I18N conventions for NetBeans. * @param base the unannotated stream * @param id an identifying number to use in annotations * @param localizable if true, this bundle is expected to be localizable * @see http://www.netbeans.org/i18n/ */ public DebugInputStream (InputStream base, int id, boolean localizable) { this.base = base; this.id = id; this.localizable = localizable; } public int read () throws IOException { //try{ if (toInsert != null) { char result = toInsert.charAt (0); if (toInsert.length () > 1) { toInsert = toInsert.substring (1); } else { toInsert = null; } return result; } int next = base.read (); if (next == '\n') { twixtCrAndNl = false; line++; } else if (next == '\r') { if (twixtCrAndNl) { line++; } else { twixtCrAndNl = true; } } else { twixtCrAndNl = false; } switch (state) { case WAITING_FOR_KEY: switch (next) { case '#': case '!': state = IN_COMMENT; lastComment = new StringBuffer (); lastComment.append ((char) next); return next; case ' ': case '\t': case '\n': case '\r': case -1: return next; case '\\': state = IN_KEY_BACKSLASH; return next; default: state = IN_KEY; return next; } case IN_COMMENT: switch (next) { case '\n': case '\r': String comment = lastComment.toString (); lastComment = null; if (localizable && comment.equals ("#NOI18N")) { // NOI18N reverseLocalizable = true; } else if (localizable && comment.equals ("#PARTNOI18N")) { // NOI18N System.err.println ("NbBundle WARNING (" + id + ":" + line + "): #PARTNOI18N encountered, will not annotate I18N parts"); // NOI18N reverseLocalizable = true; } else if (! localizable && comment.equals ("#I18N")) { // NOI18N reverseLocalizable = true; } else if (! localizable && comment.equals ("#PARTI18N")) { // NOI18N System.err.println ("NbBundle WARNING (" + id + ":" + line + "): #PARTI18N encountered, will not annotate I18N parts"); // NOI18N reverseLocalizable = false; } else if ((localizable && (comment.equals ("#I18N") || comment.equals ("#PARTI18N"))) || // NOI18N (! localizable && (comment.equals ("#NOI18N") || comment.equals ("#PARTNOI18N")))) { // NOI18N System.err.println ("NbBundle WARNING (" + id + ":" + line + "): incongruous comment " + comment + " found for bundle"); // NOI18N reverseLocalizable = false; } state = WAITING_FOR_KEY; return next; default: lastComment.append ((char) next); return next; } case IN_KEY: switch (next) { case '\\': state = IN_KEY_BACKSLASH; return next; case ' ': case '\t': state = AFTER_KEY; return next; case '=': case ':': state = WAITING_FOR_VALUE; return next; case '\r': case '\n': state = WAITING_FOR_KEY; return next; default: return next; } case IN_KEY_BACKSLASH: state = IN_KEY; return next; case AFTER_KEY: switch (next) { case '=': case ':': state = WAITING_FOR_VALUE; return next; case '\r': case '\n': state = WAITING_FOR_KEY; return next; default: return next; } case WAITING_FOR_VALUE: switch (next) { case '\r': case '\n': state = WAITING_FOR_KEY; return next; case ' ': case '\t': return next; case '\\': state = IN_VALUE_BACKSLASH; return next; default: state = IN_VALUE; return next; } case IN_VALUE: switch (next) { case '\\': // Gloss over distinction between simple escapes and \u1234, which is not important for us. // Also no need to deal specially with continuation lines; for us, there is an escaped // newline, after which will be more value, and that is all that is important. state = IN_VALUE_BACKSLASH; return next; case '\n': case '\r': // End of value. This is the tricky part. boolean revLoc = reverseLocalizable; reverseLocalizable = false; state = WAITING_FOR_KEY; // XXX don't annotate keys ending in _Mnemonic if (localizable ^ revLoc) { // This value is intended to be localizable. Annotate it. toInsert = "(" + id + ":" + line + ")" + new Character ((char) next); // NOI18N // Now return the space before the rest of the string explicitly. return ' '; } else { // This is not supposed to be a localizable value, leave it alone. return next; } default: return next; } case IN_VALUE_BACKSLASH: state = IN_VALUE; return next; default: throw new IOException ("should never happen"); // NOI18N } } //catch(IOException ioe) {ioe.printStackTrace(); throw ioe;} //catch(RuntimeException re) {re.printStackTrace(); throw re;} //} /** For testing correctness of the transformation. Run: * java org.openide.util.NbBundle$DebugLoader$DebugInputStream true < test.properties * (The argument says whether to treat the input as localizable by default.) */ public static void main (String[] args) throws Exception { if (args.length != 1) throw new Exception (); boolean loc = Boolean.valueOf (args[0]).booleanValue (); DebugInputStream dis = new DebugInputStream (System.in, 123, loc); int c; while ((c = dis.read ()) != -1) { System.out.write (c); } } } } }
... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.