org.impalaframework.classloader.FileSystemClassLoader.java Source code

Java tutorial

Introduction

Here is the source code for org.impalaframework.classloader.FileSystemClassLoader.java

Source

/*
 * Copyright 2007-2010 the original author or authors.
 * 
 * Licensed 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.impalaframework.classloader;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.impalaframework.util.FileUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ClassUtils;

/**
 * <code>ClassLoader</code> which resolves a particular class or resource from
 * one of a set of locations on the file system. Note that this class is
 * abstract as it does not override the superclass's <code>loadClass()</code>
 * method.
 * 
 * @author Phil Zoio
 */
public abstract class FileSystemClassLoader extends ClassLoader {

    private static final Log logger = LogFactory.getLog(FileSystemClassLoader.class);

    private File[] locations;

    private Map<String, Class<?>> loadedClasses = new ConcurrentHashMap<String, Class<?>>();

    /**
     * Constructs this class loader with a set of <code>File</code> locations
     * from which the class can be be loaded
     */
    public FileSystemClassLoader(File[] locations) {
        super(ClassUtils.getDefaultClassLoader());
        this.locations = locations;
    }

    /**
     * As with the overloaded constructor, except that it provides a parent
     * <code>ClassLoader</code>.
     */
    public FileSystemClassLoader(ClassLoader parent, File[] locations) {
        super(parent);
        this.locations = locations;
    }

    /**
     * Attempts to load class from one of the locations supplied via a
     * constructor. Designed to be used by subclasses.
     * @param className name of class to load
     * @return a <code>Class</code> instance, if the class could be loaded,
     * otherwise null.
     */
    protected Class<?> loadCustomClass(String className) {

        byte[] classData = null;

        try {

            classData = findClassBytes(className);

            if (classData != null) {
                Class<?> result = defineClass(className, classData, 0, classData.length, null);

                if (logger.isDebugEnabled())
                    debug("Returning class newly loaded from custom location: {}" + className);

                loadedClasses.put(className, result);

                logger.info("ModuleClassLoader: " + className + " loaded by " + result.getClassLoader());
            }

            return null;

        } catch (IOException e) {
            logger.error("IOException attempting to read class " + className + " from location(s) "
                    + Arrays.toString(locations));
            return null;
        } catch (ClassFormatError e) {
            logger.error(
                    "Invalid format for class " + className + " from location(s) " + Arrays.toString(locations));
            return null;
        }
    }

    protected byte[] findClassBytes(String className) throws IOException {

        byte[] classData = null;

        String relativeClassFile = className.replace('.', '/') + ".class";
        for (int i = 0; i < locations.length; i++) {

            File file = new File(locations[i], relativeClassFile);
            if (file.exists()) {

                classData = FileUtils.getBytes(file);
                break;
            }
        }
        return classData;
    }

    /**
     * Returns a cached <code>Class</code> instance, if it has already been
     * loaded using the <code>ClassLoader</code>. Designed to be used by
     * subclasses.
     * @param className the name of the class to return
     * @return returns the cached <code>Class</code> instance, if previously
     * loaded using this <code>ClassLoader</code>, otherwise null.
     */
    protected Class<?> getAlreadyLoadedClass(String className) {
        Class<?> loadedClass = loadedClasses.get(className);
        if (loadedClass != null) {
            if (logger.isDebugEnabled()) {
                debug("Returning already loaded custom class: " + className);
            }
            return loadedClass;
        }
        return loadedClass;
    }

    /**
     * Attempts to load the designated class by delegating to the parent class
     * loader.
     * @param className the name of the class to load
     * @return a <code>Class</code> instance successfully loaded, otherwise
     * null
     */
    protected Class<?> loadParentClass(String className) {
        try {
            Class<?> parentClass = getParent().loadClass(className);

            if (logger.isDebugEnabled()) {
                debug("Returning from parent class loader {}: {}" + getParent() + ": " + parentClass);
            }

            return parentClass;
        } catch (Exception e) {
        }
        return null;
    }

    /**
     * Attempt to load a resoure, first by calling
     * <code>getCustomResource</code>. If the resource is not found
     * <code>super.getResource(name)</code> is called.
     */
    @Override
    public URL getResource(String name) {

        final URL url = getCustomResource(name);
        if (url != null) {
            return url;
        }

        return super.getResource(name);
    }

    /**
     * Attempts to find a resource from one of the file system locations
     * specified in a constructor.
     * @param name the name of the resource to load
     * @return a <code>URL</code> instance, if the resource can be found,
     * otherwise null.
     */
    protected URL getCustomResource(String name) {
        try {
            for (int i = 0; i < locations.length; i++) {
                File file = new File(locations[i], name);
                if (file.exists()) {
                    URL url = file.toURI().toURL();
                    return url;
                }
            }

        } catch (IOException e) {
            logger.error("IOException attempting to load resource " + name + " from location(s) "
                    + Arrays.toString(locations));
        }
        return null;
    }

    /**
     * Returns an immutable map of name to <code>Class</code> instances loaded
     * via this class loader
     * @return
     */
    public Map<String, Class<?>> getLoadedClasses() {
        return Collections.unmodifiableMap(loadedClasses);
    }

    protected File[] getLocations() {
        return locations;
    }

    private void debug(String message) {
        logger.debug(this.getClass().getSimpleName() + "[" + System.identityHashCode(this) + "]: " + message);
    }

}