001: /*
002: * @(#)ClassUtil.java 0.9.0 11/13/2000 - 14:06:34
003: *
004: * Copyright (C) 2000,2002-2003 Matt Albrecht
005: * groboclown@users.sourceforge.net
006: * http://groboutils.sourceforge.net
007: *
008: * Permission is hereby granted, free of charge, to any person obtaining a
009: * copy of this software and associated documentation files (the "Software"),
010: * to deal in the Software without restriction, including without limitation
011: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
012: * and/or sell copies of the Software, and to permit persons to whom the
013: * Software is furnished to do so, subject to the following conditions:
014: *
015: * The above copyright notice and this permission notice shall be included in
016: * all copies or substantial portions of the Software.
017: *
018: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
019: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
020: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
021: * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
022: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
023: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
024: * DEALINGS IN THE SOFTWARE.
025: */
026:
027: package net.sourceforge.groboutils.util.classes.v1;
028:
029: import java.util.Hashtable;
030:
031: /**
032: * Utility class for loading classes and creating instances. It decides
033: * which JDK version to use as the loader. Class instances are put
034: * into a Hashtable, based on URL and classname. When the Java-Doc defines
035: * a <tt>jarName</tt>, as in {@link #getClass( String, String )}, the
036: * <tt>jarName</tt> may be a filename loadable from the {@link java.io.File}
037: * class, or a proper URL loadable from the {@link java.net.URL} class.
038: * If the <tt>jarName</tt> does not have a protocol, then it is assumed to
039: * be a file, otherwise, it is used as a URL.
040: * <P>
041: * Note that this class is not thread safe. It is assumed that applications
042: * will use the ClassUtil only during initialization times, since dynamic
043: * class loading can be very expensive. If you need thread safety, then you
044: * will need to ensure that either all class loading is done in a single thread,
045: * or that your application properly <tt>synchronize</tt>s the method calls.
046: * <P>
047: * Update v0.9.1: the constructor will now check for jdk2 first like before,
048: * but now if it is not found, it will try to use jdk0 compatibility - before,
049: * it would just fail out. This allows the libraries to be repackaged to
050: * contain only the jdk0 classes if so desired.
051: *
052: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
053: * @version $Date: 2003/02/10 22:52:36 $
054: * @since November 13, 2000 (GroboUtils Alpha 0.9.0)
055: */
056: public class ClassUtil {
057: //----------------------------
058: // Public data
059:
060: //----------------------------
061: // Private data
062:
063: /**
064: * Subclasses must instantiate themselves in this variable in their
065: * static initialization block, and overload the {@link #getInstance()}
066: * static method.
067: */
068: protected static ClassUtil s_instance = new ClassUtil();
069:
070: private IUrlClassLoader classLoader = null;
071: private Hashtable classHash = new Hashtable();
072:
073: //----------------------------
074: // constructors
075:
076: /**
077: * Default constructor - made protected so
078: * users won't instantiate the utility
079: */
080: protected ClassUtil() {
081: // detect which JDK we're using, and from that use the correct
082: // IUrlClassLoader instance.
083:
084: if (isJdk2Compatible()) {
085: this .classLoader = (IUrlClassLoader) createObject(getClassPackage(ClassUtil.class)
086: + ".jdk2.UrlClassLoader");
087: }
088: if (this .classLoader == null) {
089: this .classLoader = (IUrlClassLoader) createObject(getClassPackage(ClassUtil.class)
090: + ".jdk0.UrlClassLoader");
091: if (this .classLoader == null) {
092: throw new IllegalStateException(
093: "There was an error loading a class loader.");
094: }
095: }
096: }
097:
098: //----------------------------
099: // Public Static methods
100:
101: /**
102: * Retrieve the shared instance of the utility class.
103: *
104: * @return the utility instance
105: */
106: public static ClassUtil getInstance() {
107: // Due to threading issues, the instance is created
108: // in the static initialization block.
109:
110: return s_instance;
111: }
112:
113: //----------------------------
114: // Public methods
115:
116: /**
117: * Call this to flush out the cache. The cache may be huge, depending
118: * on the Jar files loaded, as well as the Class instances. This
119: * should be called whenever a mass class instantiation process is
120: * finished.
121: */
122: public void flush() {
123: this .classHash = new Hashtable();
124: this .classLoader.flush();
125: }
126:
127: /**
128: * Either finds or loads from cache the given class, using the
129: * default class loader.
130: *
131: * @param name the name of the class to load.
132: * @return the discovered Class, or <tt>null</tt> if it could not be found.
133: * @see #getClass( String, String )
134: */
135: public Class getClass(String name) {
136: return getClass(name, null);
137: }
138:
139: /**
140: * Either finds or loads from cache the given class.
141: *
142: * @param name the name of the class to load.
143: * @param jarName the URL to load the class from - it may be <tt>null</tt>.
144: * @return the discovered Class, or <tt>null</tt> if it could not be found.
145: * @see #getClass( String )
146: */
147: public Class getClass(String name, String jarName) {
148: String hashName = getClassHashName(name, jarName);
149: Class c = (Class) classHash.get(hashName);
150: if (c == null) {
151: c = loadClass(name, jarName);
152: if (c != null) {
153: classHash.put(hashName, c);
154: }
155: }
156: return c;
157: }
158:
159: /**
160: * Creates a new instance of the class with the given <tt>className</tt>
161: * using the default constructor. If there was an error during the
162: * creation, such as the class was not found, the class does not have
163: * a default constructor, or the constructor threw an exception, then
164: * <tt>null</tt> is returned.
165: *
166: * @param className the name of the class to create an instance.
167: * @return the new instance, or <tt>null</tt> if there was a problem.
168: * @see #getClass( String )
169: * @see #createObject( String, String )
170: */
171: public Object createObject(String className) {
172: return createObject(className, null);
173: }
174:
175: /**
176: * Creates a new instance of the class with the given <tt>className</tt>
177: * using the default constructor, from the given URL. If there was an
178: * error during the creation, such as the class was not found, the class
179: * does not have a default constructor, or the constructor threw an
180: * exception, then <tt>null</tt> is returned.
181: *
182: * @param className the name of the class to create an instance.
183: * @param jarName the URL to load the class from - it may be <tt>null</tt>.
184: * @return the new instance, or <tt>null</tt> if there was a problem.
185: * @see #getClass( String, String )
186: * @see #createObject( String )
187: */
188: public Object createObject(String className, String jarName) {
189: return createObject(getClass(className, jarName));
190: }
191:
192: /**
193: * Creates an Object from the given Class, using its default constructor.
194: * All creation exceptions are swallowed. If the object could not
195: * be created, then <tt>null</tt> is returned.
196: *
197: * @param c the Class object from which a new instance will be created
198: * using its default constructor.
199: * @return the instantiated object, or <tt>null</tt> if <tt>c</tt> is
200: * <tt>null</tt>, or if there was an error during initialization.
201: */
202: public Object createObject(Class c) {
203: if (c == null)
204: return null;
205:
206: Object obj = null;
207: try {
208: obj = c.newInstance();
209: } catch (InstantiationException ie) {
210: System.out.println("Could not instantiate " + c.getName()
211: + ": " + ie.getMessage());
212: obj = null;
213: } catch (IllegalAccessException iae) {
214: System.out.println("Could not instantiate " + c.getName()
215: + ": " + iae.getMessage());
216: obj = null;
217: } catch (NoSuchMethodError nsme) {
218: System.out.println("Could not instantiate " + c.getName()
219: + ": " + nsme.getMessage());
220: obj = null;
221: }
222: return obj;
223: }
224:
225: /**
226: * Checks if the current JVM version is 1.2 compatible. We check by
227: * seeing if <tt>java.net.URLClassLoader</tt> exists.
228: *
229: * @return <tt>true</tt> if {@link java.net.URLClassLoader} exists
230: * in the classpath, or <tt>false</tt> otherwise.
231: */
232: public boolean isJdk2Compatible() {
233: return (getClass("java.net.URLClassLoader") != null);
234: }
235:
236: /**
237: * Discovers the package name for the given class. The package name
238: * will not have a final '.'.
239: *
240: * @param c the class to find the package name.
241: * @return the package name, or <tt>null</tt> if <tt>c</tt> is
242: * <tt>null</tt>.
243: */
244: public String getClassPackage(Class c) {
245: if (c == null) {
246: return null;
247: }
248: String fullname = c.getName();
249: int pos = fullname.lastIndexOf('.');
250: if (pos < 0) {
251: // no package
252: return "";
253: }
254:
255: // else, extract the pacakge name.
256: return fullname.substring(0, pos);
257: }
258:
259: //----------------------------
260: // Protected methods
261:
262: /**
263: * Creates the name of the class for the hashtable lookup, which is
264: * a junction of the jar name and the class name. It allows for multiple
265: * classes with the same name.
266: *
267: * @param name the class name
268: * @param jarName the jar name - may be <tt>null</tt>.
269: * @return the name for the hashtable lookup.
270: */
271: protected String getClassHashName(String name, String jarName) {
272: if (jarName == null) {
273: jarName = "<null>";
274: }
275: StringBuffer sb = new StringBuffer(jarName);
276: sb.append(';');
277: sb.append(name);
278: return new String(sb);
279: }
280:
281: /**
282: * Attempts to load the class from the current classpath if <tt>baseJar</tt>
283: * is <tt>null</tt>, or from the appropriate class loader if it is not
284: * <tt>null</tt>. If the class is not found, then this returns
285: * <tt>null</tt>. This never throws an exception.
286: *
287: * @param className name of the class to load
288: * @param baseJar the URL file to load the class from - may be
289: * <tt>null</tt>.
290: * @return the Class instance, or <tt>null</tt> if it was not found.
291: */
292: protected Class loadClass(String className, String baseJar) {
293: Class c;
294:
295: try {
296: if (baseJar == null || baseJar.length() <= 0) {
297: try {
298: c = Class.forName(className);
299: } catch (ClassNotFoundException cnfe) {
300: // System.out.println("Class "+className+" not found");
301: c = null;
302: }
303: } else {
304: c = this .classLoader.loadClass(className, baseJar);
305: }
306: } catch (Throwable t) {
307: // probably a NoClassDefFoundError
308: c = null;
309: }
310: return c;
311: }
312:
313: //----------------------------
314: // Private methods
315: }
|