Merge branch 'master' into fix-java-encoding
diff --git a/build.gradle b/build.gradle
index 56d7ec8..ec6103c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -39,7 +39,11 @@
         from javadoc.destinationDir
     }
 
+    task provideBuildClasspath(type: ProvideBuildClasspathTask)
+
     test {
+        dependsOn provideBuildClasspath
+
         exclude "**/*\$*" // otherwise gradle runs static inner classes like TestRunnerSequenceTest$SimpleTest
         testLogging {
             events "failed"
diff --git a/buildSrc/src/main/groovy/ProvideBuildClasspathTask.groovy b/buildSrc/src/main/groovy/ProvideBuildClasspathTask.groovy
new file mode 100644
index 0000000..9ba6860
--- /dev/null
+++ b/buildSrc/src/main/groovy/ProvideBuildClasspathTask.groovy
@@ -0,0 +1,28 @@
+import org.gradle.api.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.tasks.TaskAction
+
+class ProvideBuildClasspathTask extends DefaultTask {
+    @TaskAction
+    public void writeProperties() throws Exception {
+        List<String> paths = new ArrayList<>()
+
+        project.rootProject.allprojects.each { Project otherProject ->
+            def match = otherProject.name =~ /robolectric-shadows\/(.*)/
+            if (match.matches()) {
+                def artifactName = "${otherProject.group}:${otherProject.mavenArtifactName()}:${otherProject.version}"
+                File classesDir = otherProject.sourceSets['main'].output.classesDir
+                File resourcesDir = otherProject.sourceSets['main'].output.resourcesDir
+                paths << "${artifactName.replaceAll(/:/, '\\\\:')}: ${classesDir}:${resourcesDir}"
+            }
+        }
+
+        File outDir = project.sourceSets['test'].output.resourcesDir
+        if (!outDir.directory) outDir.mkdirs()
+        File outFile = new File(outDir, 'robolectric-build-paths.properties')
+        outFile.withPrintWriter { out ->
+            out.println("# GENERATED by ${this} -- do not edit")
+            paths.each { path -> out.println path }
+        }
+    }
+}
diff --git a/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java b/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java
index 68cbaec..790306b 100644
--- a/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java
+++ b/robolectric/src/main/java/org/robolectric/RobolectricTestRunner.java
@@ -2,7 +2,6 @@
 
 import android.app.Application;
 import android.os.Build;
-import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.TestOnly;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -23,10 +22,7 @@
 import org.robolectric.internal.SdkConfig;
 import org.robolectric.internal.SdkEnvironment;
 import org.robolectric.internal.bytecode.*;
-import org.robolectric.internal.dependency.CachedDependencyResolver;
-import org.robolectric.internal.dependency.DependencyResolver;
-import org.robolectric.internal.dependency.LocalDependencyResolver;
-import org.robolectric.internal.dependency.MavenDependencyResolver;
+import org.robolectric.internal.dependency.*;
 import org.robolectric.manifest.AndroidManifest;
 import org.robolectric.res.OverlayResourceLoader;
 import org.robolectric.res.PackageResourceLoader;
@@ -38,23 +34,13 @@
 import org.robolectric.util.Pair;
 import org.robolectric.util.ReflectionHelpers;
 
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.lang.annotation.Annotation;
+import java.io.*;
 import java.lang.reflect.Constructor;
-import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
-import java.lang.reflect.Proxy;
+import java.net.URL;
 import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
+import java.util.*;
 
 /**
  * Installs a {@link org.robolectric.internal.bytecode.InstrumentingClassLoader} and
@@ -109,6 +95,22 @@
           dependencyResolver = new MavenDependencyResolver();
         }
       }
+
+      URL buildPathPropertiesUrl = getClass().getClassLoader().getResource("robolectric-build-paths.properties");
+      if (buildPathPropertiesUrl != null) {
+        try {
+          Logger.info("Using Robolectric classes from %s", buildPathPropertiesUrl.getPath());
+
+          final Properties properties = new Properties();
+          InputStream stream = buildPathPropertiesUrl.openStream();
+          properties.load(stream);
+          stream.close();
+
+          dependencyResolver = new LocalBuildResolver(properties, dependencyResolver);
+        } catch (IOException e) {
+          throw new RuntimeException("couldn't read " + buildPathPropertiesUrl, e);
+        }
+      }
     }
 
     return dependencyResolver;
diff --git a/robolectric/src/main/java/org/robolectric/internal/dependency/DependencyJar.java b/robolectric/src/main/java/org/robolectric/internal/dependency/DependencyJar.java
index 4003b1d..368e4de 100644
--- a/robolectric/src/main/java/org/robolectric/internal/dependency/DependencyJar.java
+++ b/robolectric/src/main/java/org/robolectric/internal/dependency/DependencyJar.java
@@ -32,4 +32,14 @@
   public String getClassifier() {
     return classifier;
   }
+
+  public String getShortName() {
+    return getGroupId() + ":" + getArtifactId() + ":" + getVersion()
+        + ((getClassifier() == null) ? "" : ":" + getClassifier());
+  }
+
+  @Override
+  public String toString() {
+    return "DependencyJar{" + getShortName() + '}';
+  }
 }
diff --git a/robolectric/src/main/java/org/robolectric/internal/dependency/LocalBuildResolver.java b/robolectric/src/main/java/org/robolectric/internal/dependency/LocalBuildResolver.java
new file mode 100644
index 0000000..c68ae24
--- /dev/null
+++ b/robolectric/src/main/java/org/robolectric/internal/dependency/LocalBuildResolver.java
@@ -0,0 +1,57 @@
+package org.robolectric.internal.dependency;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+public class LocalBuildResolver implements DependencyResolver {
+  private final Properties properties;
+  private DependencyResolver delegate;
+
+  public LocalBuildResolver(Properties properties, DependencyResolver dependencyResolver) {
+    this.properties = properties;
+    delegate = dependencyResolver;
+  }
+
+  @Override
+  public URL[] getLocalArtifactUrls(DependencyJar... dependencies) {
+    List<URL> urls = new ArrayList<>();
+    for (DependencyJar dependency : dependencies) {
+      urls.addAll(getUrlsForDependency(dependency));
+    }
+    return urls.toArray(new URL[urls.size()]);
+  }
+
+  @Override
+  public URL getLocalArtifactUrl(DependencyJar dependency) {
+    List<URL> urls = getUrlsForDependency(dependency);
+    if (urls.size() != 1) {
+      throw new RuntimeException("should be exactly one URL for " + dependency + " but got " + urls);
+    } else {
+      return urls.get(0);
+    }
+  }
+
+  private List<URL> getUrlsForDependency(DependencyJar dependency) {
+    List<URL> urls = new ArrayList<>();
+    String depShortName = dependency.getShortName();
+    String path = properties.getProperty(depShortName);
+    if (path != null) {
+      for (String pathPart : path.split(":")) {
+        try {
+          URL url = new File(pathPart).toURI().toURL();
+          urls.add(url);
+        } catch (MalformedURLException e) {
+          throw new RuntimeException(e);
+        }
+      }
+    } else {
+      urls.add(delegate.getLocalArtifactUrl(dependency));
+    }
+    return urls;
+  }
+
+}
diff --git a/robolectric/src/test/java/org/robolectric/internal/dependency/DependencyJarTest.java b/robolectric/src/test/java/org/robolectric/internal/dependency/DependencyJarTest.java
new file mode 100644
index 0000000..b7340ca
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/internal/dependency/DependencyJarTest.java
@@ -0,0 +1,15 @@
+package org.robolectric.internal.dependency;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class DependencyJarTest {
+  @Test
+  public void testGetShortName() throws Exception {
+    assertThat(new DependencyJar("com.group", "artifact", "1.3", null).getShortName())
+        .isEqualTo("com.group:artifact:1.3");
+    assertThat(new DependencyJar("com.group", "artifact", "1.3", "dll").getShortName())
+        .isEqualTo("com.group:artifact:1.3:dll");
+  }
+}
\ No newline at end of file
diff --git a/robolectric/src/test/java/org/robolectric/internal/dependency/LocalBuildResolverTest.java b/robolectric/src/test/java/org/robolectric/internal/dependency/LocalBuildResolverTest.java
new file mode 100644
index 0000000..a70cf54
--- /dev/null
+++ b/robolectric/src/test/java/org/robolectric/internal/dependency/LocalBuildResolverTest.java
@@ -0,0 +1,45 @@
+package org.robolectric.internal.dependency;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.URL;
+import java.util.Properties;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class LocalBuildResolverTest {
+
+  private LocalBuildResolver resolver;
+  private Properties properties;
+  private DependencyResolver mock;
+
+  @Before
+  public void setUp() throws Exception {
+    properties = new Properties();
+    mock = mock(DependencyResolver.class);
+    resolver = new LocalBuildResolver(properties, mock);
+  }
+
+  @Test
+  public void whenPathIsProvidedInProperties_shouldReturnFileUrls() throws Exception {
+    properties.setProperty("com.group:example:1.3", "/path/1:/path/2");
+    URL[] urls = resolver.getLocalArtifactUrls(new DependencyJar("com.group", "example", "1.3", null));
+    assertThat(urls).containsExactly(
+        new URL("file:///path/1"),
+        new URL("file:///path/2")
+    );
+  }
+
+  @Test
+  public void whenMissingFromProperties_shouldDelegate() throws Exception {
+    DependencyJar dependencyJar = new DependencyJar("com.group", "example", "1.3", null);
+    when(mock.getLocalArtifactUrl(dependencyJar)).thenReturn(new URL("file:///path/3"));
+    URL[] urls = resolver.getLocalArtifactUrls(dependencyJar);
+    assertThat(urls).containsExactly(
+        new URL("file:///path/3")
+    );
+  }
+}
\ No newline at end of file