/* * Copyright (c) 2015, SQL Power Group Inc. * * This file is part of SQL Power Library. * * SQL Power Library is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * SQL Power Library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package ca.sqlpower.util; import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; import java.net.URL; import java.util.Enumeration; import java.util.LinkedList; import java.util.List; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.zip.ZipEntry; /** * Scans a jar file for instances based on the implemented {@link #checkClass(Class)} method. */ public abstract class JarScanClassLoader extends ClassLoader { private static final org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger(JarScanClassLoader.class); private List<String> drivers; private int count = 0; private JarURLConnection jarConnection; private JarFile jf; /** * Creates a class loader that can scan the given JAR for classes matching * the {@link #checkClass(Class)} implementation. Uses this class's class * loader as its parent. * * @param jarLocation * The JAR to scan. This URL must <i>not</i> be a jar: URL; it * will be converted to one within this constructor. * @throws IOException */ public JarScanClassLoader(URL jarLocation) throws IOException { super(); URL jarURL = new URL("jar:" + jarLocation + "!/"); jarConnection = (JarURLConnection) jarURL.openConnection(); jf = jarConnection.getJarFile(); } public synchronized double getFraction() { double retval = 0.0; if (jf != null) { retval = (double)count/(double)jf.size(); } return retval; } /** * Returns a list of Strings naming the subclasses of * java.sql.Driver which exist in this class loader's jar * file. */ public List<String> scanForDrivers() { drivers = new LinkedList<String>(); logger.debug("********* " + jf.getName() + " has " + jf.size() + " files."); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ for (Enumeration<JarEntry> entries = jf.entries(); entries.hasMoreElements(); ) { count++; ZipEntry ent = (ZipEntry) entries.nextElement(); if (ent.getName().endsWith(".class")) { //$NON-NLS-1$ try { // drop the .class from the name String [] s = ent.getName().split("\\."); //$NON-NLS-1$ // look for the class using dots instead of slashes findClass(s[0].replace('/','.')); } catch (ClassFormatError ex) { logger.warn("JAR entry "+ent.getName()+" ends in .class but is not a class", ex); //$NON-NLS-1$ //$NON-NLS-2$ } catch (NoClassDefFoundError ex) { logger.warn("JAR does not contain dependency needed by: " + ent.getName()); //$NON-NLS-1$ } catch (Throwable ex) { logger.warn("Unexpected exception while scanning JAR file "+jf.getName(), ex); //$NON-NLS-1$ } } } //jf.close(); return drivers; } /** * Searches this ClassLoader's jar file for the given class. * * @throws ClassNotFoundException if the class can't be * located. */ protected Class<?> findClass(String name) throws ClassNotFoundException { //logger.debug("Looking for class "+name); try { ZipEntry ent = jf.getEntry(name.replace('.', '/')+".class"); //$NON-NLS-1$ if (ent == null) { throw new ClassNotFoundException("No class file "+name+" is in my jar file"); //$NON-NLS-1$ //$NON-NLS-2$ } // can we find out here if it was already loaded??? Class<?> clazz = findLoadedClass(name); if (clazz != null) { return clazz; } // haven't seen this before, so go get it... InputStream is = jf.getInputStream(ent); return readAndCheckClass(is, (int) ent.getSize(), name); } catch (IOException ex) { throw new ClassNotFoundException("IO Exception reading class from jar file", ex); //$NON-NLS-1$ } } private Class<?> readAndCheckClass(InputStream is, int size, String expectedName) throws IOException, ClassFormatError { byte[] buf = new byte[size]; int offs = 0, n; while ( (n = is.read(buf, offs, size-offs)) >= 0 && offs < size) { offs += n; } final int total = offs; if (total != size) { logger.warn("Only read "+total+" bytes of class " //$NON-NLS-1$ //$NON-NLS-2$ +expectedName+" from JAR file; exptected "+size); //$NON-NLS-1$ } Class<?> clazz = defineClass(expectedName, buf, 0, total); if (checkClass(clazz)) { logger.info("Found jdbc driver "+clazz.getName()); //$NON-NLS-1$ drivers.add(clazz.getName()); } return clazz; } protected abstract boolean checkClass(Class<?> clazz); }