OSDN Git Service

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