ResourceBundleSupport.java Source code

Java tutorial

Introduction

Here is the source code for ResourceBundleSupport.java

Source

/* 
 * JCommon : a free general purpose class library for the Java(tm) platform
 * 
 *
 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
 *
 * Project Info:  http://www.jfree.org/jcommon/index.html
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
 * in the United States and other countries.]
 *
 * ---------------------
 * ReadOnlyIterator.java
 * ---------------------
 * (C)opyright 2003-2008, by Thomas Morgner and Contributors.
 *
 * Original Author:  Thomas Morgner;
 * Contributor(s):   David Gilbert (for Object Refinery Limited);
 *
 * $Id: ResourceBundleSupport.java,v 1.12 2008/12/18 09:57:32 mungady Exp $
 *
 * Changes
 * -------
 * 18-Dec-2008 : Use ResourceBundleWrapper - see JFreeChart patch 1607918 by
 *               Jess Thrysoee (DG);
 *
 */

import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.StringTokenizer;
import java.util.TreeMap;
import java.util.TreeSet;

import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JMenu;
import javax.swing.KeyStroke;

import sun.rmi.runtime.Log;

/**
 * An utility class to ease up using property-file resource bundles.
 * <p/>
 * The class support references within the resource bundle set to minimize the
 * occurence of duplicate keys. References are given in the format:
 * <pre>
 * a.key.name=@referenced.key
 * </pre>
 * <p/>
 * A lookup to a key in an other resource bundle should be written by
 * <pre>
 * a.key.name=@@resourcebundle_name@referenced.key
 * </pre>
 *
 * @author Thomas Morgner
 */
public class ResourceBundleSupport {
    /**
     * The resource bundle that will be used for local lookups.
     */
    private ResourceBundle resources;

    /**
     * A cache for string values, as looking up the cache is faster than looking
     * up the value in the bundle.
     */
    private TreeMap cache;
    /**
     * The current lookup path when performing non local lookups. This prevents
     * infinite loops during such lookups.
     */
    private TreeSet lookupPath;

    /**
     * The name of the local resource bundle.
     */
    private String resourceBase;

    /**
     * The locale for this bundle.
     */
    private Locale locale;

    /**
     * Creates a new instance.
     *
     * @param locale  the locale.
     * @param baseName the base name of the resource bundle, a fully qualified
     *                 class name
     */
    public ResourceBundleSupport(final Locale locale, final String baseName) {
        this(locale, ResourceBundleWrapper.getBundle(baseName, locale), baseName);
    }

    /**
     * Creates a new instance.
     *
     * @param locale         the locale for which this resource bundle is
     *                       created.
     * @param resourceBundle the resourcebundle
     * @param baseName       the base name of the resource bundle, a fully
     *                       qualified class name
     */
    protected ResourceBundleSupport(final Locale locale, final ResourceBundle resourceBundle,
            final String baseName) {
        if (locale == null) {
            throw new NullPointerException("Locale must not be null");
        }
        if (resourceBundle == null) {
            throw new NullPointerException("Resources must not be null");
        }
        if (baseName == null) {
            throw new NullPointerException("BaseName must not be null");
        }
        this.locale = locale;
        this.resources = resourceBundle;
        this.resourceBase = baseName;
        this.cache = new TreeMap();
        this.lookupPath = new TreeSet();
    }

    /**
     * Creates a new instance.
     *
     * @param locale         the locale for which the resource bundle is
     *                       created.
     * @param resourceBundle the resourcebundle
     */
    public ResourceBundleSupport(final Locale locale, final ResourceBundle resourceBundle) {
        this(locale, resourceBundle, resourceBundle.toString());
    }

    /**
     * Creates a new instance.
     *
     * @param baseName the base name of the resource bundle, a fully qualified
     *                 class name
     */
    public ResourceBundleSupport(final String baseName) {
        this(Locale.getDefault(), ResourceBundleWrapper.getBundle(baseName), baseName);
    }

