2 * Copyright (C) 2007 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.dx.cf.direct;
19 import com.android.dex.util.FileUtils;
21 import java.io.ByteArrayOutputStream;
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;
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
37 public class ClassPathOpener {
39 /** {@code non-null;} pathname to start with */
40 private final String pathname;
41 /** {@code non-null;} callback interface */
42 private final Consumer consumer;
44 * If true, sort such that classes appear before their inner
45 * classes and "package-info" occurs before all other classes in that
48 private final boolean sort;
49 private FileNameFilter filter;
52 * Callback interface for {@code ClassOpener}.
54 public interface Consumer {
57 * Provides the file name and byte array for a class path element.
59 * @param name {@code non-null;} filename of element. May not be a valid
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()}.
68 boolean processFileBytes(String name, long lastModified, byte[] bytes);
71 * Informs consumer that an exception occurred while processing
72 * this path element. Processing will continue if possible.
74 * @param ex {@code non-null;} exception
76 void onException(Exception ex);
79 * Informs consumer that processing of an archive file has begun.
81 * @param file {@code non-null;} archive file being processed
83 void onProcessArchiveStart(File file);
87 * Filter interface for {@code ClassOpener}.
89 public interface FileNameFilter {
91 boolean accept(String path);
95 * An accept all filter.
97 public static final FileNameFilter acceptAll = new FileNameFilter() {
100 public boolean accept(String path) {
106 * Constructs an instance.
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
112 * @param consumer {@code non-null;} callback interface
114 public ClassPathOpener(String pathname, boolean sort, Consumer consumer) {
115 this(pathname, sort, acceptAll, consumer);
119 * Constructs an instance.
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
125 * @param consumer {@code non-null;} callback interface
127 public ClassPathOpener(String pathname, boolean sort, FileNameFilter filter,
129 this.pathname = pathname;
131 this.consumer = consumer;
132 this.filter = filter;
136 * Processes a path element.
138 * @return the OR of all return values
139 * from {@code Consumer.processFileBytes()}.
141 public boolean process() {
142 File file = new File(pathname);
144 return processOne(file, true);
148 * Processes one file.
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
155 private boolean processOne(File file, boolean topLevel) {
157 if (file.isDirectory()) {
158 return processDirectory(file, topLevel);
161 String path = file.getPath();
163 if (path.endsWith(".zip") ||
164 path.endsWith(".jar") ||
165 path.endsWith(".apk")) {
166 return processArchive(file);
168 if (filter.accept(path)) {
169 byte[] bytes = FileUtils.readFile(file);
170 return consumer.processFileBytes(path, file.lastModified(), bytes);
174 } catch (Exception ex) {
175 consumer.onException(ex);
181 * Sorts java class names such that outer classes preceed their inner
182 * classes and "package-info" preceeds all other classes in its package.
184 * @param a {@code non-null;} first class name
185 * @param b {@code non-null;} second class name
186 * @return {@code compareTo()}-style result
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');
194 * Assuming "package-info" only occurs at the end, ensures package-info
197 a = a.replace("package-info", "");
198 b = b.replace("package-info", "");
200 return a.compareTo(b);
204 * Processes a directory recursively.
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
211 private boolean processDirectory(File dir, boolean topLevel) {
213 dir = new File(dir, ".");
216 File[] files = dir.listFiles();
217 int len = files.length;
221 Arrays.sort(files, new Comparator<File>() {
222 public int compare(File a, File b) {
223 return compareClassNames(a.getName(), b.getName());
228 for (int i = 0; i < len; i++) {
229 any |= processOne(files[i], false);
236 * Processes the contents of an archive ({@code .zip},
237 * {@code .jar}, or {@code .apk}).
239 * @param file {@code non-null;} archive file to process
240 * @return whether any processing actually happened
241 * @throws IOException on i/o problem
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];
249 ArrayList<? extends java.util.zip.ZipEntry> entriesList
250 = Collections.list(zip.entries());
253 Collections.sort(entriesList, new Comparator<ZipEntry>() {
254 public int compare (ZipEntry a, ZipEntry b) {
255 return compareClassNames(a.getName(), b.getName());
260 consumer.onProcessArchiveStart(file);
262 for (ZipEntry one : entriesList) {
263 if (one.isDirectory()) {
267 String path = one.getName();
268 if (filter.accept(path)) {
269 InputStream in = zip.getInputStream(one);
273 int amt = in.read(buf);
278 baos.write(buf, 0, amt);
283 byte[] bytes = baos.toByteArray();
284 any |= consumer.processFileBytes(path, one.getTime(), bytes);