diff options
author | Yohann Roussel <yroussel@google.com> | 2014-11-18 19:15:02 +0100 |
---|---|---|
committer | Yohann Roussel <yroussel@google.com> | 2014-12-01 10:55:22 +0100 |
commit | 8e88bab04f03edbbc74fb61b31a847c22106bfab (patch) | |
tree | 9fd09a90baebf9988ee2db615456de9b1f8668dc /dx | |
parent | 9747950064e4711c15baf0986102320a2512097a (diff) | |
download | android_dalvik-8e88bab04f03edbbc74fb61b31a847c22106bfab.tar.gz android_dalvik-8e88bab04f03edbbc74fb61b31a847c22106bfab.tar.bz2 android_dalvik-8e88bab04f03edbbc74fb61b31a847c22106bfab.zip |
Keep annotated classes in main dex list
Only classes annotated with a runtime visible annotation are to be kept.
See https://code.google.com/p/android/issues/detail?id=78144
Bug: 18385117
(cherry picked from commit 893795fc95fdd77d398ebb77a0fe336c45b596cf)
Change-Id: I46ceef4aab5119ba4515a07e8f17577e15931dbd
Diffstat (limited to 'dx')
-rwxr-xr-x | dx/etc/mainDexClasses | 2 | ||||
-rwxr-xr-x | dx/etc/mainDexClasses.bat | 4 | ||||
-rw-r--r-- | dx/src/com/android/dx/cf/iface/ClassFile.java | 2 | ||||
-rw-r--r-- | dx/src/com/android/dx/cf/iface/HasAttribute.java | 32 | ||||
-rw-r--r-- | dx/src/com/android/dx/cf/iface/Member.java | 2 | ||||
-rw-r--r-- | dx/src/com/android/multidex/ArchivePathElement.java | 53 | ||||
-rw-r--r-- | dx/src/com/android/multidex/ClassPathElement.java | 2 | ||||
-rw-r--r-- | dx/src/com/android/multidex/ClassReferenceListBuilder.java | 211 | ||||
-rw-r--r-- | dx/src/com/android/multidex/FolderPathElement.java | 18 | ||||
-rw-r--r-- | dx/src/com/android/multidex/MainDexListBuilder.java | 167 | ||||
-rw-r--r-- | dx/src/com/android/multidex/Path.java | 119 |
11 files changed, 410 insertions, 202 deletions
diff --git a/dx/etc/mainDexClasses b/dx/etc/mainDexClasses index 034d47eaa..28c0f0c89 100755 --- a/dx/etc/mainDexClasses +++ b/dx/etc/mainDexClasses @@ -155,4 +155,4 @@ ${proguard} -injars ${@} -dontwarn -forceprocessing -outjars ${tmpOut} \ -libraryjars "${shrinkedAndroidJar}" -dontoptimize -dontobfuscate -dontpreverify \ -include "${baserules}" 1>/dev/null || exit 10 -java -cp "$jarpath" com.android.multidex.ClassReferenceListBuilder "${tmpOut}" ${@} || exit 11 +java -cp "$jarpath" com.android.multidex.MainDexListBuilder "${tmpOut}" ${@} || exit 11 diff --git a/dx/etc/mainDexClasses.bat b/dx/etc/mainDexClasses.bat index 00b60e8de..f6a4b56ca 100755 --- a/dx/etc/mainDexClasses.bat +++ b/dx/etc/mainDexClasses.bat @@ -96,10 +96,10 @@ 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%"
+call "%java_exe%" -Djava.ext.dirs="%frameworkdir%" com.android.multidex.MainDexListBuilder "%tmpJar%" "%params%"
goto afterClassReferenceListBuilder
:redirect
-call "%java_exe%" -Djava.ext.dirs="%frameworkdir%" com.android.multidex.ClassReferenceListBuilder "%tmpJar%" "%params%" 1>"%output%"
+call "%java_exe%" -Djava.ext.dirs="%frameworkdir%" com.android.multidex.MainDexListBuilder "%tmpJar%" "%params%" 1>"%output%"
:afterClassReferenceListBuilder
del %tmpJar%
diff --git a/dx/src/com/android/dx/cf/iface/ClassFile.java b/dx/src/com/android/dx/cf/iface/ClassFile.java index cb5237a50..d6c9ed03e 100644 --- a/dx/src/com/android/dx/cf/iface/ClassFile.java +++ b/dx/src/com/android/dx/cf/iface/ClassFile.java @@ -28,7 +28,7 @@ import com.android.dx.rop.type.TypeList; * <p><b>Note:</b> The fields referred to in this documentation are of the * {@code ClassFile} structure defined in vmspec-2 sec4.1. */ -public interface ClassFile { +public interface ClassFile extends HasAttribute { /** * Gets the field {@code magic}. * diff --git a/dx/src/com/android/dx/cf/iface/HasAttribute.java b/dx/src/com/android/dx/cf/iface/HasAttribute.java new file mode 100644 index 000000000..9f3e48db2 --- /dev/null +++ b/dx/src/com/android/dx/cf/iface/HasAttribute.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 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.dx.cf.iface; + +/** + * An element that can have {@link Attribute} + */ +public interface HasAttribute { + + /** + * Get the element {@code attributes} (along with + * {@code attributes_count}). + * + * @return {@code non-null;} the attributes list + */ + public AttributeList getAttributes(); + +} diff --git a/dx/src/com/android/dx/cf/iface/Member.java b/dx/src/com/android/dx/cf/iface/Member.java index b346de447..1097d1906 100644 --- a/dx/src/com/android/dx/cf/iface/Member.java +++ b/dx/src/com/android/dx/cf/iface/Member.java @@ -23,7 +23,7 @@ import com.android.dx.rop.cst.CstType; /** * Interface representing members of class files (that is, fields and methods). */ -public interface Member { +public interface Member extends HasAttribute { /** * Get the defining class. * diff --git a/dx/src/com/android/multidex/ArchivePathElement.java b/dx/src/com/android/multidex/ArchivePathElement.java index e76993bd9..05788d1bb 100644 --- a/dx/src/com/android/multidex/ArchivePathElement.java +++ b/dx/src/com/android/multidex/ArchivePathElement.java @@ -19,6 +19,9 @@ package com.android.multidex; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.NoSuchElementException; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -27,7 +30,10 @@ import java.util.zip.ZipFile; */ class ArchivePathElement implements ClassPathElement { - private ZipFile archive; + static class DirectoryEntryException extends IOException { + } + + private final ZipFile archive; public ArchivePathElement(ZipFile archive) { this.archive = archive; @@ -37,7 +43,9 @@ class ArchivePathElement implements ClassPathElement { public InputStream open(String path) throws IOException { ZipEntry entry = archive.getEntry(path); if (entry == null) { - throw new FileNotFoundException(path); + throw new FileNotFoundException("File \"" + path + "\" not found"); + } else if (entry.isDirectory()) { + throw new DirectoryEntryException(); } else { return archive.getInputStream(entry); } @@ -48,4 +56,45 @@ class ArchivePathElement implements ClassPathElement { archive.close(); } + @Override + public Iterable<String> list() { + return new Iterable<String>() { + + @Override + public Iterator<String> iterator() { + return new Iterator<String>() { + Enumeration<? extends ZipEntry> delegate = archive.entries(); + ZipEntry next = null; + + @Override + public boolean hasNext() { + while (next == null && delegate.hasMoreElements()) { + next = delegate.nextElement(); + if (next.isDirectory()) { + next = null; + } + } + return next != null; + } + + @Override + public String next() { + if (hasNext()) { + String name = next.getName(); + next = null; + return name; + } else { + throw new NoSuchElementException(); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + } diff --git a/dx/src/com/android/multidex/ClassPathElement.java b/dx/src/com/android/multidex/ClassPathElement.java index 6c60721a4..aee81cdcb 100644 --- a/dx/src/com/android/multidex/ClassPathElement.java +++ b/dx/src/com/android/multidex/ClassPathElement.java @@ -36,4 +36,6 @@ interface ClassPathElement { void close() throws IOException; + Iterable<String> list(); + } diff --git a/dx/src/com/android/multidex/ClassReferenceListBuilder.java b/dx/src/com/android/multidex/ClassReferenceListBuilder.java index 7a9b11d3d..0434cad98 100644 --- a/dx/src/com/android/multidex/ClassReferenceListBuilder.java +++ b/dx/src/com/android/multidex/ClassReferenceListBuilder.java @@ -17,114 +17,41 @@ 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. + * Tool to find direct class references to other classes. */ 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>(); - - /** - * - * @param inputPath list of path to input jars or folders. Path elements must be separated by - * the system path separator: ':' on Unix, ';' on Windows. - */ - public ClassReferenceListBuilder(String inputPath) throws IOException { - this(new Path(inputPath)); - } + private Set<String> classNames = new HashSet<String>(); - private ClassReferenceListBuilder(Path path) { + public ClassReferenceListBuilder(Path path) { this.path = path; } + /** + * Kept for compatibility with the gradle integration, this method just forwards to + * {@link MainDexListBuilder#main(String[])}. + * @deprecated use {@link MainDexListBuilder#main(String[])} instead. + */ + @Deprecated 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. - } - } - } - } + MainDexListBuilder.main(args); } /** @@ -139,7 +66,7 @@ public class ClassReferenceListBuilder { ZipEntry entry = entries.nextElement(); String name = entry.getName(); if (name.endsWith(CLASS_EXTENSION)) { - toKeep.add(name.substring(0, name.length() - CLASS_EXTENSION.length())); + classNames.add(name.substring(0, name.length() - CLASS_EXTENSION.length())); } } @@ -162,41 +89,8 @@ public class ClassReferenceListBuilder { } } - /** - * Returns a list of classes to keep. This can be passed to dx as a file with --main-dex-list. - */ - public Set<String> getMainDexList() { - Set<String> resultSet = new HashSet<String>(toKeep.size()); - for (String classDescriptor : toKeep) { - resultSet.add(classDescriptor + CLASS_EXTENSION); - } - - return resultSet; - } - - 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); - } + Set<String> getClassNames() { + return classNames; } private void addDependencies(ConstantPool pool) { @@ -220,14 +114,13 @@ public class ClassReferenceListBuilder { } private void addClassWithHierachy(String classBinaryName) { - if (toKeep.contains(classBinaryName)) { + if (classNames.contains(classBinaryName)) { return; } - String fileName = classBinaryName + CLASS_EXTENSION; try { - DirectClassFile classFile = path.getClass(fileName); - toKeep.add(classBinaryName); + DirectClassFile classFile = path.getClass(classBinaryName + CLASS_EXTENSION); + classNames.add(classBinaryName); CstType superClass = classFile.getSuperclass(); if (superClass != null) { addClassWithHierachy(superClass.getClassType().getClassName()); @@ -243,76 +136,4 @@ public class ClassReferenceListBuilder { } } - 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]; - - private 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 index 224254730..97fb11f17 100644 --- a/dx/src/com/android/multidex/FolderPathElement.java +++ b/dx/src/com/android/multidex/FolderPathElement.java @@ -20,6 +20,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; +import java.util.ArrayList; /** * A folder element. @@ -42,4 +43,21 @@ class FolderPathElement implements ClassPathElement { public void close() { } + @Override + public Iterable<String> list() { + ArrayList<String> result = new ArrayList<String>(); + collect(baseFolder, "", result); + return result; + } + + private void collect(File folder, String prefix, ArrayList<String> result) { + for (File file : folder.listFiles()) { + if (file.isDirectory()) { + collect(file, prefix + SEPARATOR_CHAR + file.getName(), result); + } else { + result.add(prefix + SEPARATOR_CHAR + file.getName()); + } + } + } + } diff --git a/dx/src/com/android/multidex/MainDexListBuilder.java b/dx/src/com/android/multidex/MainDexListBuilder.java new file mode 100644 index 000000000..c9e1a186c --- /dev/null +++ b/dx/src/com/android/multidex/MainDexListBuilder.java @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2014 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.attrib.AttRuntimeVisibleAnnotations; +import com.android.dx.cf.direct.DirectClassFile; +import com.android.dx.cf.iface.Attribute; +import com.android.dx.cf.iface.FieldList; +import com.android.dx.cf.iface.HasAttribute; +import com.android.dx.cf.iface.MethodList; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import java.util.zip.ZipFile; + +/** + * This is a command line tool used by mainDexClasses script to build a main dex classes list. First + * argument of the command line is an archive, each class file contained in this archive is used to + * identify a class that can be used during secondary dex installation, 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. The tool also browses for classes annotated by runtime + * visible annotations and adds them to the list/ 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 MainDexListBuilder { + 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 build" + EOL + + "the main dex list." + EOL; + + private Set<String> filesToKeep = new HashSet<String>(); + + public static void main(String[] args) { + + if (args.length != 2) { + printUsage(); + System.exit(STATUS_ERROR); + } + + try { + + MainDexListBuilder builder = new MainDexListBuilder(args[0], args[1]); + Set<String> toKeep = builder.getMainDexList(); + printList(toKeep); + } catch (IOException e) { + System.err.println("A fatal error occured: " + e.getMessage()); + System.exit(STATUS_ERROR); + return; + } + } + + public MainDexListBuilder(String rootJar, String pathString) throws IOException { + ZipFile jarOfRoots = null; + Path path = null; + try { + try { + jarOfRoots = new ZipFile(rootJar); + } catch (IOException e) { + throw new IOException("\"" + rootJar + "\" can not be read as a zip archive. (" + + e.getMessage() + ")", e); + } + path = new Path(pathString); + + ClassReferenceListBuilder mainListBuilder = new ClassReferenceListBuilder(path); + mainListBuilder.addRoots(jarOfRoots); + for (String className : mainListBuilder.getClassNames()) { + filesToKeep.add(className + CLASS_EXTENSION); + } + keepAnnotated(path); + } 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. + } + } + } + } + } + + /** + * Returns a list of classes to keep. This can be passed to dx as a file with --main-dex-list. + */ + public Set<String> getMainDexList() { + return filesToKeep; + } + + private static void printUsage() { + System.err.print(USAGE_MESSAGE); + } + + private static void printList(Set<String> fileNames) { + for (String fileName : fileNames) { + System.out.println(fileName); + } + } + + /** + * Keep classes annotated with runtime annotations. + */ + private void keepAnnotated(Path path) throws FileNotFoundException { + for (ClassPathElement element : path.getElements()) { + forClazz: + for (String name : element.list()) { + if (name.endsWith(CLASS_EXTENSION)) { + DirectClassFile clazz = path.getClass(name); + if (hasRuntimeVisibleAnnotation(clazz)) { + filesToKeep.add(name); + } else { + MethodList methods = clazz.getMethods(); + for (int i = 0; i<methods.size(); i++) { + if (hasRuntimeVisibleAnnotation(methods.get(i))) { + filesToKeep.add(name); + continue forClazz; + } + } + FieldList fields = clazz.getFields(); + for (int i = 0; i<fields.size(); i++) { + if (hasRuntimeVisibleAnnotation(fields.get(i))) { + filesToKeep.add(name); + continue forClazz; + } + } + } + } + } + } + } + + private boolean hasRuntimeVisibleAnnotation(HasAttribute element) { + Attribute att = element.getAttributes().findFirst( + AttRuntimeVisibleAnnotations.ATTRIBUTE_NAME); + return (att != null && ((AttRuntimeVisibleAnnotations)att).getAnnotations().size()>0); + } +} diff --git a/dx/src/com/android/multidex/Path.java b/dx/src/com/android/multidex/Path.java new file mode 100644 index 000000000..155b40f98 --- /dev/null +++ b/dx/src/com/android/multidex/Path.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2014 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 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.List; +import java.util.regex.Pattern; +import java.util.zip.ZipException; +import java.util.zip.ZipFile; + +class Path { + + 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.getPath() + + "\" is not a directory neither a zip file"); + } else { + throw new FileNotFoundException("File \"" + file.getPath() + "\" not found"); + } + } + + List<ClassPathElement> elements = new ArrayList<ClassPathElement>(); + private final String definition; + private final ByteArrayOutputStream baos = new ByteArrayOutputStream(40 * 1024); + private final byte[] readBuffer = new byte[20 * 1024]; + + 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("Wrong classpath: " + 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; + } + + Iterable<ClassPathElement> getElements() { + return elements; + } + + private void addElement(ClassPathElement element) { + assert element != null; + elements.add(element); + } + + synchronized 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("File \"" + path + "\" not found"); + } + return classFile; + } +}
\ No newline at end of file |