    /**
     * Creates a new instance.
     *
     * @param resourceBundle the resourcebundle
     * @param baseName       the base name of the resource bundle, a fully
     *                       qualified class name
     */
    protected ResourceBundleSupport(final ResourceBundle resourceBundle, final String baseName) {
        this(Locale.getDefault(), resourceBundle, baseName);
    }

    /**
     * Creates a new instance.
     *
     * @param resourceBundle the resourcebundle
     */
    public ResourceBundleSupport(final ResourceBundle resourceBundle) {
        this(Locale.getDefault(), resourceBundle, resourceBundle.toString());
    }

    /**
     * The base name of the resource bundle.
     *
     * @return the resource bundle's name.
     */
    protected final String getResourceBase() {
        return this.resourceBase;
    }

    /**
     * Gets a string for the given key from this resource bundle or one of its
     * parents. If the key is a link, the link is resolved and the referenced
     * string is returned instead.
     *
     * @param key the key for the desired string
     * @return the string for the given key
     * @throws NullPointerException     if <code>key</code> is <code>null</code>
     * @throws MissingResourceException if no object for the given key can be
     *                                  found
     * @throws ClassCastException       if the object found for the given key is
     *                                  not a string
     */
    public synchronized String getString(final String key) {
        final String retval = (String) this.cache.get(key);
        if (retval != null) {
            return retval;
        }
        this.lookupPath.clear();
        return internalGetString(key);
    }

    /**
     * Performs the lookup for the given key. If the key points to a link the
     * link is resolved and that key is looked up instead.
     *
     * @param key the key for the string
     * @return the string for the given key
     */
    protected String internalGetString(final String key) {
        if (this.lookupPath.contains(key)) {
            throw new MissingResourceException("InfiniteLoop in resource lookup", getResourceBase(),
                    this.lookupPath.toString());
        }
        final String fromResBundle = this.resources.getString(key);
        if (fromResBundle.startsWith("@@")) {
            // global forward ...
            final int idx = fromResBundle.indexOf('@', 2);
            if (idx == -1) {
                throw new MissingResourceException("Invalid format for global lookup key.", getResourceBase(), key);
            }
            try {
                final ResourceBundle res = ResourceBundleWrapper.getBundle(fromResBundle.substring(2, idx));
                return res.getString(fromResBundle.substring(idx + 1));
            } catch (Exception e) {
                System.out.println("Error during global lookup:" + e);
                throw new MissingResourceException("Error during global lookup", getResourceBase(), key);
            }
        } else if (fromResBundle.startsWith("@")) {
            // local forward ...
            final String newKey = fromResBundle.substring(1);
            this.lookupPath.add(key);
            final String retval = internalGetString(newKey);

            this.cache.put(key, retval);
            return retval;
        } else {
            this.cache.put(key, fromResBundle);
            return fromResBundle;
        }
    }

    /**
     * Returns an scaled icon suitable for buttons or menus.
     *
     * @param key   the name of the resource bundle key
     * @param large true, if the image should be scaled to 24x24, or false for
     *              16x16
     * @return the icon.
     */
    public Icon getIcon(final String key, final boolean large) {
        final String name = getString(key);
        return createIcon(name, true, large);
    }

    /**
     * Returns an unscaled icon.
     *
     * @param key the name of the resource bundle key
     * @return the icon.
     */
    public Icon getIcon(final String key) {
        final String name = getString(key);
        return createIcon(name, false, false);
    }

    /**
     * Returns the mnemonic stored at the given resourcebundle key. The mnemonic
     * should be either the symbolic name of one of the KeyEvent.VK_* constants
     * (without the 'VK_') or the character for that key.
     * <p/>
     * For the enter key, the resource bundle would therefore either contain
     * "ENTER" or "\n".
     * <pre>
     * a.resourcebundle.key=ENTER
     * an.other.resourcebundle.key=\n
     * </pre>
     *
     * @param key the resourcebundle key
     * @return the mnemonic
     */
    public Integer getMnemonic(final String key) {
        final String name = getString(key);
        return createMnemonic(name);
    }

