1/** 2 * Copyright 2007 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.tonicsystems.jarjar.util; 18 19import java.util.*; 20import java.util.zip.*; 21import java.io.*; 22import java.util.jar.*; 23 24public class ClassPathIterator implements Iterator<ClassPathEntry> 25{ 26 private static final FileFilter CLASS_FILTER = new FileFilter() { 27 public boolean accept(File file) { 28 return file.isDirectory() || isClass(file.getName()); 29 } 30 }; 31 32 private static final FileFilter JAR_FILTER = new FileFilter() { 33 public boolean accept(File file) { 34 return hasExtension(file.getName(), ".jar"); 35 } 36 }; 37 38 private final Iterator<File> files; 39 private Iterator<ClassPathEntry> entries = Collections.<ClassPathEntry>emptyList().iterator(); 40 private ClassPathEntry next; 41 private List<ZipFile> zips = new ArrayList<ZipFile>(); 42 43 public ClassPathIterator(String classPath) throws IOException { 44 this(new File(System.getProperty("user.dir")), classPath, null); 45 } 46 47 public ClassPathIterator(File parent, String classPath, String delim) throws IOException { 48 if (delim == null) { 49 delim = System.getProperty("path.separator"); 50 } 51 StringTokenizer st = new StringTokenizer(classPath, delim); 52 List<File> fileList = new ArrayList<File>(); 53 while (st.hasMoreTokens()) { 54 String part = (String)st.nextElement(); 55 boolean wildcard = false; 56 if (part.endsWith("/*")) { 57 part = part.substring(0, part.length() - 1); 58 if (part.indexOf('*') >= 0) 59 throw new IllegalArgumentException("Multiple wildcards are not allowed: " + part); 60 wildcard = true; 61 } else if (part.indexOf('*') >= 0) { 62 throw new IllegalArgumentException("Incorrect wildcard usage: " + part); 63 } 64 65 File file = new File(part); 66 if (!file.isAbsolute()) 67 file = new File(parent, part); 68 if (!file.exists()) 69 throw new IllegalArgumentException("File " + file + " does not exist"); 70 71 if (wildcard) { 72 if (!file.isDirectory()) 73 throw new IllegalArgumentException("File " + file + " + is not a directory"); 74 fileList.addAll(findFiles(file, JAR_FILTER, false, new ArrayList<File>())); 75 } else { 76 fileList.add(file); 77 } 78 } 79 this.files = fileList.iterator(); 80 advance(); 81 } 82 83 public boolean hasNext() { 84 return next != null; 85 } 86 87 /** Closes all zip files opened by this iterator. */ 88 public void close() throws IOException { 89 next = null; 90 for (ZipFile zip : zips) { 91 zip.close(); 92 } 93 } 94 95 public void remove() { 96 throw new UnsupportedOperationException(); 97 } 98 99 public ClassPathEntry next() { 100 if (!hasNext()) 101 throw new NoSuchElementException(); 102 ClassPathEntry result = next; 103 try { 104 advance(); 105 } catch (IOException e) { 106 throw new RuntimeIOException(e); 107 } 108 return result; 109 } 110 111 private void advance() throws IOException { 112 if (!entries.hasNext()) { 113 if (!files.hasNext()) { 114 next = null; 115 return; 116 } 117 File file = files.next(); 118 if (hasExtension(file.getName(), ".jar")) { 119 ZipFile zip = new JarFile(file); 120 zips.add(zip); 121 entries = new ZipIterator(zip); 122 } else if (hasExtension(file.getName(), ".zip")) { 123 ZipFile zip = new ZipFile(file); 124 zips.add(zip); 125 entries = new ZipIterator(zip); 126 } else if (file.isDirectory()) { 127 entries = new FileIterator(file); 128 } else { 129 throw new IllegalArgumentException("Do not know how to handle " + file); 130 } 131 } 132 133 boolean foundClass = false; 134 while (!foundClass && entries.hasNext()) { 135 next = entries.next(); 136 foundClass = isClass(next.getName()); 137 } 138 if (!foundClass) { 139 advance(); 140 } 141 } 142 143 private static class ZipIterator implements Iterator<ClassPathEntry> { 144 private final ZipFile zip; 145 private final Enumeration<? extends ZipEntry> entries; 146 147 ZipIterator(ZipFile zip) { 148 this.zip = zip; 149 this.entries = zip.entries(); 150 } 151 152 public boolean hasNext() { 153 return entries.hasMoreElements(); 154 } 155 156 public void remove() { 157 throw new UnsupportedOperationException(); 158 } 159 160 public ClassPathEntry next() { 161 final ZipEntry entry = entries.nextElement(); 162 return new ClassPathEntry() { 163 public String getSource() { 164 return zip.getName(); 165 } 166 167 public String getName() { 168 return entry.getName(); 169 } 170 171 public InputStream openStream() throws IOException { 172 return zip.getInputStream(entry); 173 } 174 }; 175 } 176 } 177 178 private static class FileIterator implements Iterator<ClassPathEntry> { 179 private final File dir; 180 private final Iterator<File> entries; 181 182 FileIterator(File dir) { 183 this.dir = dir; 184 this.entries = findFiles(dir, CLASS_FILTER, true, new ArrayList<File>()).iterator(); 185 } 186 187 public boolean hasNext() { 188 return entries.hasNext(); 189 } 190 191 public void remove() { 192 throw new UnsupportedOperationException(); 193 } 194 195 public ClassPathEntry next() { 196 final File file = entries.next(); 197 return new ClassPathEntry() { 198 public String getSource() throws IOException { 199 return dir.getCanonicalPath(); 200 } 201 202 public String getName() { 203 return file.getName(); 204 } 205 206 public InputStream openStream() throws IOException { 207 return new BufferedInputStream(new FileInputStream(file)); 208 } 209 }; 210 } 211 } 212 213 private static List<File> findFiles(File dir, FileFilter filter, boolean recurse, List<File> collect) { 214 for (File file : dir.listFiles(filter)) { 215 if (recurse && file.isDirectory()) { 216 findFiles(file, filter, recurse, collect); 217 } else { 218 collect.add(file); 219 } 220 } 221 return collect; 222 } 223 224 private static boolean isClass(String name) { 225 return hasExtension(name, ".class"); 226 } 227 228 private static boolean hasExtension(String name, String ext) { 229 if (name.length() < ext.length()) 230 return false; 231 String actual = name.substring(name.length() - ext.length()); 232 return actual.equals(ext) || actual.equals(ext.toUpperCase()); 233 } 234} 235