/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.codehaus.groovy.tools; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.HashMap; import java.util.List; import java.util.Map; /** * This ClassLoader should be used as root of class loaders. Any * RootLoader does have it's own classpath. When searching for a * class or resource this classpath will be used. Parent * Classloaders are ignored first. If a class or resource * can't be found in the classpath of the RootLoader, then parent is * checked. * <p> * <b>Note:</b> this is very against the normal behavior of * classloaders. Normal is to first check parent and then look in * the resources you gave this classloader. * <p> * It's possible to add urls to the classpath at runtime through * {@link <a href="#addURL(URL)">addURL(URL)</a>} * <p> * <b>Why using RootLoader?</b> * If you have to load classes with multiple classloaders and a * classloader does know a class which depends on a class only * a child of this loader does know, then you won't be able to * load the class. To load the class the child is not allowed * to redirect it's search for the class to the parent first. * That way the child can load the class. If the child does not * have all classes to do this, this fails of course. * <p> * For example: * <p> * <pre> * parentLoader (has classpath: a.jar;c.jar) * | * | * childLoader (has classpath: a.jar;b.jar;c.jar) * </pre> * * class C (from c.jar) extends B (from b.jar) * * childLoader.find("C") * --> parentLoader does know C.class, try to load it * --> to load C.class it has to load B.class * --> parentLoader is unable to find B.class in a.jar or c.jar * --> NoClassDefFoundException! * * if childLoader had tried to load the class by itself, there * would be no problem. Changing childLoader to be a RootLoader * instance will solve that problem. * * @author Jochen Theodorou */ public class RootLoader extends URLClassLoader { private static final URL[] EMPTY_URL_ARRAY = new URL[0]; private final Map<String, Class> customClasses = new HashMap<String, Class>(); /** * constructs a new RootLoader without classpath * * @param parent the parent Loader */ private RootLoader(ClassLoader parent) { this(EMPTY_URL_ARRAY, parent); } /** * constructs a new RootLoader with a parent loader and an * array of URLs as classpath */ public RootLoader(URL[] urls, ClassLoader parent) { super(urls, parent); // major hack here...! try { customClasses.put("org.w3c.dom.Node", super.loadClass("org.w3c.dom.Node", false)); } catch (Exception e) { /* ignore */ } } private static ClassLoader chooseParent() { ClassLoader cl = RootLoader.class.getClassLoader(); if (cl != null) return cl; return ClassLoader.getSystemClassLoader(); } /** * constructs a new RootLoader with a {@link LoaderConfiguration} * object which holds the classpath */ public RootLoader(LoaderConfiguration lc) { this(chooseParent()); Thread.currentThread().setContextClassLoader(this); URL[] urls = lc.getClassPathUrls(); for (URL url : urls) { addURL(url); } // TODO M12N eventually defer this until later when we have a full Groovy // environment and use normal Grape.grab() String groovyHome = System.getProperty("groovy.home"); List<String> grabUrls = lc.getGrabUrls(); for (String grabUrl : grabUrls) { Map<String, Object> grabParts = GrapeUtil.getIvyParts(grabUrl); String group = grabParts.get("group").toString(); String module = grabParts.get("module").toString(); String name = grabParts.get("module").toString() + "-" + grabParts.get("version") + ".jar"; File jar = new File(groovyHome + "/repo/" + group + "/" + module + "/jars/" + name); try { addURL(jar.toURI().toURL()); } catch (MalformedURLException e) { // ignore } } } /** * loads a class using the name of the class */ protected synchronized Class loadClass(final String name, boolean resolve) throws ClassNotFoundException { Class c = this.findLoadedClass(name); if (c != null) return c; c = (Class) customClasses.get(name); if (c != null) return c; try { c = oldFindClass(name); } catch (ClassNotFoundException cnfe) { // IGNORE } if (c == null) c = super.loadClass(name, resolve); if (resolve) resolveClass(c); return c; } /** * returns the URL of a resource, or null if it is not found */ public URL getResource(String name) { URL url = findResource(name); if (url == null) url = super.getResource(name); return url; } /** * adds an url to the classpath of this classloader */ public void addURL(URL url) { super.addURL(url); } private Class oldFindClass(String name) throws ClassNotFoundException { return super.findClass(name); } protected Class findClass(String name) throws ClassNotFoundException { throw new ClassNotFoundException(name); } }