Introduction of a tool to find main dex classes.
This is for legacy application wanting to allow --multi-dex on dx
and load the multiple files using the com.android.multidex.installer
library. The mainDexClasses script will provide the content of the
file to give to dx in --main-dex-list.
Change-Id: Id14bc785c6a888f6af49c018b2c29b89284ae40e
diff --git a/dx/Android.mk b/dx/Android.mk
index edf8ca3..a69c785 100644
--- a/dx/Android.mk
+++ b/dx/Android.mk
@@ -29,6 +29,65 @@
endif # TARGET_BUILD_APPS
+# the mainDexClasses rules
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := mainDexClasses.rules
+
+include $(BUILD_SYSTEM)/base_rules.mk
+
+$(LOCAL_BUILT_MODULE): $(HOST_OUT_JAVA_LIBRARIES)/dx$(COMMON_JAVA_PACKAGE_SUFFIX)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/mainDexClasses.rules | $(ACP)
+ @echo "Copy: $(PRIVATE_MODULE) ($@)"
+ $(copy-file-to-new-target)
+
+INTERNAL_DALVIK_MODULES += $(LOCAL_INSTALLED_MODULE)
+
+installed_mainDexClasses.rules := $(LOCAL_INSTALLED_MODULE)
+
+# the shrinkedAndroid jar is a library used by the mainDexClasses script
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := shrinkedAndroid
+LOCAL_BUILT_MODULE_STEM := shrinkedAndroid.jar
+LOCAL_MODULE_SUFFIX := $(COMMON_JAVA_PACKAGE_SUFFIX)
+
+include $(BUILD_SYSTEM)/base_rules.mk
+
+$(LOCAL_BUILT_MODULE): PRIVATE_PROGUARD_FLAGS:= \
+ -include $(addprefix $(LOCAL_PATH)/, shrinkedAndroid.proguard.flags)
+$(LOCAL_BUILT_MODULE): $(call java-lib-files,android_stubs_current)| $(PROGUARD)
+ $(call proguard-enabled-commands,)
+
+INTERNAL_DALVIK_MODULES += $(LOCAL_INSTALLED_MODULE)
+
+installed_shrinkedAndroid := $(LOCAL_INSTALLED_MODULE)
+
+# the mainDexClasses script
+# ============================================================
+include $(CLEAR_VARS)
+LOCAL_IS_HOST_MODULE := true
+LOCAL_MODULE_CLASS := EXECUTABLES
+LOCAL_MODULE_TAGS := optional
+LOCAL_MODULE := mainDexClasses
+
+include $(BUILD_SYSTEM)/base_rules.mk
+
+$(LOCAL_BUILT_MODULE): $(HOST_OUT_JAVA_LIBRARIES)/dx$(COMMON_JAVA_PACKAGE_SUFFIX)
+$(LOCAL_BUILT_MODULE): $(LOCAL_PATH)/etc/mainDexClasses | $(ACP)
+ @echo "Copy: $(PRIVATE_MODULE) ($@)"
+ $(copy-file-to-new-target)
+ $(hide) chmod 755 $@
+
+$(LOCAL_INSTALLED_MODULE): | $(installed_shrinkedAndroid) $(installed_mainDexClasses.rules)
+INTERNAL_DALVIK_MODULES += $(LOCAL_INSTALLED_MODULE)
+
# the dexmerger script
# ============================================================
include $(CLEAR_VARS)
diff --git a/dx/etc/mainDexClasses b/dx/etc/mainDexClasses
new file mode 100644
index 0000000..39760ec
--- /dev/null
+++ b/dx/etc/mainDexClasses
@@ -0,0 +1,150 @@
+#!/bin/bash
+#
+# 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.
+
+function makeTempJar ()
+{
+ local tempDir=/tmp
+ if [ ! -e ${tempDir} ]; then
+ tempDir=.
+ fi
+ local tempfile="${tempDir}/mainDexClasses-$$.tmp.jar"
+ if [ -e ${tempfile} ]; then
+ echo "Failed to create temporary file" >2
+ exit 6
+ fi
+ echo ${tempfile}
+}
+
+function cleanTmp ()
+{
+ if [ -e ${tmpOut} ] ; then
+ rm ${tmpOut}
+ fi
+}
+
+
+# Set up prog to be the path of this script, including following symlinks,
+# and set up progdir to be the fully-qualified pathname of its directory.
+prog="$0"
+
+while [ -h "${prog}" ]; do
+ newProg=`/bin/ls -ld "${prog}"`
+ newProg=`expr "${newProg}" : ".* -> \(.*\)$"`
+ if expr "x${newProg}" : 'x/' >/dev/null; then
+ prog="${newProg}"
+ else
+ progdir=`dirname "${prog}"`
+ prog="${progdir}/${newProg}"
+ fi
+done
+oldwd=`pwd`
+progdir=`dirname "${prog}"`
+cd "${progdir}"
+progdir=`pwd`
+prog="${progdir}"/`basename "${prog}"`
+cd "${oldwd}"
+
+baserules="${progdir}"/mainDexClasses.rules
+if [ ! -r ${baserules} ]; then
+ echo `basename "$prog"`": can't find mainDexClasses.rules" 1>&2
+ exit 1
+fi
+
+jarfile=dx.jar
+libdir="$progdir"
+
+if [ ! -r "$libdir/$jarfile" ]; then
+ # set dx.jar location for the SDK case
+ libdir=`dirname "$progdir"`/platform-tools/lib
+fi
+
+
+if [ ! -r "$libdir/$jarfile" ]; then
+ # set dx.jar location for the Android tree case
+ libdir=`dirname "$progdir"`/framework
+fi
+
+if [ ! -r "$libdir/$jarfile" ]; then
+ echo `basename "$prog"`": can't find $jarfile" 1>&2
+ exit 1
+fi
+
+proguardExec="proguard.sh"
+proguard=${PROGUARD_HOME}/${proguardExec}
+
+if [ ! -r ${proguard} ]; then
+ # set proguard location for the SDK case
+ proguardBaseDir=`dirname "$progdir"`
+ #"${progdir}"/../..
+ proguardBaseDir=`dirname "$proguardBaseDir"`
+ proguard="${proguardBaseDir}"/tools/proguard/bin/${proguardExec}
+fi
+
+if [ ! -r ${proguard} ]; then
+ # set proguard location for the Android tree case
+ proguardBaseDir=`dirname "$proguardBaseDir"`
+ # ${progdir}"/../../../..
+ proguardBaseDir=`dirname "$proguardBaseDir"`
+ proguard="${proguardBaseDir}"/external/proguard/bin/${proguardExec}
+fi
+
+if [ ! -r ${proguard} ]; then
+ echo `basename "$prog"`": can't find ${proguardExec}" 1>&2
+ exit 1
+fi
+
+shrinkedAndroidJar=${SHRINKED_ANDROID_JAR}
+if [ -z ${shrinkedAndroidJar} ]; then
+ shrinkedAndroidJar=shrinkedAndroid.jar
+fi
+
+if [ ! -r ${shrinkedAndroidJar} ]; then
+ shrinkedAndroidJar=${libdir}/${shrinkedAndroidJar}
+fi
+
+if [ ! -r ${shrinkedAndroidJar} ]; then
+ echo `basename "$prog"`": can't find shrinkedAndroid.jar" 1>&2
+ exit 1
+fi
+
+if [ "$OSTYPE" = "cygwin" ]; then
+ # For Cygwin, convert the jarfile path into native Windows style.
+ jarpath=`cygpath -w "$libdir/$jarfile"`
+ proguard=`cygpath -w "${proguard}"`
+ shrinkedAndroidJar=`cygpath -w "${shrinkedAndroidJar}"`
+else
+ jarpath="$libdir/$jarfile"
+fi
+
+if expr "x$1" : 'x--output' >/dev/null; then
+ exec 1>$2
+ shift 2
+fi
+
+if [ $# -ne 1 ]; then
+ echo "Usage : $0 [--output <output file>] <application path>" 1>&2
+ exit 2
+fi
+
+tmpOut=`makeTempJar`
+
+trap cleanTmp 0
+
+${proguard} -injars ${@} -dontwarn -forceprocessing -outjars ${tmpOut} \
+ -libraryjars "${shrinkedAndroidJar}" -dontoptimize -dontobfuscate -dontpreverify \
+ -include "${baserules}" 1>/dev/null || exit 10
+
+exec java -cp "$jarpath" com.android.multidex.ClassReferenceListBuilder ${tmpOut} ${@} || exit 11
diff --git a/dx/etc/mainDexClasses.bat b/dx/etc/mainDexClasses.bat
new file mode 100755
index 0000000..00b60e8
--- /dev/null
+++ b/dx/etc/mainDexClasses.bat
@@ -0,0 +1,110 @@
+@echo off
+REM Copyright (C) 2013 The Android Open Source Project
+REM
+REM Licensed under the Apache License, Version 2.0 (the "License");
+REM you may not use this file except in compliance with the License.
+REM You may obtain a copy of the License at
+REM
+REM http://www.apache.org/licenses/LICENSE-2.0
+REM
+REM Unless required by applicable law or agreed to in writing, software
+REM distributed under the License is distributed on an "AS IS" BASIS,
+REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+REM See the License for the specific language governing permissions and
+REM limitations under the License.
+
+REM don't modify the caller's environment
+setlocal
+
+rem Check we have a valid Java.exe in the path.
+set java_exe=
+if exist "%~dp0..\tools\lib\find_java.bat" call "%~dp0..\tools\lib\find_java.bat"
+if exist "%~dp0..\..\tools\lib\find_java.bat" call "%~dp0..\..\tools\lib\find_java.bat"
+if not defined java_exe goto :EOF
+
+set baserules="%~dp0\mainDexClasses.rules"
+
+REM Locate dx.jar in the directory where dx.bat was found.
+set jarfile=dx.jar
+set "frameworkdir=%~dp0"
+rem frameworkdir must not end with a dir sep.
+set "frameworkdir=%frameworkdir:~0,-1%"
+if exist "%frameworkdir%\%jarfile%" goto JarFileOk
+ set "frameworkdir=%~dp0lib"
+
+if exist "%frameworkdir%\%jarfile%" goto JarFileOk
+ set "frameworkdir=%~dp0..\framework"
+:JarFileOk
+
+set "jarpath=%frameworkdir%\%jarfile%"
+
+set "shrinkedAndroidJar=%SHRINKED_ANDROID_JAR%
+if exist "%shrinkedAndroidJar%" goto shrinkedAndroidOk
+ set "shrinkedAndroidJar=shrinkedAndroid.jar"
+
+if exist "%shrinkedAndroidJar%" goto shrinkedAndroidOk
+ set "shrinkedAndroidJar=%frameworkdir%\%shrinkedAndroidJar%"
+
+:shrinkedAndroidOk
+set "proguardExec=proguard.bat"
+set "proguard=%PROGUARD_HOME%\bin\%proguardExec%"
+
+if exist "%proguard%" goto proguardOk
+REM set proguard location for the SDK case
+ set "PROGUARD_HOME=%~dp0\..\..\tools\proguard"
+ set "proguard=%PROGUARD_HOME%\bin\%proguardExec%"
+
+if exist "%proguard%" goto proguardOk
+REM set proguard location for the Android tree case
+ set "PROGUARD_HOME=%~dp0\..\..\..\..\external\proguard"
+ set "proguard=%PROGUARD_HOME%\bin\%proguardExec%"
+
+:proguardOk
+REM Capture all arguments.
+REM Note that when reading the input arguments with %1, the cmd.exe
+REM automagically converts --name=value arguments into 2 arguments "--name"
+REM followed by "value". Dx has been changed to know how to deal with that.
+set params=
+
+set output=
+
+:firstArg
+if [%1]==[] goto endArgs
+
+ if %1 NEQ --output goto notOut
+ set "output=%2"
+ shift
+ shift
+ goto firstArg
+
+:notOut
+ if defined params goto usage
+ set params=%1
+ shift
+ goto firstArg
+
+:endArgs
+if defined params ( goto makeTmpJar ) else ( goto usage )
+
+:makeTmpJar
+set "tmpJar=%TMP%\mainDexClasses-%RANDOM%.tmp.jar"
+if exist "%tmpJar%" goto makeTmpJar
+echo "" > "%tmpJar%"
+set "exitStatus=0"
+
+
+call "%proguard%" -injars %params% -dontwarn -forceprocessing -outjars "%tmpJar%" -libraryjars "%shrinkedAndroidJar%" -dontoptimize -dontobfuscate -dontpreverify -include "%baserules%" 1>nul
+
+if DEFINED output goto redirect
+call "%java_exe%" -Djava.ext.dirs="%frameworkdir%" com.android.multidex.ClassReferenceListBuilder "%tmpJar%" "%params%"
+goto afterClassReferenceListBuilder
+:redirect
+call "%java_exe%" -Djava.ext.dirs="%frameworkdir%" com.android.multidex.ClassReferenceListBuilder "%tmpJar%" "%params%" 1>"%output%"
+:afterClassReferenceListBuilder
+
+del %tmpJar%
+exit /b
+
+:usage
+echo "Usage : %0 [--output <output file>] <application path>"
+exit /b 1
diff --git a/dx/etc/mainDexClasses.rules b/dx/etc/mainDexClasses.rules
new file mode 100644
index 0000000..4a3bb50
--- /dev/null
+++ b/dx/etc/mainDexClasses.rules
@@ -0,0 +1,17 @@
+ -keep public class * extends android.app.Instrumentation {
+ }
+ -keep public class * extends android.app.Application {
+ }
+ -keep public class * extends android.app.Activity {
+ }
+ -keep public class * extends android.app.Service {
+ }
+ -keep public class * extends android.content.ContentProvider {
+ }
+ -keep public class * extends android.content.BroadcastReceiver {
+ }
+ -keep public class * extends android.app.backup.BackupAgent {
+ }
+ -keep class android.support.multidex.** {
+ *;
+ }
\ No newline at end of file
diff --git a/dx/shrinkedAndroid.proguard.flags b/dx/shrinkedAndroid.proguard.flags
new file mode 100644
index 0000000..65c8689
--- /dev/null
+++ b/dx/shrinkedAndroid.proguard.flags
@@ -0,0 +1,21 @@
+-dontwarn
+-forceprocessing
+-dontoptimize
+-dontobfuscate
+-dontpreverify
+-keep public class * extends android.app.Instrumentation {
+}
+-keep public class * extends android.app.Application {
+}
+-keep public class * extends android.app.Activity {
+}
+-keep public class * extends android.app.Service {
+}
+-keep public class * extends android.content.ContentProvider {
+}
+-keep public class * extends android.content.BroadcastReceiver {
+}
+-keep public class * extends android.app.backup.BackupAgent {
+}
+-keep class android.support.multidex.installer.** {
+}
diff --git a/dx/src/com/android/dx/rop/cst/ConstantPool.java b/dx/src/com/android/dx/rop/cst/ConstantPool.java
index efc394d..f4b8086 100644
--- a/dx/src/com/android/dx/rop/cst/ConstantPool.java
+++ b/dx/src/com/android/dx/rop/cst/ConstantPool.java
@@ -67,4 +67,11 @@
* the index is in-range but invalid
*/
public Constant getOrNull(int n);
+
+ /**
+ * Get all entries in this constant pool.
+ *
+ * @return the returned array may contain null entries.
+ */
+ public Constant[] getEntries();
}
diff --git a/dx/src/com/android/dx/rop/cst/StdConstantPool.java b/dx/src/com/android/dx/rop/cst/StdConstantPool.java
index bb975e4..f941f7d 100644
--- a/dx/src/com/android/dx/rop/cst/StdConstantPool.java
+++ b/dx/src/com/android/dx/rop/cst/StdConstantPool.java
@@ -88,6 +88,15 @@
}
/**
+ * Get all entries in this constant pool.
+ *
+ * @return the returned array may contain null entries.
+ */
+ public Constant[] getEntries() {
+ return entries;
+ }
+
+ /**
* Sets the entry at the given index.
*
* @param n {@code >= 1, < size();} which entry
diff --git a/dx/src/com/android/multidex/ArchivePathElement.java b/dx/src/com/android/multidex/ArchivePathElement.java
new file mode 100644
index 0000000..e76993b
--- /dev/null
+++ b/dx/src/com/android/multidex/ArchivePathElement.java
@@ -0,0 +1,51 @@
+/*
+ * 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 java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+
+/**
+ * A zip element.
+ */
+class ArchivePathElement implements ClassPathElement {
+
+ private ZipFile archive;
+
+ public ArchivePathElement(ZipFile archive) {
+ this.archive = archive;
+ }
+
+ @Override
+ public InputStream open(String path) throws IOException {
+ ZipEntry entry = archive.getEntry(path);
+ if (entry == null) {
+ throw new FileNotFoundException(path);
+ } else {
+ return archive.getInputStream(entry);
+ }
+ }
+
+ @Override
+ public void close() throws IOException {
+ archive.close();
+ }
+
+}
diff --git a/dx/src/com/android/multidex/ClassPathElement.java b/dx/src/com/android/multidex/ClassPathElement.java
new file mode 100644
index 0000000..6c60721
--- /dev/null
+++ b/dx/src/com/android/multidex/ClassPathElement.java
@@ -0,0 +1,39 @@
+/*
+ * 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 java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * An element of the class path in which class files can be found.
+ */
+interface ClassPathElement {
+
+ char SEPARATOR_CHAR = '/';
+
+ /**
+ * Open a "file" from this {@code ClassPathElement}.
+ * @param path a '/' separated relative path to the wanted file.
+ * @return an {@code InputStream} ready to read the requested file.
+ * @throws IOException if the path can not be found or if an error occurred while opening it.
+ */
+ InputStream open(String path) throws IOException;
+
+ void close() throws IOException;
+
+}
diff --git a/dx/src/com/android/multidex/ClassReferenceListBuilder.java b/dx/src/com/android/multidex/ClassReferenceListBuilder.java
new file mode 100644
index 0000000..080efaa
--- /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().getDescriptor());
+ }
+
+ TypeList interfaceList = classFile.getInterfaces();
+ int interfaceNumber = interfaceList.size();
+ for (int i = 0; i < interfaceNumber; i++) {
+ addClassWithHierachy(interfaceList.getType(i).getDescriptor());
+ }
+ } 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;
+ }
+ }
+
+
+}
diff --git a/dx/src/com/android/multidex/FolderPathElement.java b/dx/src/com/android/multidex/FolderPathElement.java
new file mode 100644
index 0000000..2242547
--- /dev/null
+++ b/dx/src/com/android/multidex/FolderPathElement.java
@@ -0,0 +1,45 @@
+/*
+ * 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 java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+/**
+ * A folder element.
+ */
+class FolderPathElement implements ClassPathElement {
+
+ private File baseFolder;
+
+ public FolderPathElement(File baseFolder) {
+ this.baseFolder = baseFolder;
+ }
+
+ @Override
+ public InputStream open(String path) throws FileNotFoundException {
+ return new FileInputStream(new File(baseFolder,
+ path.replace(SEPARATOR_CHAR, File.separatorChar)));
+ }
+
+ @Override
+ public void close() {
+ }
+
+}