merge in lmp-mr1-release history after reset to lmp-mr1-dev
diff --git a/dx/etc/mainDexClasses b/dx/etc/mainDexClasses
index 034d47e..28c0f0c 100755
--- a/dx/etc/mainDexClasses
+++ b/dx/etc/mainDexClasses
@@ -155,4 +155,4 @@
   -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 00b60e8..f6a4b56 100755
--- a/dx/etc/mainDexClasses.bat
+++ b/dx/etc/mainDexClasses.bat
@@ -96,10 +96,10 @@
 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 cb5237a..d6c9ed0 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 @@
  * <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 0000000..9f3e48d
--- /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 b346de4..1097d19 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 @@
 /**
  * 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 e76993b..05788d1 100644
--- a/dx/src/com/android/multidex/ArchivePathElement.java
+++ b/dx/src/com/android/multidex/ArchivePathElement.java
@@ -19,6 +19,9 @@
 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 @@
  */
 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 @@
     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 @@
         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 6c60721..aee81cd 100644
--- a/dx/src/com/android/multidex/ClassPathElement.java
+++ b/dx/src/com/android/multidex/ClassPathElement.java
@@ -36,4 +36,6 @@
 
     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 7a9b11d..0434cad 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>();
+    private Set<String> classNames = 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 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 @@
             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 @@
         }
     }
 
-    /**
-     * 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 @@
     }
 
     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 @@
         }
     }
 
-    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 2242547..97fb11f 100644
--- a/dx/src/com/android/multidex/FolderPathElement.java
+++ b/dx/src/com/android/multidex/FolderPathElement.java
@@ -20,6 +20,7 @@
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
+import java.util.ArrayList;
 
 /**
  * A folder element.
@@ -42,4 +43,21 @@
     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 0000000..c9e1a18
--- /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 0000000..155b40f
--- /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