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