| /* |
| * 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; |
| } |
| } |
| |
| |
| } |