001/* ========================================================================
002 * JCommon : a free general purpose class library for the Java(tm) platform
003 * ========================================================================
004 *
005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006 *
007 * Project Info:  http://www.jfree.org/jcommon/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it
010 * under the terms of the GNU Lesser General Public License as published by
011 * the Free Software Foundation; either version 2.1 of the License, or
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301,
022 * USA.
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
025 * in the United States and other countries.]
026 *
027 * ---------------------
028 * ObjectUtilitiess.java
029 * ---------------------
030 * (C) Copyright 2003-2005, by Object Refinery Limited.
031 *
032 * Original Author:  David Gilbert (for Object Refinery Limited);
033 * Contributor(s):   -;
034 *
035 * $Id: ObjectUtilities.java,v 1.21 2008/09/10 09:24:41 mungady Exp $
036 *
037 * Changes
038 * -------
039 * 25-Mar-2003 : Version 1 (DG);
040 * 15-Sep-2003 : Fixed bug in clone(List) method (DG);
041 * 25-Nov-2004 : Modified clone(Object) method to fail with objects that
042 *               cannot be cloned, added new deepClone(Collection) method.
043 *               Renamed ObjectUtils --> ObjectUtilities (DG);
044 * 11-Jan-2005 : Removed deprecated code in preparation for 1.0.0 release (DG);
045 * 18-Aug-2005 : Added casts to suppress compiler warnings, as suggested in
046 *               patch 1260622 (DG);
047 *
048 */
049
050package org.jfree.util;
051
052import java.io.IOException;
053import java.io.InputStream;
054import java.lang.reflect.InvocationTargetException;
055import java.lang.reflect.Method;
056import java.lang.reflect.Modifier;
057import java.net.URL;
058import java.util.ArrayList;
059import java.util.Collection;
060import java.util.Iterator;
061import java.util.StringTokenizer;
062
063/**
064 * A collection of useful static utility methods for handling classes and object
065 * instantiation.
066 *
067 * @author Thomas Morgner
068 */
069public final class ObjectUtilities {
070
071    /**
072     * A constant for using the TheadContext as source for the classloader.
073     */
074    public static final String THREAD_CONTEXT = "ThreadContext";
075    /**
076     * A constant for using the ClassContext as source for the classloader.
077     */
078    public static final String CLASS_CONTEXT = "ClassContext";
079
080    /**
081     * By default use the thread context.
082     */
083    private static String classLoaderSource = THREAD_CONTEXT;
084    /**
085     * The custom classloader to be used (if not null).
086     */
087    private static ClassLoader classLoader;
088
089    /**
090     * Default constructor - private.
091     */
092    private ObjectUtilities() {
093    }
094
095    /**
096     * Returns the internal configuration entry, whether the classloader of
097     * the thread context or the context classloader should be used.
098     *
099     * @return the classloader source, either THREAD_CONTEXT or CLASS_CONTEXT.
100     */
101    public static String getClassLoaderSource() {
102        return classLoaderSource;
103    }
104
105    /**
106     * Defines the internal configuration entry, whether the classloader of
107     * the thread context or the context classloader should be used.
108     * <p/>
109     * This setting can only be defined using the API, there is no safe way
110     * to put this into an external configuration file.
111     *
112     * @param classLoaderSource the classloader source,
113     *                          either THREAD_CONTEXT or CLASS_CONTEXT.
114     */
115    public static void setClassLoaderSource(final String classLoaderSource) {
116        ObjectUtilities.classLoaderSource = classLoaderSource;
117    }
118
119    /**
120     * Returns <code>true</code> if the two objects are equal OR both
121     * <code>null</code>.
122     *
123     * @param o1 object 1 (<code>null</code> permitted).
124     * @param o2 object 2 (<code>null</code> permitted).
125     * @return <code>true</code> or <code>false</code>.
126     */
127    public static boolean equal(final Object o1, final Object o2) {
128        if (o1 == o2) {
129            return true;
130        }
131        if (o1 != null) {
132            return o1.equals(o2);
133        }
134        else {
135            return false;
136        }
137    }
138
139    /**
140     * Returns a hash code for an object, or zero if the object is
141     * <code>null</code>.
142     *
143     * @param object the object (<code>null</code> permitted).
144     * @return The object's hash code (or zero if the object is
145     *         <code>null</code>).
146     */
147    public static int hashCode(final Object object) {
148        int result = 0;
149        if (object != null) {
150            result = object.hashCode();
151        }
152        return result;
153    }
154
155    /**
156     * Returns a clone of the specified object, if it can be cloned, otherwise
157     * throws a CloneNotSupportedException.
158     *
159     * @param object the object to clone (<code>null</code> not permitted).
160     * @return A clone of the specified object.
161     * @throws CloneNotSupportedException if the object cannot be cloned.
162     */
163    public static Object clone(final Object object)
164        throws CloneNotSupportedException {
165        if (object == null) {
166            throw new IllegalArgumentException("Null 'object' argument.");
167        }
168        if (object instanceof PublicCloneable) {
169            final PublicCloneable pc = (PublicCloneable) object;
170            return pc.clone();
171        }
172        else {
173            try {
174                final Method method = object.getClass().getMethod("clone",
175                        (Class[]) null);
176                if (Modifier.isPublic(method.getModifiers())) {
177                    return method.invoke(object, (Object[]) null);
178                }
179            }
180            catch (NoSuchMethodException e) {
181                Log.warn("Object without clone() method is impossible.");
182            }
183            catch (IllegalAccessException e) {
184                Log.warn("Object.clone(): unable to call method.");
185            }
186            catch (InvocationTargetException e) {
187                Log.warn("Object without clone() method is impossible.");
188            }
189        }
190        throw new CloneNotSupportedException("Failed to clone.");
191    }
192
193    /**
194     * Returns a new collection containing clones of all the items in the
195     * specified collection.
196     *
197     * @param collection the collection (<code>null</code> not permitted).
198     * @return A new collection containing clones of all the items in the
199     *         specified collection.
200     * @throws CloneNotSupportedException if any of the items in the collection
201     *                                    cannot be cloned.
202     */
203    public static Collection deepClone(final Collection collection)
204        throws CloneNotSupportedException {
205
206        if (collection == null) {
207            throw new IllegalArgumentException("Null 'collection' argument.");
208        }
209        // all JDK-Collections are cloneable ...
210        // and if the collection is not clonable, then we should throw
211        // a CloneNotSupportedException anyway ...
212        final Collection result
213            = (Collection) ObjectUtilities.clone(collection);
214        result.clear();
215        final Iterator iterator = collection.iterator();
216        while (iterator.hasNext()) {
217            final Object item = iterator.next();
218            if (item != null) {
219                result.add(clone(item));
220            }
221            else {
222                result.add(null);
223            }
224        }
225        return result;
226    }
227
228    /**
229     * Redefines the custom classloader.
230     *
231     * @param classLoader the new classloader or null to use the default.
232     */
233    public static synchronized void setClassLoader(
234            final ClassLoader classLoader) {
235        ObjectUtilities.classLoader = classLoader;
236    }
237
238    /**
239     * Returns the custom classloader or null, if no custom classloader is defined.
240     *
241     * @return the custom classloader or null to use the default.
242     */
243    public static ClassLoader getClassLoader() {
244      return classLoader;
245    }
246
247    /**
248     * Returns the classloader, which was responsible for loading the given
249     * class.
250     *
251     * @param c the classloader, either an application class loader or the
252     *          boot loader.
253     * @return the classloader, never null.
254     * @throws SecurityException if the SecurityManager does not allow to grab
255     *                           the context classloader.
256     */
257    public static ClassLoader getClassLoader(final Class c) {
258        final String localClassLoaderSource;
259        synchronized(ObjectUtilities.class)
260        {
261          if (classLoader != null) {
262              return classLoader;
263          }
264          localClassLoaderSource = classLoaderSource;
265        }
266
267        if ("ThreadContext".equals(localClassLoaderSource)) {
268            final ClassLoader threadLoader = Thread.currentThread().getContextClassLoader();
269            if (threadLoader != null) {
270                return threadLoader;
271            }
272        }
273
274        // Context classloader - do not cache ..
275        final ClassLoader applicationCL = c.getClassLoader();
276        if (applicationCL == null) {
277            return ClassLoader.getSystemClassLoader();
278        }
279        else {
280            return applicationCL;
281        }
282    }
283
284
285    /**
286     * Returns the resource specified by the <strong>absolute</strong> name.
287     *
288     * @param name the name of the resource
289     * @param c    the source class
290     * @return the url of the resource or null, if not found.
291     */
292    public static URL getResource(final String name, final Class c) {
293        final ClassLoader cl = getClassLoader(c);
294        if (cl == null) {
295            return null;
296        }
297        return cl.getResource(name);
298    }
299
300    /**
301     * Returns the resource specified by the <strong>relative</strong> name.
302     *
303     * @param name the name of the resource relative to the given class
304     * @param c    the source class
305     * @return the url of the resource or null, if not found.
306     */
307    public static URL getResourceRelative(final String name, final Class c) {
308        final ClassLoader cl = getClassLoader(c);
309        final String cname = convertName(name, c);
310        if (cl == null) {
311            return null;
312        }
313        return cl.getResource(cname);
314    }
315
316    /**
317     * Transform the class-relative resource name into a global name by
318     * appending it to the classes package name. If the name is already a
319     * global name (the name starts with a "/"), then the name is returned
320     * unchanged.
321     *
322     * @param name the resource name
323     * @param c    the class which the resource is relative to
324     * @return the tranformed name.
325     */
326    private static String convertName(final String name, Class c) {
327        if (name.startsWith("/")) {
328            // strip leading slash..
329            return name.substring(1);
330        }
331
332        // we cant work on arrays, so remove them ...
333        while (c.isArray()) {
334            c = c.getComponentType();
335        }
336        // extract the package ...
337        final String baseName = c.getName();
338        final int index = baseName.lastIndexOf('.');
339        if (index == -1) {
340            return name;
341        }
342
343        final String pkgName = baseName.substring(0, index);
344        return pkgName.replace('.', '/') + "/" + name;
345    }
346
347    /**
348     * Returns the inputstream for the resource specified by the
349     * <strong>absolute</strong> name.
350     *
351     * @param name the name of the resource
352     * @param context the source class
353     * @return the url of the resource or null, if not found.
354     */
355    public static InputStream getResourceAsStream(final String name,
356                                                  final Class context) {
357        final URL url = getResource(name, context);
358        if (url == null) {
359            return null;
360        }
361
362        try {
363            return url.openStream();
364        }
365        catch (IOException e) {
366            return null;
367        }
368    }
369
370    /**
371     * Returns the inputstream for the resource specified by the
372     * <strong>relative</strong> name.
373     *
374     * @param name the name of the resource relative to the given class
375     * @param context the source class
376     * @return the url of the resource or null, if not found.
377     */
378    public static InputStream getResourceRelativeAsStream
379        (final String name, final Class context) {
380        final URL url = getResourceRelative(name, context);
381        if (url == null) {
382            return null;
383        }
384
385        try {
386            return url.openStream();
387        }
388        catch (IOException e) {
389            return null;
390        }
391    }
392
393    /**
394     * Tries to create a new instance of the given class. This is a short cut
395     * for the common bean instantiation code.
396     *
397     * @param className the class name as String, never null.
398     * @param source    the source class, from where to get the classloader.
399     * @return the instantiated object or null, if an error occured.
400     */
401    public static Object loadAndInstantiate(final String className,
402                                            final Class source) {
403        try {
404            final ClassLoader loader = getClassLoader(source);
405            final Class c = loader.loadClass(className);
406            return c.newInstance();
407        }
408        catch (Exception e) {
409            return null;
410        }
411    }
412
413    /**
414     * Tries to create a new instance of the given class. This is a short cut
415     * for the common bean instantiation code. This method is a type-safe method
416     * and will not instantiate the class unless it is an instance of the given
417     * type.
418     *
419     * @param className the class name as String, never null.
420     * @param source    the source class, from where to get the classloader.
421     * @param type  the type.
422     * @return the instantiated object or null, if an error occurred.
423     */
424    public static Object loadAndInstantiate(final String className,
425                                            final Class source,
426                                            final Class type) {
427        try {
428            final ClassLoader loader = getClassLoader(source);
429            final Class c = loader.loadClass(className);
430            if (type.isAssignableFrom(c)) {
431                return c.newInstance();
432            }
433        }
434        catch (Exception e) {
435            return null;
436        }
437        return null;
438    }
439
440    /**
441     * Returns <code>true</code> if this is version 1.4 or later of the
442     * Java runtime.
443     *
444     * @return A boolean.
445     */
446    public static boolean isJDK14() {
447        try {
448          final ClassLoader loader = getClassLoader(ObjectUtilities.class);
449          if (loader != null) {
450              try {
451                loader.loadClass("java.util.RandomAccess");
452                return true;
453              }
454              catch (ClassNotFoundException e) {
455                return false;
456              }
457              catch(Exception e) {
458                // do nothing, but do not crash ...
459              }
460          }
461        }
462        catch (Exception e) {
463          // cant do anything about it, we have to accept and ignore it ..
464        }
465
466        // OK, the quick and dirty, but secure way failed. Lets try it
467        // using the standard way.
468        try {
469            final String version = System.getProperty
470                    ("java.vm.specification.version");
471            // parse the beast...
472            if (version == null) {
473                return false;
474            }
475
476            String[] versions = parseVersions(version);
477            String[] target = new String[]{ "1", "4" };
478            return (ArrayUtilities.compareVersionArrays(versions, target) >= 0);
479        }
480        catch(Exception e) {
481            return false;
482        }
483    }
484
485    private static String[] parseVersions (String version)
486    {
487      if (version == null)
488      {
489        return new String[0];
490      }
491
492      final ArrayList versions = new ArrayList();
493      final StringTokenizer strtok = new StringTokenizer(version, ".");
494      while (strtok.hasMoreTokens())
495      {
496        versions.add (strtok.nextToken());
497      }
498      return (String[]) versions.toArray(new String[versions.size()]);
499    }
500}