001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     *  Unless required by applicable law or agreed to in writing, software
012     *  distributed under the License is distributed on an "AS IS" BASIS,
013     *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     *  See the License for the specific language governing permissions and
015     *  limitations under the License.
016     */
017    package org.apache.xbean.finder.archive;
018    
019    import java.io.File;
020    import java.io.IOException;
021    import java.io.InputStream;
022    import java.net.URL;
023    import java.util.ArrayList;
024    import java.util.Enumeration;
025    import java.util.Iterator;
026    import java.util.List;
027    import java.util.jar.JarEntry;
028    import java.util.jar.JarFile;
029    import java.util.jar.JarInputStream;
030    
031    /**
032     * @version $Rev$ $Date$
033     */
034    public class JarArchive implements Archive {
035    
036        private final ClassLoader loader;
037        private final URL url;
038    
039        public JarArchive(ClassLoader loader, URL url) {
040            if (!"jar".equals(url.getProtocol())) throw new IllegalArgumentException("not a file url: " + url);
041            this.loader = loader;
042            this.url = url;
043        }
044    
045        @Override
046        public InputStream getBytecode(String className) throws IOException, ClassNotFoundException {
047            int pos = className.indexOf("<");
048            if (pos > -1) {
049                className = className.substring(0, pos);
050            }
051            pos = className.indexOf(">");
052            if (pos > -1) {
053                className = className.substring(0, pos);
054            }
055            if (!className.endsWith(".class")) {
056                className = className.replace('.', '/') + ".class";
057            }
058    
059            URL resource = loader.getResource(className);
060            if (resource != null) return resource.openStream();
061    
062            throw new ClassNotFoundException(className);
063        }
064    
065    
066        @Override
067        public Class<?> loadClass(String className) throws ClassNotFoundException {
068            return loader.loadClass(className);
069        }
070    
071        @Override
072        public Iterator<String> iterator() {
073            try {
074                return jar(url).iterator();
075            } catch (IOException e) {
076                throw new IllegalStateException(e);
077            }
078        }
079    
080        private List<String> jar(URL location) throws IOException {
081            String jarPath = location.getFile();
082            if (jarPath.indexOf("!") > -1){
083                jarPath = jarPath.substring(0, jarPath.indexOf("!"));
084            }
085            URL url = new URL(jarPath);
086            if ("file".equals(url.getProtocol())) { // ZipFile is faster than ZipInputStream
087                JarFile jarFile = new JarFile(url.getFile().replace("%20", " "));
088                return jar(jarFile);
089            } else {
090                InputStream in = url.openStream();
091                try {
092                    JarInputStream jarStream = new JarInputStream(in);
093                    return jar(jarStream);
094                } finally {
095                    in.close();
096                }
097            }
098        }
099    
100        private List<String> jar(JarFile jarFile) {
101            List<String> classNames = new ArrayList<String>();
102    
103            Enumeration<? extends JarEntry> jarEntries =jarFile.entries();
104            while (jarEntries.hasMoreElements()) {
105                JarEntry entry = jarEntries.nextElement();
106                addClassName(classNames, entry);
107            }
108    
109            return classNames;
110        }
111    
112        private List<String> jar(JarInputStream jarStream) throws IOException {
113            List<String> classNames = new ArrayList<String>();
114    
115            JarEntry entry;
116            while ((entry = jarStream.getNextJarEntry()) != null) {
117                addClassName(classNames, entry);
118            }
119    
120            return classNames;
121        }
122    
123        private void addClassName(List<String> classNames, JarEntry entry) {
124            if (entry.isDirectory() || !entry.getName().endsWith(".class")) {
125                return;
126            }
127            String className = entry.getName();
128            className = className.replaceFirst(".class$", "");
129            if (className.contains(".")) {
130                return;
131            }
132            className = className.replace(File.separatorChar, '.');
133            classNames.add(className);
134        }
135    }
136