SqlCreatorLoader.java

/* $Id$
 *****************************************************************************
 * Copyright (c) 2009 Contributors - see below
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *    tfmorris
 *****************************************************************************
 *
 * Some portions of this file was previously release using the BSD License:
 */

// Copyright (c) 2007 The Regents of the University of California. All
// Rights Reserved. Permission to use, copy, modify, and distribute this
// software and its documentation without fee, and without a written
// agreement is hereby granted, provided that the above copyright notice
// and this paragraph appear in all copies. This software program and
// documentation are copyrighted by The Regents of the University of
// California. The software program and documentation are supplied "AS
// IS", without any accompanying services from The Regents. The Regents
// does not warrant that the operation of the program will be
// uninterrupted or error-free. The end-user understands that the program
// was developed for research purposes and is advised not to rely
// exclusively on the program for any reason. IN NO EVENT SHALL THE
// UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT,
// SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS,
// ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
// THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
// SUCH DAMAGE. THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
// PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
// CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, SUPPORT,
// UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

package org.argouml.language.sql;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;

import org.apache.log4j.Logger;

/**
 * Class for looking through the argouml extension dir for classes implementing
 * {@link SqlCodeCreator}. Creates an instance of all found classes.
 * <p>
 * TODO: This appears to have been cloned from an earlier version of ArgoUML's
 * ModuleLoader2, but is missing the robustness improvements that were made to
 * it. - tfm 20080905
 * <p>
 * TODO: This should look for some specific naming pattern 
 * (e.g. SqlCodeCreator*) instead of attempting to load every single class in
 * every jar. - tfm
 * <p>
 * @author drahmann
 */
public class SqlCreatorLoader {
    /**
     * Scans through the given jar file looking for classes. If a .class file is
     * found in the .jar a class name is build. Then the specified class loader
     * is used to try to load the class. If the class can be loaded it is added
     * to the result.
     * 
     * @param classLoader
     *            The {@link ClassLoader} to use for loading classes.
     * @param jarFile
     *            The {@link JarFile} to scan through.
     * @return A {@link Collection} of {@link Class} objects.
     */
    private Collection<Class> getClassesFromJar(ClassLoader classLoader,
            JarFile jarFile) {
        Collection<Class> classes = new HashSet<Class>();
        Enumeration<JarEntry> e = jarFile.entries();
        while (e.hasMoreElements()) {
            ZipEntry ze = e.nextElement();
            String name = ze.getName();
            if (name.endsWith(".class")) {
                name = name.substring(0, name.length() - 6);
                name = name.replace("/", ".");

                try {
                    classes.add(classLoader.loadClass(name));
                } catch (ClassNotFoundException e1) {
                    LOG.info("Class could not be loaded from jar: " + name);
                } catch (UnsupportedClassVersionError e1) {
                    LOG.error("Unsupported Java class version for " + name);
                } catch (NoClassDefFoundError e1) {
                    LOG.error("Unable to find required class while loading "
                            + name + " - may indicate an obsolete"
                            + " extension module or an unresolved dependency", e1);
                } catch (Throwable e1) {
                    LOG.error("Unexpected error while loading " + name, e1);
                }
            }
        }
        return classes;
    }

    private static final Logger LOG = Logger.getLogger(SqlCreatorLoader.class);

    /**
     * Scans through the given directory for .jar- and .class-files. If such
     * files are found a class name is build and it is tries to load the class.
     * If it is possible the loaded class is added to the result list.
     * 
     * @param dir
     *            The directory to scan through.
     * @return A {@link Collection} of {@link Class} objects.
     */
    private Collection<Class> getClassesFromDir(File dir) {
        Collection<Class> result = new HashSet<Class>();

        if (!dir.exists()) {
            return result;
        }

        for (File file : dir.listFiles()) {
            if (file.isDirectory()) {
                result.addAll(getClassesFromDir(file));
            } else if (file.getName().endsWith(".jar")) {
                try {
                    JarFile jarFile = new JarFile(file);
                    ClassLoader cl = new URLClassLoader(new URL[] { file
                            .toURL() }, getClass().getClassLoader());
                    result.addAll(getClassesFromJar(cl, jarFile));
                } catch (IOException e) {
                    LOG.error("Exception", e);
                }
            } else if (file.getName().endsWith(".class")) {
                String className = file.getName();
                className = className.substring(0, className.length()
                        - Utils.CLASS_SUFFIX.length());
                String packageName = dir.getAbsolutePath();
                packageName = packageName.substring(moduleDir.getPath()
                        .length());
                if (packageName.startsWith(File.separator)) {
                    packageName = packageName.substring(1);
                }
                packageName = packageName.replace(File.separatorChar, '.');

                className = packageName + "." + className;
                try {
                    result.add(Class.forName(className));
                } catch (ClassNotFoundException e) {
                    LOG.info("Class could not be loaded from .class-file: "
                            + className);
                }
            }
        }

        return result;
    }

    private File moduleDir;

    /**
     * Scans through the argouml extension dir and looks for classes
     * implementing {@link SqlCodeCreator}. If such a class is found, it is
     * tried to instantiate it using the standard constructor. If this can be
     * done the instance is added to the <code>Collection</code> that will be
     * returned.
     * 
     * @return A <code>Collection</code> containing all found classes that
     *         implement {@link SqlCodeCreator}.
     */
    public Collection<Class<SqlCodeCreator>> getCodeCreators() {
        Collection<Class> classes = new HashSet<Class>();
        moduleDir = new File(Utils.getModuleRoot());
        classes.addAll(getClassesFromDir(moduleDir));

        Collection<Class<SqlCodeCreator>> result = 
        	new HashSet<Class<SqlCodeCreator>>();
        for (Class foundClass : classes) {
            if (foundClass != SqlCodeCreator.class
                    && SqlCodeCreator.class.isAssignableFrom(foundClass)) {
                result.add((Class<SqlCodeCreator>) foundClass);
            }
        }

        return result;
    }
}