1 /*
2  * Copyright (C) 2013 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.multidex;
18 
19 import com.android.dx.cf.direct.DirectClassFile;
20 import com.android.dx.rop.cst.Constant;
21 import com.android.dx.rop.cst.ConstantPool;
22 import com.android.dx.rop.cst.CstType;
23 import com.android.dx.rop.type.Type;
24 import com.android.dx.rop.type.TypeList;
25 
26 import java.io.FileNotFoundException;
27 import java.io.IOException;
28 import java.util.Enumeration;
29 import java.util.HashSet;
30 import java.util.Set;
31 import java.util.zip.ZipEntry;
32 import java.util.zip.ZipFile;
33 
34 /**
35  * Tool to find direct class references to other classes.
36  */
37 public class ClassReferenceListBuilder {
38     private static final String CLASS_EXTENSION = ".class";
39 
40     private Path path;
41     private Set<String> classNames = new HashSet<String>();
42 
ClassReferenceListBuilder(Path path)43     public ClassReferenceListBuilder(Path path) {
44         this.path = path;
45     }
46 
47     /**
48      * Kept for compatibility with the gradle integration, this method just forwards to
49      * {@link MainDexListBuilder#main(String[])}.
50      * @deprecated use {@link MainDexListBuilder#main(String[])} instead.
51      */
52     @Deprecated
main(String[] args)53     public static void main(String[] args) {
54         MainDexListBuilder.main(args);
55     }
56 
57     /**
58      * @param jarOfRoots Archive containing the class files resulting of the tracing, typically
59      * this is the result of running ProGuard.
60      */
addRoots(ZipFile jarOfRoots)61     public void addRoots(ZipFile jarOfRoots) throws IOException {
62 
63         // keep roots
64         for (Enumeration<? extends ZipEntry> entries = jarOfRoots.entries();
65                 entries.hasMoreElements();) {
66             ZipEntry entry = entries.nextElement();
67             String name = entry.getName();
68             if (name.endsWith(CLASS_EXTENSION)) {
69                 classNames.add(name.substring(0, name.length() - CLASS_EXTENSION.length()));
70             }
71         }
72 
73         // keep direct references of roots (+ direct references hierarchy)
74         for (Enumeration<? extends ZipEntry> entries = jarOfRoots.entries();
75                 entries.hasMoreElements();) {
76             ZipEntry entry = entries.nextElement();
77             String name = entry.getName();
78             if (name.endsWith(CLASS_EXTENSION)) {
79                 DirectClassFile classFile;
80                 try {
81                     classFile = path.getClass(name);
82                 } catch (FileNotFoundException e) {
83                     throw new IOException("Class " + name +
84                             " is missing form original class path " + path, e);
85                 }
86 
87                 addDependencies(classFile.getConstantPool());
88             }
89         }
90     }
91 
getClassNames()92     Set<String> getClassNames() {
93         return classNames;
94     }
95 
addDependencies(ConstantPool pool)96     private void addDependencies(ConstantPool pool) {
97         for (Constant constant : pool.getEntries()) {
98             if (constant instanceof CstType) {
99                 Type type = ((CstType) constant).getClassType();
100                 String descriptor = type.getDescriptor();
101                 if (descriptor.endsWith(";")) {
102                     int lastBrace = descriptor.lastIndexOf('[');
103                     if (lastBrace < 0) {
104                         addClassWithHierachy(descriptor.substring(1, descriptor.length()-1));
105                     } else {
106                         assert descriptor.length() > lastBrace + 3
107                         && descriptor.charAt(lastBrace + 1) == 'L';
108                         addClassWithHierachy(descriptor.substring(lastBrace + 2,
109                                 descriptor.length() - 1));
110                     }
111                 }
112             }
113         }
114     }
115 
addClassWithHierachy(String classBinaryName)116     private void addClassWithHierachy(String classBinaryName) {
117         if (classNames.contains(classBinaryName)) {
118             return;
119         }
120 
121         try {
122             DirectClassFile classFile = path.getClass(classBinaryName + CLASS_EXTENSION);
123             classNames.add(classBinaryName);
124             CstType superClass = classFile.getSuperclass();
125             if (superClass != null) {
126                 addClassWithHierachy(superClass.getClassType().getClassName());
127             }
128 
129             TypeList interfaceList = classFile.getInterfaces();
130             int interfaceNumber = interfaceList.size();
131             for (int i = 0; i < interfaceNumber; i++) {
132                 addClassWithHierachy(interfaceList.getType(i).getClassName());
133             }
134         } catch (FileNotFoundException e) {
135             // Ignore: The referenced type is not in the path it must be part of the libraries.
136         }
137     }
138 
139 }
140