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