    /**
     * Returns an optional mnemonic.
     *
     * @param key  the key.
     *
     * @return The mnemonic.
     */
    public Integer getOptionalMnemonic(final String key) {
        final String name = getString(key);
        if (name != null && name.length() > 0) {
            return createMnemonic(name);
        }
        return null;
    }

    /**
     * Returns the keystroke stored at the given resourcebundle key.
     * <p/>
     * The keystroke will be composed of a simple key press and the plattform's
     * MenuKeyMask.
     * <p/>
     * The keystrokes character key should be either the symbolic name of one of
     * the KeyEvent.VK_* constants or the character for that key.
     * <p/>
     * For the 'A' key, the resource bundle would therefore either contain
     * "VK_A" or "a".
     * <pre>
     * a.resourcebundle.key=VK_A
     * an.other.resourcebundle.key=a
     * </pre>
     *
     * @param key the resourcebundle key
     * @return the mnemonic
     * @see Toolkit#getMenuShortcutKeyMask()
     */
    public KeyStroke getKeyStroke(final String key) {
        return getKeyStroke(key, getMenuKeyMask());
    }

    /**
     * Returns an optional key stroke.
     *
     * @param key  the key.
     *
     * @return The key stroke.
     */
    public KeyStroke getOptionalKeyStroke(final String key) {
        return getOptionalKeyStroke(key, getMenuKeyMask());
    }

    /**
     * Returns the keystroke stored at the given resourcebundle key.
     * <p/>
     * The keystroke will be composed of a simple key press and the given
     * KeyMask. If the KeyMask is zero, a plain Keystroke is returned.
     * <p/>
     * The keystrokes character key should be either the symbolic name of one of
     * the KeyEvent.VK_* constants or the character for that key.
     * <p/>
     * For the 'A' key, the resource bundle would therefore either contain
     * "VK_A" or "a".
     * <pre>
     * a.resourcebundle.key=VK_A
     * an.other.resourcebundle.key=a
     * </pre>
     *
     * @param key the resourcebundle key.
     * @param mask  the mask.
     *
     * @return the mnemonic
     * @see Toolkit#getMenuShortcutKeyMask()
     */
    public KeyStroke getKeyStroke(final String key, final int mask) {
        final String name = getString(key);
        return KeyStroke.getKeyStroke(createMnemonic(name).intValue(), mask);
    }

    /**
     * Returns an optional key stroke.
     *
     * @param key  the key.
     * @param mask  the mask.
     *
     * @return The key stroke.
     */
    public KeyStroke getOptionalKeyStroke(final String key, final int mask) {
        final String name = getString(key);

        if (name != null && name.length() > 0) {
            return KeyStroke.getKeyStroke(createMnemonic(name).intValue(), mask);
        }
        return null;
    }

    /**
     * Returns a JMenu created from a resource bundle definition.
     * <p/>
     * The menu definition consists of two keys, the name of the menu and the
     * mnemonic for that menu. Both keys share a common prefix, which is
     * extended by ".name" for the name of the menu and ".mnemonic" for the
     * mnemonic.
     * <p/>
     * <pre>
     * # define the file menu
     * menu.file.name=File
     * menu.file.mnemonic=F
     * </pre>
     * The menu definition above can be used to create the menu by calling
     * <code>createMenu ("menu.file")</code>.
     *
     * @param keyPrefix the common prefix for that menu
     * @return the created menu
     */
    public JMenu createMenu(final String keyPrefix) {
        final JMenu retval = new JMenu();
        retval.setText(getString(keyPrefix + ".name"));
        retval.setMnemonic(getMnemonic(keyPrefix + ".mnemonic").intValue());
        return retval;
    }

