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.FileInputStream; 023import java.io.IOException; 024import java.io.InputStream; 025import java.net.URL; 026import java.util.ArrayList; 027import java.util.Iterator; 028import java.util.List; 029 030/** 031 * @version $Rev$ $Date$ 032 */ 033public class FileArchive implements Archive { 034 035 private final ClassLoader loader; 036 private final String basePackage; 037 private final File dir; 038 private List<String> list; 039 private final MJarSupport mjar = new MJarSupport(); 040 041 public FileArchive(ClassLoader loader, URL url) { 042 this.loader = loader; 043 this.basePackage = ""; 044 this.dir = toFile(url); 045 } 046 047 public FileArchive(ClassLoader loader, File dir) { 048 this.loader = loader; 049 this.basePackage = ""; 050 this.dir = dir; 051 } 052 053 public FileArchive(ClassLoader loader, URL url, String basePackage) { 054 this.loader = loader; 055 this.basePackage = basePackage; 056 this.dir = toFile(url); 057 } 058 059 public FileArchive(ClassLoader loader, File dir, String basePackage) { 060 this.loader = loader; 061 this.basePackage = basePackage; 062 this.dir = dir; 063 } 064 065 public File getDir() { 066 return dir; 067 } 068 069 public InputStream getBytecode(String className) throws IOException, ClassNotFoundException { 070 int pos = className.indexOf("<"); 071 if (pos > -1) { 072 className = className.substring(0, pos); 073 } 074 pos = className.indexOf(">"); 075 if (pos > -1) { 076 className = className.substring(0, pos); 077 } 078 if (!className.endsWith(".class")) { 079 className = className.replace('.', '/') + ".class"; 080 } 081 082 if (mjar.isMjar()) { 083 final MJarSupport.Clazz resource = mjar.getClasses().get(className); 084 if (resource != null) { 085 className = resource.getPath() + ".class"; 086 } 087 } 088 089 URL resource = loader.getResource(className); 090 if (resource != null) return new BufferedInputStream(resource.openStream()); 091 092 throw new ClassNotFoundException(className); 093 } 094 095 096 public Class<?> loadClass(String className) throws ClassNotFoundException { 097 // we assume the loader supports mjar if needed, do we want to wrap it to enforce it? 098 // probably not otherwise runtime will be weird and unexpected no? 099 return loader.loadClass(className); 100 } 101 102 public Iterator<Entry> iterator() { 103 return new ArchiveIterator(this, _iterator()); 104 } 105 106 public Iterator<String> _iterator() { 107 if (list != null) return list.iterator(); 108 109 final File manifest = new File(dir, "META-INF/MANIFEST.MF"); 110 if (manifest.exists()) { 111 InputStream is = null; 112 try { 113 is = new FileInputStream(manifest); 114 mjar.load(is); 115 } catch (final IOException e) { 116 // no-op 117 } finally { 118 if (is != null) { 119 try { 120 is.close(); 121 } catch (final IOException e) { 122 // no-op 123 } 124 } 125 } 126 } 127 128 list = file(dir); 129 return list.iterator(); 130 } 131 132 private List<String> file(File dir) { 133 List<String> classNames = new ArrayList<String>(); 134 if (dir.isDirectory()) { 135 scanDir(dir, classNames, (basePackage.length() > 0) ? (basePackage + ".") : basePackage); 136 } 137 return classNames; 138 } 139 140 private void scanDir(File dir, List<String> classNames, String packageName) { 141 File[] files = dir.listFiles(); 142 // using /tmp/. as dir we can get null 143 if (files == null) { 144 return; 145 } 146 for (File file : files) { 147 if (file.isDirectory()) { 148 scanDir(file, classNames, packageName + file.getName() + "."); 149 } else if (file.getName().endsWith(".class")) { 150 String name = file.getName(); 151 name = name.substring(0, name.length() - 6); 152 if (name.contains(".") || name.equals("module-info") /*todo?*/) continue; 153 if (packageName.startsWith("META-INF.versions")) { 154 if (mjar.isMjar()) { 155 mjar.visit(packageName + name); 156 continue; 157 } 158 } 159 classNames.add(packageName + name); 160 } 161 } 162 } 163 164 private static File toFile(URL url) { 165 if (!"file".equals(url.getProtocol())) throw new IllegalArgumentException("not a file url: " + url); 166 String path = url.getFile(); 167 File dir = new File(decode(path)); 168 if (dir.getName().equals("META-INF")) { 169 dir = dir.getParentFile(); // Scrape "META-INF" off 170 } 171 return dir; 172 } 173 174 public static String decode(String fileName) { 175 if (fileName.indexOf('%') == -1) return fileName; 176 177 StringBuilder result = new StringBuilder(fileName.length()); 178 ByteArrayOutputStream out = new ByteArrayOutputStream(); 179 180 for (int i = 0; i < fileName.length();) { 181 char c = fileName.charAt(i); 182 183 if (c == '%') { 184 out.reset(); 185 do { 186 if (i + 2 >= fileName.length()) { 187 throw new IllegalArgumentException("Incomplete % sequence at: " + i); 188 } 189 190 int d1 = Character.digit(fileName.charAt(i + 1), 16); 191 int d2 = Character.digit(fileName.charAt(i + 2), 16); 192 193 if (d1 == -1 || d2 == -1) { 194 throw new IllegalArgumentException("Invalid % sequence (" + fileName.substring(i, i + 3) + ") at: " + String.valueOf(i)); 195 } 196 197 out.write((byte) ((d1 << 4) + d2)); 198 199 i += 3; 200 201 } while (i < fileName.length() && fileName.charAt(i) == '%'); 202 203 204 result.append(out.toString()); 205 206 continue; 207 } else { 208 result.append(c); 209 } 210 211 i++; 212 } 213 return result.toString(); 214 } 215}