blob: 0f4bb6aa700c45a56229a7e347ad1ca23512a2e8 [file] [log] [blame]
/*
* 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 final 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;
}
}
}