    /**
     * Returns a URL pointing to a resource located in the classpath. The
     * resource is looked up using the given key.
     * <p/>
     * Example: The load a file named 'logo.gif' which is stored in a java
     * package named 'org.jfree.resources':
     * <pre>
     * mainmenu.logo=org/jfree/resources/logo.gif
     * </pre>
     * The URL for that file can be queried with: <code>getResource("mainmenu.logo");</code>.
     *
     * @param key the key for the resource
     * @return the resource URL
     */
    public URL getResourceURL(final String key) {
        final String name = getString(key);
        final URL in = ObjectUtilities.getResource(name, ResourceBundleSupport.class);
        if (in == null) {
            System.out.println("Unable to find file in the class path: " + name + "; key=" + key);
        }
        return in;
    }

    /**
     * Attempts to load an image from classpath. If this fails, an empty image
     * icon is returned.
     *
     * @param resourceName the name of the image. The name should be a global
     *                     resource name.
     * @param scale        true, if the image should be scaled, false otherwise
     * @param large        true, if the image should be scaled to 24x24, or
     *                     false for 16x16
     * @return the image icon.
     */
    private ImageIcon createIcon(final String resourceName, final boolean scale, final boolean large) {
        final URL in = ObjectUtilities.getResource(resourceName, ResourceBundleSupport.class);
        ;
        if (in == null) {
            System.out.println("Unable to find file in the class path: " + resourceName);
            return new ImageIcon(createTransparentImage(1, 1));
        }
        final Image img = Toolkit.getDefaultToolkit().createImage(in);
        if (img == null) {
            System.out.println("Unable to instantiate the image: " + resourceName);
            return new ImageIcon(createTransparentImage(1, 1));
        }
        if (scale) {
            if (large) {
                return new ImageIcon(img.getScaledInstance(24, 24, Image.SCALE_SMOOTH));
            }
            return new ImageIcon(img.getScaledInstance(16, 16, Image.SCALE_SMOOTH));
        }
        return new ImageIcon(img);
    }

    /**
     * Creates the Mnemonic from the given String. The String consists of the
     * name of the VK constants of the class KeyEvent without VK_*.
     *
     * @param keyString the string
     * @return the mnemonic as integer
     */
    private Integer createMnemonic(final String keyString) {
        if (keyString == null) {
            throw new NullPointerException("Key is null.");
        }
        if (keyString.length() == 0) {
            throw new IllegalArgumentException("Key is empty.");
        }
        int character = keyString.charAt(0);
        if (keyString.startsWith("VK_")) {
            try {
                final Field f = KeyEvent.class.getField(keyString);
                final Integer keyCode = (Integer) f.get(null);
                character = keyCode.intValue();
            } catch (Exception nsfe) {
                // ignore the exception ...
            }
        }
        return new Integer(character);
    }

    /**
     * Returns the plattforms default menu shortcut keymask.
     *
     * @return the default key mask.
     */
    private int getMenuKeyMask() {
        try {
            return Toolkit.getDefaultToolkit().getMenuShortcutKeyMask();
        } catch (UnsupportedOperationException he) {
            // headless exception extends UnsupportedOperation exception,
            // but the HeadlessException is not defined in older JDKs...
            return InputEvent.CTRL_MASK;
        }
    }

    /**
     * Creates a transparent image.  These can be used for aligning menu items.
     *
     * @param width  the width.
     * @param height the height.
     * @return the created transparent image.
     */
    private BufferedImage createTransparentImage(final int width, final int height) {
        final BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
        final int[] data = img.getRGB(0, 0, width, height, null, 0, width);
        Arrays.fill(data, 0x00000000);
        img.setRGB(0, 0, width, height, data, 0, width);
        return img;
    }

    /**
     * Creates a transparent icon. The Icon can be used for aligning menu
     * items.
     *
     * @param width  the width of the new icon
     * @param height the height of the new icon
     * @return the created transparent icon.
     */
    public Icon createTransparentIcon(final int width, final int height) {
        return new ImageIcon(createTransparentImage(width, height));
    }

    /**
     * Formats the message stored in the resource bundle (using a
     * MessageFormat).
     *
     * @param key       the resourcebundle key
     * @param parameter the parameter for the message
     * @return the formated string
     */
    public String formatMessage(final String key, final Object parameter) {
        return formatMessage(key, new Object[] { parameter });
    }

