summaryrefslogtreecommitdiffstats
path: root/dx/src/com/android/multidex/ClassReferenceListBuilder.java
diff options
context:
space:
mode:
Diffstat (limited to 'dx/src/com/android/multidex/ClassReferenceListBuilder.java')
-rw-r--r--dx/src/com/android/multidex/ClassReferenceListBuilder.java294
1 files changed, 294 insertions, 0 deletions
diff --git a/dx/src/com/android/multidex/ClassReferenceListBuilder.java b/dx/src/com/android/multidex/ClassReferenceListBuilder.java
new file mode 100644
index 000000000..104262f48
--- /dev/null
+++ b/dx/src/com/android/multidex/ClassReferenceListBuilder.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.multidex;
+
+import com.android.dx.cf.direct.DirectClassFile;
+import com.android.dx.cf.direct.StdAttributeFactory;
+import com.android.dx.rop.cst.Constant;
+import com.android.dx.rop.cst.ConstantPool;
+import com.android.dx.rop.cst.CstType;
+import com.android.dx.rop.type.Type;
+import com.android.dx.rop.type.TypeList;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+/**
+ * This is a command line tool used by mainDexClasses script to find direct class references to
+ * other classes. First argument of the command line is an archive, each class file contained in
+ * this archive is used to identify a class whose references are to be searched, those class files
+ * are not opened by this tool only their names matter. Other arguments must be zip files or
+ * directories, they constitute in a classpath in with the classes named by the first argument
+ * will be searched. Each searched class must be found. On each of this classes are searched for
+ * their dependencies to other classes. Finally the tools prints on standard output a list of class
+ * files names suitable as content of the file argument --main-dex-list of dx.
+ */
+public class ClassReferenceListBuilder {
+
+ private static final String CLASS_EXTENSION = ".class";
+
+ private static final int STATUS_ERROR = 1;
+
+ private static final String EOL = System.getProperty("line.separator");
+
+ private static String USAGE_MESSAGE =
+ "Usage:" + EOL + EOL +
+ "Short version: Don't use this." + EOL + EOL +
+ "Slightly longer version: This tool is used by mainDexClasses script to find direct"
+ + EOL +
+ "references of some classes." + EOL;
+
+ private Path path;
+ private Set<String> toKeep = new HashSet<String>();
+
+ private ClassReferenceListBuilder(Path path) {
+ this.path = path;
+ }
+
+ public static void main(String[] args) {
+
+ if (args.length != 2) {
+ printUsage();
+ System.exit(STATUS_ERROR);
+ }
+
+ ZipFile jarOfRoots;
+ try {
+ jarOfRoots = new ZipFile(args[0]);
+ } catch (IOException e) {
+ System.err.println("\"" + args[0] + "\" can not be read as a zip archive. ("
+ + e.getMessage() + ")");
+ System.exit(STATUS_ERROR);
+ return;
+ }
+
+ Path path = null;
+ try {
+ path = new Path(args[1]);
+
+ ClassReferenceListBuilder builder = new ClassReferenceListBuilder(path);
+ builder.addRoots(jarOfRoots);
+
+ printList(builder.toKeep);
+ } catch (IOException e) {
+ System.err.println("A fatal error occured: " + e.getMessage());
+ System.exit(STATUS_ERROR);
+ return;
+ } finally {
+ try {
+ jarOfRoots.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ if (path != null) {
+ for (ClassPathElement element : path.elements) {
+ try {
+ element.close();
+ } catch (IOException e) {
+ // keep going, lets do our best.
+ }
+ }
+ }
+ }
+ }
+
+ private static void printUsage() {
+ System.err.print(USAGE_MESSAGE);
+ }
+
+ private static ClassPathElement getClassPathElement(File file)
+ throws ZipException, IOException {
+ if (file.isDirectory()) {
+ return new FolderPathElement(file);
+ } else if (file.isFile()) {
+ return new ArchivePathElement(new ZipFile(file));
+ } else if (file.exists()) {
+ throw new IOException(file.getAbsolutePath() +
+ " is not a directory neither a zip file");
+ } else {
+ throw new FileNotFoundException(file.getAbsolutePath());
+ }
+ }
+
+ private static void printList(Set<String> toKeep) {
+ for (String classDescriptor : toKeep) {
+ System.out.print(classDescriptor);
+ System.out.println(CLASS_EXTENSION);
+ }
+ }
+
+ private void addRoots(ZipFile jarOfRoots) throws IOException {
+
+ // keep roots
+ for (Enumeration<? extends ZipEntry> entries = jarOfRoots.entries();
+ entries.hasMoreElements();) {
+ ZipEntry entry = entries.nextElement();
+ String name = entry.getName();
+ if (name.endsWith(CLASS_EXTENSION)) {
+ toKeep.add(name.substring(0, name.length() - CLASS_EXTENSION.length()));
+ }
+ }
+
+ // keep direct references of roots (+ direct references hierarchy)
+ for (Enumeration<? extends ZipEntry> entries = jarOfRoots.entries();
+ entries.hasMoreElements();) {
+ ZipEntry entry = entries.nextElement();
+ String name = entry.getName();
+ if (name.endsWith(CLASS_EXTENSION)) {
+ DirectClassFile classFile;
+ try {
+ classFile = path.getClass(name);
+ } catch (FileNotFoundException e) {
+ throw new IOException("Class " + name +
+ " is missing form original class path " + path, e);
+ }
+
+ addDependencies(classFile.getConstantPool());
+ }
+ }
+ }
+
+ private void addDependencies(ConstantPool pool) {
+ int entryCount = pool.size();
+ for (Constant constant : pool.getEntries()) {
+ if (constant instanceof CstType) {
+ Type type = ((CstType) constant).getClassType();
+ String descriptor = type.getDescriptor();
+ if (descriptor.endsWith(";")) {
+ int lastBrace = descriptor.lastIndexOf('[');
+ if (lastBrace < 0) {
+ addClassWithHierachy(descriptor.substring(1, descriptor.length()-1));
+ } else {
+ assert descriptor.length() > lastBrace + 3
+ && descriptor.charAt(lastBrace + 1) == 'L';
+ addClassWithHierachy(descriptor.substring(lastBrace + 2,
+ descriptor.length() - 1));
+ }
+ }
+ }
+ }
+ }
+
+ private void addClassWithHierachy(String classBinaryName) {
+ if (toKeep.contains(classBinaryName)) {
+ return;
+ }
+
+ String fileName = classBinaryName + CLASS_EXTENSION;
+ try {
+ DirectClassFile classFile = path.getClass(fileName);
+ toKeep.add(classBinaryName);
+ CstType superClass = classFile.getSuperclass();
+ if (superClass != null) {
+ addClassWithHierachy(superClass.getClassType().getClassName());
+ }
+
+ TypeList interfaceList = classFile.getInterfaces();
+ int interfaceNumber = interfaceList.size();
+ for (int i = 0; i < interfaceNumber; i++) {
+ addClassWithHierachy(interfaceList.getType(i).getClassName());
+ }
+ } catch (FileNotFoundException e) {
+ // Ignore: The referenced type is not in the path it must be part of the libraries.
+ }
+ }
+
+ private static class Path {
+ private List<ClassPathElement> elements = new ArrayList<ClassPathElement>();
+ private String definition;
+ private ByteArrayOutputStream baos = new ByteArrayOutputStream(40 * 1024);
+ private byte[] readBuffer = new byte[20 * 1024];
+
+ public Path(String definition) throws IOException {
+ this.definition = definition;
+ for (String filePath : definition.split(Pattern.quote(File.pathSeparator))) {
+ try {
+ addElement(getClassPathElement(new File(filePath)));
+ } catch (IOException e) {
+ throw new IOException("\"" + filePath + "\" can not be used as a classpath"
+ + " element. ("
+ + e.getMessage() + ")", e);
+ }
+ }
+ }
+
+ private static byte[] readStream(InputStream in, ByteArrayOutputStream baos, byte[] readBuffer)
+ throws IOException {
+ try {
+ for (;;) {
+ int amt = in.read(readBuffer);
+ if (amt < 0) {
+ break;
+ }
+
+ baos.write(readBuffer, 0, amt);
+ }
+ } finally {
+ in.close();
+ }
+ return baos.toByteArray();
+ }
+
+ @Override
+ public String toString() {
+ return definition;
+ }
+
+ private void addElement(ClassPathElement element) {
+ assert element != null;
+ elements.add(element);
+ }
+
+ private DirectClassFile getClass(String path) throws FileNotFoundException {
+ DirectClassFile classFile = null;
+ for (ClassPathElement element : elements) {
+ try {
+ InputStream in = element.open(path);
+ try {
+ byte[] bytes = readStream(in, baos, readBuffer);
+ baos.reset();
+ classFile = new DirectClassFile(bytes, path, false);
+ classFile.setAttributeFactory(StdAttributeFactory.THE_ONE);
+ break;
+ } finally {
+ in.close();
+ }
+ } catch (IOException e) {
+ // search next element
+ }
+ }
+ if (classFile == null) {
+ throw new FileNotFoundException(path);
+ }
+ return classFile;
+ }
+ }
+
+
+}