OSDN Git Service

c9fe275cd68be72eefbdd4da95596d2de642c26e
[android-x86/dalvik.git] / dx / src / com / android / dx / cf / direct / ClassPathOpener.java
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
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
17 package com.android.dx.cf.direct;
18
19 import com.android.dex.util.FileUtils;
20
21 import java.io.ByteArrayOutputStream;
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.Comparator;
29 import java.util.zip.ZipEntry;
30 import java.util.zip.ZipFile;
31
32 /**
33  * Opens all the class files found in a class path element. Path elements
34  * can point to class files, {jar,zip,apk} files, or directories containing
35  * class files.
36  */
37 public class ClassPathOpener {
38
39     /** {@code non-null;} pathname to start with */
40     private final String pathname;
41     /** {@code non-null;} callback interface */
42     private final Consumer consumer;
43     /**
44      * If true, sort such that classes appear before their inner
45      * classes and "package-info" occurs before all other classes in that
46      * package.
47      */
48     private final boolean sort;
49     private FileNameFilter filter;
50
51     /**
52      * Callback interface for {@code ClassOpener}.
53      */
54     public interface Consumer {
55
56         /**
57          * Provides the file name and byte array for a class path element.
58          *
59          * @param name {@code non-null;} filename of element. May not be a valid
60          * filesystem path.
61          *
62          * @param lastModified milliseconds since 1970-Jan-1 00:00:00 GMT
63          * @param bytes {@code non-null;} file data
64          * @return true on success. Result is or'd with all other results
65          * from {@code processFileBytes} and returned to the caller
66          * of {@code process()}.
67          */
68         boolean processFileBytes(String name, long lastModified, byte[] bytes);
69
70         /**
71          * Informs consumer that an exception occurred while processing
72          * this path element. Processing will continue if possible.
73          *
74          * @param ex {@code non-null;} exception
75          */
76         void onException(Exception ex);
77
78         /**
79          * Informs consumer that processing of an archive file has begun.
80          *
81          * @param file {@code non-null;} archive file being processed
82          */
83         void onProcessArchiveStart(File file);
84     }
85
86     /**
87      * Filter interface for {@code ClassOpener}.
88      */
89     public interface FileNameFilter {
90
91         boolean accept(String path);
92     }
93
94     /**
95      * An accept all filter.
96      */
97     public static final FileNameFilter acceptAll = new FileNameFilter() {
98
99         @Override
100         public boolean accept(String path) {
101             return true;
102         }
103     };
104
105     /**
106      * Constructs an instance.
107      *
108      * @param pathname {@code non-null;} path element to process
109      * @param sort if true, sort such that classes appear before their inner
110      * classes and "package-info" occurs before all other classes in that
111      * package.
112      * @param consumer {@code non-null;} callback interface
113      */
114     public ClassPathOpener(String pathname, boolean sort, Consumer consumer) {
115         this(pathname, sort, acceptAll, consumer);
116     }
117
118     /**
119      * Constructs an instance.
120      *
121      * @param pathname {@code non-null;} path element to process
122      * @param sort if true, sort such that classes appear before their inner
123      * classes and "package-info" occurs before all other classes in that
124      * package.
125      * @param consumer {@code non-null;} callback interface
126      */
127     public ClassPathOpener(String pathname, boolean sort, FileNameFilter filter,
128             Consumer consumer) {
129         this.pathname = pathname;
130         this.sort = sort;
131         this.consumer = consumer;
132         this.filter = filter;
133     }
134
135     /**
136      * Processes a path element.
137      *
138      * @return the OR of all return values
139      * from {@code Consumer.processFileBytes()}.
140      */
141     public boolean process() {
142         File file = new File(pathname);
143
144         return processOne(file, true);
145     }
146
147     /**
148      * Processes one file.
149      *
150      * @param file {@code non-null;} the file to process
151      * @param topLevel whether this is a top-level file (that is,
152      * specified directly on the commandline)
153      * @return whether any processing actually happened
154      */
155     private boolean processOne(File file, boolean topLevel) {
156         try {
157             if (file.isDirectory()) {
158                 return processDirectory(file, topLevel);
159             }
160
161             String path = file.getPath();
162
163             if (path.endsWith(".zip") ||
164                     path.endsWith(".jar") ||
165                     path.endsWith(".apk")) {
166                 return processArchive(file);
167             }
168             if (filter.accept(path)) {
169                 byte[] bytes = FileUtils.readFile(file);
170                 return consumer.processFileBytes(path, file.lastModified(), bytes);
171             } else {
172                 return false;
173             }
174         } catch (Exception ex) {
175             consumer.onException(ex);
176             return false;
177         }
178     }
179
180     /**
181      * Sorts java class names such that outer classes preceed their inner
182      * classes and "package-info" preceeds all other classes in its package.
183      *
184      * @param a {@code non-null;} first class name
185      * @param b {@code non-null;} second class name
186      * @return {@code compareTo()}-style result
187      */
188     private static int compareClassNames(String a, String b) {
189         // Ensure inner classes sort second
190         a = a.replace('$','0');
191         b = b.replace('$','0');
192
193         /*
194          * Assuming "package-info" only occurs at the end, ensures package-info
195          * sorts first.
196          */
197         a = a.replace("package-info", "");
198         b = b.replace("package-info", "");
199
200         return a.compareTo(b);
201     }
202
203     /**
204      * Processes a directory recursively.
205      *
206      * @param dir {@code non-null;} file representing the directory
207      * @param topLevel whether this is a top-level directory (that is,
208      * specified directly on the commandline)
209      * @return whether any processing actually happened
210      */
211     private boolean processDirectory(File dir, boolean topLevel) {
212         if (topLevel) {
213             dir = new File(dir, ".");
214         }
215
216         File[] files = dir.listFiles();
217         int len = files.length;
218         boolean any = false;
219
220         if (sort) {
221             Arrays.sort(files, new Comparator<File>() {
222                 public int compare(File a, File b) {
223                     return compareClassNames(a.getName(), b.getName());
224                 }
225             });
226         }
227
228         for (int i = 0; i < len; i++) {
229             any |= processOne(files[i], false);
230         }
231
232         return any;
233     }
234
235     /**
236      * Processes the contents of an archive ({@code .zip},
237      * {@code .jar}, or {@code .apk}).
238      *
239      * @param file {@code non-null;} archive file to process
240      * @return whether any processing actually happened
241      * @throws IOException on i/o problem
242      */
243     private boolean processArchive(File file) throws IOException {
244         ZipFile zip = new ZipFile(file);
245         ByteArrayOutputStream baos = new ByteArrayOutputStream(40000);
246         byte[] buf = new byte[20000];
247         boolean any = false;
248
249         ArrayList<? extends java.util.zip.ZipEntry> entriesList
250                 = Collections.list(zip.entries());
251
252         if (sort) {
253             Collections.sort(entriesList, new Comparator<ZipEntry>() {
254                public int compare (ZipEntry a, ZipEntry b) {
255                    return compareClassNames(a.getName(), b.getName());
256                }
257             });
258         }
259
260         consumer.onProcessArchiveStart(file);
261
262         for (ZipEntry one : entriesList) {
263             if (one.isDirectory()) {
264                 continue;
265             }
266
267             String path = one.getName();
268             if (filter.accept(path)) {
269                 InputStream in = zip.getInputStream(one);
270
271                 baos.reset();
272                 for (;;) {
273                     int amt = in.read(buf);
274                     if (amt < 0) {
275                         break;
276                     }
277
278                     baos.write(buf, 0, amt);
279                 }
280
281                 in.close();
282
283                 byte[] bytes = baos.toByteArray();
284                 any |= consumer.processFileBytes(path, one.getTime(), bytes);
285             }
286         }
287
288         zip.close();
289         return any;
290     }
291 }