001    /*
002     * $Id: XrayClassLoader.java,v 1.4 2011/07/09 21:43:23 oboehm Exp $
003     *
004     * Copyright (c) 2010 by Oliver Boehm
005     *
006     * Licensed under the Apache License, Version 2.0 (the "License");
007     * you may not use this file except in compliance with the License.
008     * You may obtain a copy of the License at
009     *
010     *   http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     *
018     * (c)reated 26.12.2010 by oliver (ob@oasd.de)
019     */
020    
021    package patterntesting.runtime.experimental;
022    
023    import java.io.*;
024    import java.net.URI;
025    import java.util.*;
026    import java.util.jar.*;
027    
028    import org.apache.commons.io.*;
029    import org.apache.commons.lang.StringUtils;
030    import org.slf4j.*;
031    
032    import patterntesting.runtime.monitor.ClasspathMonitor;
033    import patterntesting.runtime.util.Converter;
034    
035    /**
036     * If we want to load a class and see what happens if another class (needed by
037     * the original class) is missing we need a class loader where we can control
038     * the classpath and other things.
039     *
040     * @author oliver
041     * @since 1.1 (26.12.2010)
042     */
043    public final class XrayClassLoader extends ClassLoader {
044    
045        private static final ClasspathMonitor classpathMonitor = ClasspathMonitor.getInstance();
046        private static final Logger log = LoggerFactory.getLogger(XrayClassLoader.class);
047        private final Map<String, Class<?>> loadedClassMap = new HashMap<String, Class<?>>();
048    
049        /**
050         * Load class.
051         *
052         * @param classname the classname
053         * @return the class
054         * @throws ClassNotFoundException the class not found exception
055         * @see java.lang.ClassLoader#loadClass(java.lang.String)
056         */
057        @Override
058        public Class<?> loadClass(final String classname) throws ClassNotFoundException {
059            Class<?> loaded = loadedClassMap.get(classname);
060            if (loaded == null) {
061                try {
062                    loaded = findClass(classname);
063                } catch (SecurityException ce) {
064                    log.debug(ce.getLocalizedMessage() + " - using parent to load " + classname);
065                    loaded = super.loadClass(classname);
066                }
067                loadedClassMap.put(classname, loaded);
068            }
069            return loaded;
070        }
071    
072        /**
073         * Gets the loaded classed of this classloader here.
074         *
075         * @return the loaded classed
076         */
077        public Set<Class<?>> getLoadedClasses() {
078            return new HashSet<Class<?>>(this.loadedClassMap.values());
079        }
080    
081        /**
082         * Find class.
083         *
084         * @param classname the classname
085         * @return the class
086         * @throws ClassNotFoundException the class not found exception
087         * @see java.lang.ClassLoader#findClass(java.lang.String)
088         */
089        @Override
090        protected Class<?> findClass(final String classname) throws ClassNotFoundException {
091            URI classUri = classpathMonitor.whichClass(classname);
092            try {
093                byte[] data = read(classUri);
094                return defineClass(classname, data, 0, data.length);
095            } catch (IOException ioe) {
096                throw new ClassNotFoundException("can't load class " + classname, ioe);
097            }
098        }
099    
100        private static byte[] read(final URI uri) throws IOException {
101            log.trace("loading " + uri + "...");
102            try {
103                File file = new File(uri);
104                return FileUtils.readFileToByteArray(file);
105            } catch (IllegalArgumentException iae) {
106                String scheme = uri.getScheme();
107                if ("jar".equals(scheme)) {
108                    return readJar(uri);
109                } else {
110                    throw new IllegalArgumentException("don't know how to load " + uri);
111                }
112            }
113        }
114    
115        private static byte[] readJar(final URI uri) throws IOException {
116            File file = Converter.toFile(StringUtils.substringBefore(uri.toString(), "!"));
117            String classpath = StringUtils.substringAfterLast(uri.toString(), "!");
118            JarFile jarFile = new JarFile(file);
119            JarEntry entry = getEntry(classpath, jarFile);
120            InputStream istream = jarFile.getInputStream(entry);
121            try {
122                return IOUtils.toByteArray(istream);
123            } finally {
124                istream.close();
125            }
126        }
127    
128        private static JarEntry getEntry(final String classpath, final JarFile jarFile) {
129            JarEntry entry = jarFile.getJarEntry(classpath);
130            if (entry == null) {
131                entry = jarFile.getJarEntry(classpath.substring(1));
132            }
133            return entry;
134        }
135    
136    }
137