001: /*
002: Copyright (c) 2003-2005, Dennis M. Sosnoski
003: All rights reserved.
004:
005: Redistribution and use in source and binary forms, with or without modification,
006: are permitted provided that the following conditions are met:
007:
008: * Redistributions of source code must retain the above copyright notice, this
009: list of conditions and the following disclaimer.
010: * Redistributions in binary form must reproduce the above copyright notice,
011: this list of conditions and the following disclaimer in the documentation
012: and/or other materials provided with the distribution.
013: * Neither the name of JiBX nor the names of its contributors may be used
014: to endorse or promote products derived from this software without specific
015: prior written permission.
016:
017: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
018: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
019: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
021: ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: */
028:
029: package org.jibx.binding;
030:
031: import java.io.ByteArrayOutputStream;
032: import java.io.File;
033: import java.io.IOException;
034: import java.io.InputStream;
035: import java.net.MalformedURLException;
036: import java.net.URL;
037: import java.net.URLClassLoader;
038: import java.util.ArrayList;
039: import java.util.HashMap;
040:
041: import org.jibx.binding.classes.BoundClass;
042: import org.jibx.binding.classes.ClassCache;
043: import org.jibx.binding.classes.ClassFile;
044: import org.jibx.binding.classes.MungedClass;
045: import org.jibx.binding.def.BindingDefinition;
046: import org.jibx.runtime.JiBXException;
047:
048: /**
049: * Binding classloader. This is intended to substitute for the System
050: * classloader (i.e., the one used for loading user classes). It first processes
051: * one or more binding definitions, caching the binary classes modified by the
052: * bindings. It then uses these modified forms of the classes when they're
053: * requested for loading.
054: *
055: * @author Dennis M. Sosnoski
056: * @version 1.0
057: */
058:
059: public class Loader extends URLClassLoader {
060: /** Binding definitions used by loader. */
061: private ArrayList m_bindings;
062:
063: /** Flag for bindings compiled into class code. */
064: private boolean m_isBound;
065:
066: /** Map of classes modified by binding. */
067: private HashMap m_classMap;
068:
069: /**
070: * Constructor with classpath URLs and parent classloader supplied. Sets up
071: * the paths for both actual classloading and finding classes to be bound.
072: *
073: * @param paths array of classpath URLs
074: * @param parent classloader used for delegation loading
075: */
076: public Loader(URL[] paths, ClassLoader parent) {
077:
078: // configure the base class
079: super (paths, parent);
080: m_bindings = new ArrayList();
081: m_classMap = new HashMap();
082:
083: // find all the file URLs in path
084: ArrayList fpaths = new ArrayList(paths.length);
085: for (int i = 0; i < paths.length; i++) {
086: URL path = paths[i];
087: if ("file".equals(path.getProtocol())) {
088: fpaths.add(path.getPath());
089: }
090: }
091:
092: // set paths to be used for loading referenced classes
093: String[] dirs = (String[]) fpaths.toArray(new String[0]);
094: ClassCache.setPaths(dirs);
095: ClassFile.setPaths(dirs);
096:
097: // reset static information accumulation for binding
098: BoundClass.reset();
099: MungedClass.reset();
100: BindingDefinition.reset();
101: }
102:
103: /**
104: * Constructor with classpath URLs supplied. This uses the supplied
105: * classpaths, delegating directly to the parent classloader of the normal
106: * System classloader.
107: *
108: * @param paths array of classpath URLs
109: */
110: public Loader(URL[] paths) {
111: this (paths, ClassLoader.getSystemClassLoader().getParent());
112: }
113:
114: /**
115: * Default constructor. This reads the standard class path and uses it for
116: * locating classes used by the binding, delegating directly to the parent
117: * classloader of the normal System classloader.
118: *
119: * @exception MalformedURLException on error in classpath URLs
120: */
121: public Loader() throws MalformedURLException {
122: this (getClassPaths());
123: }
124:
125: /**
126: * Reset loader information. This discards all prior bindings and clears the
127: * internal state in preparation for loading a different set of bindings. It
128: * is not possible to clear the loaded classes, though, so any new bindings
129: * must refer to different classes from those previously loaded.
130: */
131: public void reset() {
132: m_bindings.clear();
133: m_classMap.clear();
134: m_isBound = false;
135: BoundClass.reset();
136: MungedClass.reset();
137: BindingDefinition.reset();
138: }
139:
140: /**
141: * Method builds an array of URL for items in the class path.
142: *
143: * @return array of classpath URLs
144: */
145: public static URL[] getClassPaths() throws MalformedURLException {
146: String[] paths = Utility.getClassPaths();
147: URL[] urls = new URL[paths.length];
148: for (int i = 0; i < urls.length; i++) {
149: urls[i] = new File(paths[i]).toURL();
150: }
151: return urls;
152: }
153:
154: /**
155: * Load binding definition. This may be called multiple times to load
156: * multiple bindings, but only prior to the bindings being compiled. The
157: * reader form of the call is generally preferred, since the document
158: * encoding may not be properly interpreted from a stream.
159: *
160: * @param fname binding definition full name
161: * @param sname short form of name to use as the default name of the binding
162: * @param is input stream for binding definition document
163: * @param url URL for binding definition (<code>null</code> if not
164: * available)
165: * @exception IllegalStateException if called after bindings have been
166: * compiled
167: * @exception IOException if error reading the binding
168: * @exception JiBXException if error in processing the binding definition
169: */
170: public void loadBinding(String fname, String sname, InputStream is,
171: URL url) throws JiBXException, IOException {
172:
173: // error if called after bindings have been compiled
174: if (m_isBound) {
175: throw new IllegalStateException(
176: "Call not allowed after bindings compiled");
177: } else {
178: m_bindings.add(Utility.loadBinding(fname, sname, is, url,
179: true));
180: }
181: }
182:
183: /**
184: * Load binding definition from file path. This may be called multiple times
185: * to load multiple bindings, but only prior to the bindings being compiled.
186: *
187: * @param path binding definition file path
188: * @exception IllegalStateException if called after bindings have been
189: * compiled
190: * @exception IOException if error reading the file
191: * @exception JiBXException if error in processing the binding definition
192: */
193: public void loadFileBinding(String path) throws JiBXException,
194: IOException {
195:
196: // error if called after bindings have been compiled
197: if (m_isBound) {
198: throw new IllegalStateException(
199: "Call not allowed after bindings compiled");
200: } else {
201: m_bindings.add(Utility.loadFileBinding(path, true));
202: }
203: }
204:
205: /**
206: * Load binding definition from file path. This may be called multiple times
207: * to load multiple bindings, but only prior to the bindings being compiled.
208: *
209: * @param path binding definition file path
210: * @exception IllegalStateException if called after bindings have been
211: * compiled
212: * @exception IOException if error reading the file
213: * @exception JiBXException if error in processing the binding definition
214: */
215: public void loadResourceBinding(String path) throws JiBXException,
216: IOException {
217:
218: // error if called after bindings have been compiled
219: if (m_isBound) {
220: throw new IllegalStateException(
221: "Call not allowed after bindings compiled");
222: } else {
223: String fname = path;
224: int split = fname.lastIndexOf('/');
225: if (split >= 0) {
226: fname = fname.substring(split + 1);
227: }
228: String sname = fname;
229: split = sname.lastIndexOf('.');
230: if (split >= 0) {
231: sname = sname.substring(0, split);
232: }
233: sname = Utility.convertName(sname);
234: InputStream is = getResourceAsStream(path);
235: if (is == null) {
236: throw new IOException("Resource " + path + " not found");
237: } else {
238: loadBinding(fname, sname, is, null);
239: }
240: }
241: }
242:
243: /**
244: * Process the binding definitions. This compiles the bindings into the
245: * classes, saving the modified classes for loading when needed.
246: *
247: * @exception JiBXException if error in processing the binding definition
248: */
249: public void processBindings() throws JiBXException {
250: if (!m_isBound) {
251:
252: // handle code generation from bindings
253: int count = m_bindings.size();
254: for (int i = 0; i < count; i++) {
255: BindingDefinition binding = (BindingDefinition) m_bindings
256: .get(i);
257: binding.generateCode(false);
258: }
259:
260: // build hashmap of modified classes
261: ClassFile[][] lists = MungedClass.fixChanges(false);
262: count = lists[0].length;
263: for (int i = 0; i < count; i++) {
264: ClassFile clas = lists[0][i];
265: m_classMap.put(clas.getName(), clas);
266: }
267:
268: // finish by setting flag for binding done
269: m_isBound = true;
270: }
271: }
272:
273: /**
274: * Check if a class has been modified by a binding. If bindings haven't been
275: * compiled prior to this call they will be compiled automatically when this
276: * method is called.
277: *
278: * @param name fully qualified package and class name to be found
279: * @return <code>true</code> if class modified by binding,
280: * <code>false</code> if not
281: */
282: protected boolean isBoundClass(String name) {
283:
284: // first complete binding processing if not already done
285: if (!m_isBound) {
286: try {
287: processBindings();
288: } catch (JiBXException e) {
289: e.printStackTrace();
290: }
291: }
292: return m_classMap.containsKey(name);
293: }
294:
295: /**
296: * Find and load class by name. If the named class has been modified by a
297: * binding this loads the modified binary class; otherwise, it just uses the
298: * base class implementation to do the loading. If bindings haven't been
299: * compiled prior to this call they will be compiled automatically when this
300: * method is called.
301: * @see java.lang.ClassLoader#findClass(java.lang.String)
302: *
303: * @param name fully qualified package and class name to be found
304: * @return the loaded class
305: * @throws ClassNotFoundException if the class cannot be found
306: */
307: protected Class findClass(String name)
308: throws ClassNotFoundException {
309:
310: // check if class has been modified by binding
311: if (isBoundClass(name)) {
312: try {
313:
314: // convert class information to byte array
315: ClassFile clas = (ClassFile) m_classMap.get(name);
316: ByteArrayOutputStream bos = new ByteArrayOutputStream();
317: clas.writeFile(bos);
318: byte[] bytes = bos.toByteArray();
319: return defineClass(name, bytes, 0, bytes.length);
320:
321: } catch (IOException e) {
322: throw new ClassNotFoundException(
323: "Unable to load modified class " + name);
324: }
325: } else {
326:
327: // just use base class handling
328: return super .findClass(name);
329:
330: }
331: }
332:
333: /**
334: * Version of bind-on-demand loader which will not delegate handling of
335: * classes included in the binding definition. Somewhat dangerous to use,
336: * since it may result in loading multiple versions of the same class
337: * (with and without binding).
338: */
339: public static class NondelegatingLoader extends Loader {
340: /**
341: * @throws MalformedURLException
342: */
343: public NondelegatingLoader() throws MalformedURLException {
344: super (getClassPaths(), ClassLoader.getSystemClassLoader());
345: }
346:
347: /* (non-Javadoc)
348: * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
349: */
350: protected synchronized Class loadClass(String name,
351: boolean resolve) throws ClassNotFoundException {
352: if (isBoundClass(name)) {
353: Class clas = findLoadedClass(name);
354: if (clas == null) {
355: clas = findClass(name);
356: }
357: return clas;
358: } else {
359: return super.loadClass(name, resolve);
360: }
361: }
362: }
363: }
|