    /**
     * Formats the message stored in the resource bundle (using a
     * MessageFormat).
     *
     * @param key  the resourcebundle key
     * @param par1 the first parameter for the message
     * @param par2 the second parameter for the message
     * @return the formated string
     */
    public String formatMessage(final String key, final Object par1, final Object par2) {
        return formatMessage(key, new Object[] { par1, par2 });
    }

    /**
     * Formats the message stored in the resource bundle (using a
     * MessageFormat).
     *
     * @param key        the resourcebundle key
     * @param parameters the parameter collection for the message
     * @return the formated string
     */
    public String formatMessage(final String key, final Object[] parameters) {
        final MessageFormat format = new MessageFormat(getString(key));
        format.setLocale(getLocale());
        return format.format(parameters);
    }

    /**
     * Returns the current locale for this resource bundle.
     *
     * @return the locale.
     */
    public Locale getLocale() {
        return this.locale;
    }
}

/* 
 * JCommon : a free general purpose class library for the Java(tm) platform
 * 
 *
 * (C) Copyright 2000-2008, by Object Refinery Limited and Contributors.
 *
 * Project Info:  http://www.jfree.org/jcommon/index.html
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
 * in the United States and other countries.]
 *
 * --------------------------
 * ResourceBundleWrapper.java
 * --------------------------
 * (C)opyright 2008, by Jess Thrysoee and Contributors.
 *
 * Original Author:  Jess Thrysoee;
 * Contributor(s):   David Gilbert (for Object Refinery Limited);
 *
 * Changes
 * -------
 * 18-Dec-2008 : Version 1 (JT);
 *
 */

/**
 * Wrapper of ResourceBundle.getBundle() methods. This wrapper is introduced to
 * avoid a dramatic performance penalty by superfluous resource (and classes
 * loaded by Class.forName) lookups on web server in applets.
 *
 * <pre>
 * public class AppletC extends javax.swing.JApplet {
 *    public void init() {
 *       ResourceBundleWrapper.removeCodeBase(getCodeBase(),
 *               (URLClassLoader) getClass().getClassLoader());
 *    ...
 * </pre>
 *
 * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4243379">
 *               Bug ID: 4243379</a>
 * @see <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4668479">
 *               Bug ID: 4668479</a>
 *
 * @since 1.0.15
 */
class ResourceBundleWrapper {

    /**
     * A special class loader with no code base lookup.  This field may be
     * <code>null</code> (the field is only initialised if removeCodeBase() is
     * called from an applet.
     */
    private static URLClassLoader noCodeBaseClassLoader;

    /**
     * Private constructor.
     */
    private ResourceBundleWrapper() {
        // all methods are static, no need to instantiate
    }

    /**
     * Instantiate a {@link URLClassLoader} for resource lookups where the
     * codeBase URL is removed.  This method is typically called from an
     * applet's init() method.  If this method is never called, the
     * <code>getBundle()</code> methods map to the standard
     * {@link ResourceBundle} lookup methods.
     *
     * @param codeBase  the codeBase URL.
     * @param urlClassLoader  the class loader.
     */
    public static void removeCodeBase(URL codeBase, URLClassLoader urlClassLoader) {
        List urlsNoBase = new ArrayList();

        URL[] urls = urlClassLoader.getURLs();
        for (int i = 0; i < urls.length; i++) {
            if (!urls[i].sameFile(codeBase)) {
                urlsNoBase.add(urls[i]);
            }
        }
        // substitute the filtered URL list
        URL[] urlsNoBaseArray = (URL[]) urlsNoBase.toArray(new URL[0]);
        noCodeBaseClassLoader = URLClassLoader.newInstance(urlsNoBaseArray);
    }

    /**
     * Finds and returns the specified resource bundle.
     *
     * @param baseName  the base name.
     *
     * @return The resource bundle.
     */
    public static final ResourceBundle getBundle(String baseName) {
        // the noCodeBaseClassLoader is configured by a call to the
        // removeCodeBase() method, typically in the init() method of an
        // applet...
        if (noCodeBaseClassLoader != null) {
            return ResourceBundle.getBundle(baseName, Locale.getDefault(), noCodeBaseClassLoader);
        } else {
            // standard ResourceBundle behaviour
            return ResourceBundle.getBundle(baseName);
        }
    }

