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