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.ByteArrayOutputStream;
020    import java.io.File;
021    import java.io.IOException;
022    import java.io.InputStream;
023    import java.net.URL;
024    import java.util.ArrayList;
025    import java.util.Iterator;
026    import java.util.List;
027    
028    /**
029     * @version $Rev$ $Date$
030     */
031    public class FileArchive implements Archive {
032    
033        private final ClassLoader loader;
034        private final File dir;
035    
036        public FileArchive(ClassLoader loader, URL url) {
037            this.loader = loader;
038            this.dir = toFile(url);
039        }
040    
041        public FileArchive(ClassLoader loader, File dir) {
042            this.loader = loader;
043            this.dir = dir;
044        }
045    
046        @Override
047        public InputStream getBytecode(String className) throws IOException, ClassNotFoundException {
048            int pos = className.indexOf("<");
049            if (pos > -1) {
050                className = className.substring(0, pos);
051            }
052            pos = className.indexOf(">");
053            if (pos > -1) {
054                className = className.substring(0, pos);
055            }
056            if (!className.endsWith(".class")) {
057                className = className.replace('.', '/') + ".class";
058            }
059    
060            URL resource = loader.getResource(className);
061            if (resource != null) return resource.openStream();
062    
063            throw new ClassNotFoundException(className);
064        }
065    
066    
067        @Override
068        public Class<?> loadClass(String className) throws ClassNotFoundException {
069            return loader.loadClass(className);
070        }
071    
072        @Override
073        public Iterator<String> iterator() {
074            return file(dir).iterator();
075        }
076    
077        private List<String> file(File dir) {
078            List<String> classNames = new ArrayList<String>();
079            if (dir.isDirectory()) {
080                scanDir(dir, classNames, "");
081            }
082            return classNames;
083        }
084    
085        private void scanDir(File dir, List<String> classNames, String packageName) {
086            File[] files = dir.listFiles();
087            for (File file : files) {
088                if (file.isDirectory()) {
089                    scanDir(file, classNames, packageName + file.getName() + ".");
090                } else if (file.getName().endsWith(".class")) {
091                    String name = file.getName();
092                    name = name.replaceFirst(".class$", "");
093                    if (name.contains(".")) continue;
094                    classNames.add(packageName + name);
095                }
096            }
097        }
098    
099        private static File toFile(URL url) {
100            if (!"file".equals(url.getProtocol())) throw new IllegalArgumentException("not a file url: " + url);
101            String path = url.getFile();
102            File dir = new File(decode(path));
103            if (dir.getName().equals("META-INF")) {
104                dir = dir.getParentFile(); // Scrape "META-INF" off
105            }
106            return dir;
107        }
108    
109        public static String decode(String fileName) {
110            if (fileName.indexOf('%') == -1) return fileName;
111    
112            StringBuilder result = new StringBuilder(fileName.length());
113            ByteArrayOutputStream out = new ByteArrayOutputStream();
114    
115            for (int i = 0; i < fileName.length();) {
116                char c = fileName.charAt(i);
117    
118                if (c == '%') {
119                    out.reset();
120                    do {
121                        if (i + 2 >= fileName.length()) {
122                            throw new IllegalArgumentException("Incomplete % sequence at: " + i);
123                        }
124    
125                        int d1 = Character.digit(fileName.charAt(i + 1), 16);
126                        int d2 = Character.digit(fileName.charAt(i + 2), 16);
127    
128                        if (d1 == -1 || d2 == -1) {
129                            throw new IllegalArgumentException("Invalid % sequence (" + fileName.substring(i, i + 3) + ") at: " + String.valueOf(i));
130                        }
131    
132                        out.write((byte) ((d1 << 4) + d2));
133    
134                        i += 3;
135    
136                    } while (i < fileName.length() && fileName.charAt(i) == '%');
137    
138    
139                    result.append(out.toString());
140    
141                    continue;
142                } else {
143                    result.append(c);
144                }
145    
146                i++;
147            }
148            return result.toString();
149        }
150    }