    /**
     * Finds and returns the specified resource bundle.
     *
     * @param baseName  the base name.
     * @param locale  the locale.
     *
     * @return The resource bundle.
     */
    public static final ResourceBundle getBundle(String baseName, Locale locale) {

        // the noCodeBaseClassLoader is configured by a call to the
        // removeCodeBase() method, typically in the init() method of an
        // applet...
        if (noCodeBaseClassLoader != null) {
            return ResourceBundle.getBundle(baseName, locale, noCodeBaseClassLoader);
        } else {
            // standard ResourceBundle behaviour
            return ResourceBundle.getBundle(baseName, locale);
        }
    }

    /**
     * Maps directly to <code>ResourceBundle.getBundle(baseName, locale,
     * loader)</code>.
     *
     * @param baseName  the base name.
     * @param locale  the locale.
     * @param loader  the class loader.
     *
     * @return The resource bundle.
     */
    public static ResourceBundle getBundle(String baseName, Locale locale, ClassLoader loader) {
        return ResourceBundle.getBundle(baseName, locale, loader);
    }

}

/* 
 * JCommon : a free general purpose class library for the Java(tm) platform
 * 
 *
 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
 *
 * Project Info:  http://www.jfree.org/jcommon/index.html
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
 * USA.
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
 * in the United States and other countries.]
 *
 * ---------------------
 * ObjectUtilitiess.java
 * ---------------------
 * (C) Copyright 2003-2005, by Object Refinery Limited.
 *
 * Original Author:  David Gilbert (for Object Refinery Limited);
 * Contributor(s):   -;
 *
 * $Id: ObjectUtilities.java,v 1.21 2008/09/10 09:24:41 mungady Exp $
 *
 * Changes
 * -------
 * 25-Mar-2003 : Version 1 (DG);
 * 15-Sep-2003 : Fixed bug in clone(List) method (DG);
 * 25-Nov-2004 : Modified clone(Object) method to fail with objects that
 *               cannot be cloned, added new deepClone(Collection) method.
 *               Renamed ObjectUtils --> ObjectUtilities (DG);
 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
 * 18-Aug-2005 : Added casts to suppress compiler warnings, as suggested in
 *               patch 1260622 (DG);
 *
 */

/**
 * A collection of useful static utility methods for handling classes and object
 * instantiation.
 *
 * @author Thomas Morgner
 */
final class ObjectUtilities {

    /**
     * A constant for using the TheadContext as source for the classloader.
     */
    public static final String THREAD_CONTEXT = "ThreadContext";
    /**
     * A constant for using the ClassContext as source for the classloader.
     */
    public static final String CLASS_CONTEXT = "ClassContext";

    /**
     * By default use the thread context.
     */
    private static String classLoaderSource = THREAD_CONTEXT;
    /**
     * The custom classloader to be used (if not null).
     */
    private static ClassLoader classLoader;

    /**
     * Default constructor - private.
     */
    private ObjectUtilities() {
    }

    /**
     * Returns the internal configuration entry, whether the classloader of
     * the thread context or the context classloader should be used.
     *
     * @return the classloader source, either THREAD_CONTEXT or CLASS_CONTEXT.
     */
    public static String getClassLoaderSource() {
        return classLoaderSource;
    }

    /**
     * Defines the internal configuration entry, whether the classloader of
     * the thread context or the context classloader should be used.
     * <p/>
     * This setting can only be defined using the API, there is no safe way
     * to put this into an external configuration file.
     *
     * @param classLoaderSource the classloader source,
     *                          either THREAD_CONTEXT or CLASS_CONTEXT.
     */
    public static void setClassLoaderSource(final String classLoaderSource) {
        ObjectUtilities.classLoaderSource = classLoaderSource;
    }

    /**
     * Returns <code>true</code> if the two objects are equal OR both
     * <code>null</code>.
     *
     * @param o1 object 1 (<code>null</code> permitted).
     * @param o2 object 2 (<code>null</code> permitted).
     * @return <code>true</code> or <code>false</code>.
     */
    public static boolean equal(final Object o1, final Object o2) {
        if (o1 == o2) {
            return true;
        }
        if (o1 != null) {
            return o1.equals(o2);
        } else {
            return false;
        }
    }

    /**
     * Returns a hash code for an object, or zero if the object is
     * <code>null</code>.
     *
     * @param object the object (<code>null</code> permitted).
     * @return The object's hash code (or zero if the object is
     *         <code>null</code>).
     */
    public static int hashCode(final Object object) {
        int result = 0;
        if (object != null) {
            result = object.hashCode();
        }
        return result;
    }

    /**
     * Returns a clone of the specified object, if it can be cloned, otherwise
     * throws a CloneNotSupportedException.
     *
     * @param object the object to clone (<code>null</code> not permitted).
     * @return A clone of the specified object.
     * @throws CloneNotSupportedException if the object cannot be cloned.
     */
    public static Object clone(final Object object) throws CloneNotSupportedException {
        if (object == null) {
            throw new IllegalArgumentException("Null 'object' argument.");
        }

        try {
            final Method method = object.getClass().getMethod("clone", (Class[]) null);
            if (Modifier.isPublic(method.getModifiers())) {
                return method.invoke(object, (Object[]) null);
            }
        } catch (NoSuchMethodException e) {
            System.out.println("Object without clone() method is impossible.");
        } catch (IllegalAccessException e) {
            System.out.println("Object.clone(): unable to call method.");
        } catch (InvocationTargetException e) {
            System.out.println("Object without clone() method is impossible.");
        }

        throw new CloneNotSupportedException("Failed to clone.");
    }

    /**
     * Returns a new collection containing clones of all the items in the
     * specified collection.
     *
     * @param collection the collection (<code>null</code> not permitted).
     * @return A new collection containing clones of all the items in the
     *         specified collection.
     * @throws CloneNotSupportedException if any of the items in the collection
     *                                    cannot be cloned.
     */
    public static Collection deepClone(final Collection collection) throws CloneNotSupportedException {

        if (collection == null) {
            throw new IllegalArgumentException("Null 'collection' argument.");
        }
        // all JDK-Collections are cloneable ...
        // and if the collection is not clonable, then we should throw
        // a CloneNotSupportedException anyway ...
        final Collection result = (Collection) ObjectUtilities.clone(collection);
        result.clear();
        final Iterator iterator = collection.iterator();
        while (iterator.hasNext()) {
            final Object item = iterator.next();
            if (item != null) {
                result.add(clone(item));
            } else {
                result.add(null);
            }
        }
        return result;
    }

    /**
     * Returns the classloader, which was responsible for loading the given
     * class.
     *
     * @param c the classloader, either an application class loader or the
     *          boot loader.
     * @return the classloader, never null.
     * @throws SecurityException if the SecurityManager does not allow to grab
     *                           the context classloader.
     */
    public static ClassLoader getClassLoader(final Class c) {
        final String localClassLoaderSource;
        synchronized (ObjectUtilities.class) {
            if (classLoader != null) {
                return classLoader;
            }
            localClassLoaderSource = classLoaderSource;
        }

        if ("ThreadContext".equals(localClassLoaderSource)) {
            final ClassLoader threadLoader = Thread.currentThread().getContextClassLoader();
            if (threadLoader != null) {
                return threadLoader;
            }
        }

        // Context classloader - do not cache ..
        final ClassLoader applicationCL = c.getClassLoader();
        if (applicationCL == null) {
            return ClassLoader.getSystemClassLoader();
        } else {
            return applicationCL;
        }
    }

    /**
     * Returns the resource specified by the <strong>absolute</strong> name.
     *
     * @param name the name of the resource
     * @param c    the source class
     * @return the url of the resource or null, if not found.
     */
    public static URL getResource(final String name, final Class c) {
        final ClassLoader cl = getClassLoader(c);
        if (cl == null) {
            return null;
        }
        return cl.getResource(name);
    }

    /**
     * Returns the resource specified by the <strong>relative</strong> name.
     *
     * @param name the name of the resource relative to the given class
     * @param c    the source class
     * @return the url of the resource or null, if not found.
     */
    public static URL getResourceRelative(final String name, final Class c) {
        final ClassLoader cl = getClassLoader(c);
        final String cname = convertName(name, c);
        if (cl == null) {
            return null;
        }
        return cl.getResource(cname);
    }

    /**
     * Transform the class-relative resource name into a global name by
     * appending it to the classes package name. If the name is already a
     * global name (the name starts with a "/"), then the name is returned
     * unchanged.
     *
     * @param name the resource name
     * @param c    the class which the resource is relative to
     * @return the tranformed name.
     */
    private static String convertName(final String name, Class c) {
        if (name.startsWith("/")) {
            // strip leading slash..
            return name.substring(1);
        }

        // we cant work on arrays, so remove them ...
        while (c.isArray()) {
            c = c.getComponentType();
        }
        // extract the package ...
        final String baseName = c.getName();
        final int index = baseName.lastIndexOf('.');
        if (index == -1) {
            return name;
        }

        final String pkgName = baseName.substring(0, index);
        return pkgName.replace('.', '/') + "/" + name;
    }

    /**
     * Returns the inputstream for the resource specified by the
     * <strong>absolute</strong> name.
     *
     * @param name the name of the resource
     * @param context the source class
     * @return the url of the resource or null, if not found.
     */
    public static InputStream getResourceAsStream(final String name, final Class context) {
        final URL url = getResource(name, context);
        if (url == null) {
            return null;
        }

        try {
            return url.openStream();
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * Returns the inputstream for the resource specified by the
     * <strong>relative</strong> name.
     *
     * @param name the name of the resource relative to the given class
     * @param context the source class
     * @return the url of the resource or null, if not found.
     */
    public static InputStream getResourceRelativeAsStream(final String name, final Class context) {
        final URL url = getResourceRelative(name, context);
        if (url == null) {
            return null;
        }

        try {
            return url.openStream();
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * Tries to create a new instance of the given class. This is a short cut
     * for the common bean instantiation code.
     *
     * @param className the class name as String, never null.
     * @param source    the source class, from where to get the classloader.
     * @return the instantiated object or null, if an error occured.
     */
    public static Object loadAndInstantiate(final String className, final Class source) {
        try {
            final ClassLoader loader = getClassLoader(source);
            final Class c = loader.loadClass(className);
            return c.newInstance();
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * Tries to create a new instance of the given class. This is a short cut
     * for the common bean instantiation code. This method is a type-safe method
     * and will not instantiate the class unless it is an instance of the given
     * type.
     *
     * @param className the class name as String, never null.
     * @param source    the source class, from where to get the classloader.
     * @param type  the type.
     * @return the instantiated object or null, if an error occurred.
     */
    public static Object loadAndInstantiate(final String className, final Class source, final Class type) {
        try {
            final ClassLoader loader = getClassLoader(source);
            final Class c = loader.loadClass(className);
            if (type.isAssignableFrom(c)) {
                return c.newInstance();
            }
        } catch (Exception e) {
            return null;
        }
        return null;
    }

    /**
     * Returns <code>true</code> if this is version 1.4 or later of the
     * Java runtime.
     *
     * @return A boolean.
     */
    public static boolean isJDK14() {
        return false;
    }

    private static String[] parseVersions(String version) {
        if (version == null) {
            return new String[0];
        }

        final ArrayList versions = new ArrayList();
        final StringTokenizer strtok = new StringTokenizer(version, ".");
        while (strtok.hasMoreTokens()) {
            versions.add(strtok.nextToken());
        }
        return (String[]) versions.toArray(new String[versions.size()]);
    }
}