Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Extend url protocols #361

Merged
merged 7 commits into from

2 participants

@lmcgrath

Solution to #360: Unable to extend URL protocols handled by ClasspathIterable

I replaced the hard-coded dependencies on FileResourceIterator and ZipResourceIterator in the ClasspathIterable with a factory for loading resource iterators. Resource iterators are now created using ResourceIteratorFactory implementations and making these implementations available to the ClasspathIterable only requires a META-INF/services/cucumber.io.ResourceIteratorFactory file with the names of the implementing classes on each line.

The factory used by ClasspathIterable will try to find an appropriate factory to create resource iterators for each URL. If no appropriate factory is found, it will fall back to the ZipResourceIteratorFactory and then the FileResourceIteratorFactory. This means that existing behavior is not impacted by the changes.

There is a proof-of-concept test, cucumber.io.DelegatingResourceIteratorFactoryTest, which shows the META-INF/services/cucumber.io.ResourceIteratorFactory file in action. Other tests for fall-back capability with ZIP/File resource iterator factories would have been inconclusive because any test resources could exist either as a file or within a JAR depending on the test runtime environment.

Other changes:

  • I added URL-decoding to the ResourceLoaderTest class's dir field because I'm on Windows and spaces in my path were causing the test to fail
  • I added explicit generic argument to line 70 of cucumber.table.DataTable because I couldn't get the code to compile even with modifications to the compiler
Logan McGrath and others added some commits
Logan McGrath Because I don't want to have to tweak the crap outta my compiler to make
this work...
dcc204c
Logan McGrath * URL-decoding for test dir because this test will fail if you're on
Windows and your source code sits in a directory with spaces
d22a649
Logan McGrath style violation! 0155856
Logan McGrath Beginning work to make resource iterators extendable
* Raised visibility of ClasspathIterable.getPath(URL)
* Created ResourceIteratorFactory interface
* Factored FileResourceIterator creation into ResourceIteratorFactory
implementation
* Added services file for ResourceIteratorFactory
d886710
Logan McGrath Extensible at last!
* Factored resource iterators into separate factories
* There seems to be a classpath issue with the ZipResourceIterator,
because when it's created by a factory loaded using the ServiceLoader
class, it's unable to find some ZIP files (this was a problem in the
cucumber-jython tests) the solution was to create the
ZipThenFileResourceIteratorFallback class
a13365c
Logan McGrath Test for proof of concept
* Cosmetic tweaks
* Removed hard-coded dependency on ZipResourceIteratorFactory in
ClasspathIterable
271be9a
lmcgrath Merge branch 'master' into extend-url-protocols 4d2b84a
@aslakhellesoy

I didn't know about the ServiceLoader class!! Maybe that can be used to load the various Backend implementations as well. I see only empty constructors are allowed, but I can live with some setters...

Anyway - nice patch. I'll take a closer look when I have more time.

@lmcgrath

I know right?? I didn't know about the ServiceLoader class until I started working with Arquillian several months ago. It loads all its extensions using it.

You can probably follow a similar pattern for the Backend implementations like what I did in the patch and just load factories for creating the back-ends themselves, that way you can get around the no-args constructor problem.

@aslakhellesoy aslakhellesoy merged commit 4d2b84a into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Jul 17, 2012
  1. Because I don't want to have to tweak the crap outta my compiler to make

    Logan McGrath authored
    this work...
  2. * URL-decoding for test dir because this test will fail if you're on

    Logan McGrath authored
    Windows and your source code sits in a directory with spaces
  3. style violation!

    Logan McGrath authored
  4. Beginning work to make resource iterators extendable

    Logan McGrath authored
    * Raised visibility of ClasspathIterable.getPath(URL)
    * Created ResourceIteratorFactory interface
    * Factored FileResourceIterator creation into ResourceIteratorFactory
    implementation
    * Added services file for ResourceIteratorFactory
  5. Extensible at last!

    Logan McGrath authored
    * Factored resource iterators into separate factories
    * There seems to be a classpath issue with the ZipResourceIterator,
    because when it's created by a factory loaded using the ServiceLoader
    class, it's unable to find some ZIP files (this was a problem in the
    cucumber-jython tests) the solution was to create the
    ZipThenFileResourceIteratorFallback class
  6. Test for proof of concept

    Logan McGrath authored
    * Cosmetic tweaks
    * Removed hard-coded dependency on ZipResourceIteratorFactory in
    ClasspathIterable
  7. Merge branch 'master' into extend-url-protocols

    lmcgrath authored
This page is out of date. Refresh to see the latest.
View
13 core/src/main/java/cucumber/io/ClasspathIterable.java
@@ -13,11 +13,13 @@
class ClasspathIterable implements Iterable<Resource> {
private final ClassLoader cl;
+ private final ResourceIteratorFactory resourceIteratorFactory;
private final String path;
private final String suffix;
public ClasspathIterable(ClassLoader cl, String path, String suffix) {
this.cl = cl;
+ this.resourceIteratorFactory = new DelegatingResourceIteratorFactory();
this.path = path;
this.suffix = suffix;
}
@@ -29,14 +31,7 @@ public ClasspathIterable(ClassLoader cl, String path, String suffix) {
Enumeration<URL> resources = cl.getResources(path);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
- if (url.getProtocol().equals("jar")) {
- String jarPath = filePath(url);
- iterator.push(new ZipResourceIterator(jarPath, path, suffix));
- } else {
- File file = new File(getPath(url));
- File rootDir = new File(file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - path.length()));
- iterator.push(new FileResourceIterator(rootDir, file, suffix));
- }
+ iterator.push(this.resourceIteratorFactory.createIterator(url, path, suffix));
}
return iterator;
} catch (IOException e) {
@@ -54,7 +49,7 @@ static boolean hasSuffix(String suffix, String name) {
return suffix == null || name.endsWith(suffix);
}
- private static String getPath(URL url) {
+ static String getPath(URL url) {
try {
return URLDecoder.decode(url.getPath(), "UTF-8");
} catch (UnsupportedEncodingException e) {
View
69 core/src/main/java/cucumber/io/DelegatingResourceIteratorFactory.java
@@ -0,0 +1,69 @@
+package cucumber.io;
+
+import java.net.URL;
+import java.util.Iterator;
+import java.util.ServiceLoader;
+
+import cucumber.runtime.CucumberException;
+
+
+/**
+ * A {@link ResourceIteratorFactory} implementation which delegates to
+ * factories found by the ServiceLoader class.
+ */
+public class DelegatingResourceIteratorFactory implements ResourceIteratorFactory {
+
+ /**
+ * The delegates.
+ */
+ private final Iterable<ResourceIteratorFactory> delegates;
+
+ /**
+ * The fallback resource iterator factory.
+ */
+ private final ResourceIteratorFactory fallback;
+
+ /**
+ * Initializes a new instance of the DelegatingResourceIteratorFactory
+ * class.
+ */
+ public DelegatingResourceIteratorFactory() {
+ this(new ZipThenFileResourceIteratorFallback());
+ }
+
+ /**
+ * Initializes a new instance of the DelegatingResourceIteratorFactory
+ * class with a fallback factory.
+ *
+ * @param fallback The fallback resource iterator factory to use when an
+ * appropriate one couldn't be found otherwise.
+ */
+ public DelegatingResourceIteratorFactory(ResourceIteratorFactory fallback) {
+ delegates = ServiceLoader.load(ResourceIteratorFactory.class);
+ this.fallback = fallback;
+ }
+
+ @Override
+ public boolean isFactoryFor(URL url) {
+ for (ResourceIteratorFactory delegate : delegates) {
+ if (delegate.isFactoryFor(url)) {
+ return true;
+ }
+ }
+ return fallback.isFactoryFor(url);
+ }
+
+ @Override
+ public Iterator<Resource> createIterator(URL url, String path, String suffix) {
+ for (ResourceIteratorFactory delegate : delegates) {
+ if (delegate.isFactoryFor(url)) {
+ return delegate.createIterator(url, path, suffix);
+ }
+ }
+ if (fallback.isFactoryFor(url)) {
+ return fallback.createIterator(url, path, suffix);
+ } else {
+ throw new CucumberException("Fallback factory cannot handle URL: " + url);
+ }
+ }
+}
View
39 core/src/main/java/cucumber/io/FileResourceIteratorFactory.java
@@ -0,0 +1,39 @@
+package cucumber.io;
+
+import static cucumber.io.ClasspathIterable.getPath;
+
+import java.io.File;
+import java.net.URL;
+import java.util.Iterator;
+
+/**
+ * Factory which creates {@link FileResourceIterator}s.
+ *
+ * <p>{@link FileResourceIterator}s should be created for any cases where a
+ * URL's protocol isn't otherwise handled. Thus, {@link #isFactoryFor(URL)}
+ * will always return <code>true</code>. Because of this behavior, the
+ * <code>FileResourceIteratorFactory</code> should never be registered as a
+ * service implementation for {@link ResourceIteratorFactory} as it could
+ * easily hide other service implementations.</p>
+ */
+public class FileResourceIteratorFactory implements ResourceIteratorFactory {
+
+ /**
+ * Initializes a new instance of the FileResourceIteratorFactory class.
+ */
+ public FileResourceIteratorFactory() {
+ // intentionally empty
+ }
+
+ @Override
+ public boolean isFactoryFor(URL url) {
+ return true;
+ }
+
+ @Override
+ public Iterator<Resource> createIterator(URL url, String path, String suffix) {
+ File file = new File(getPath(url));
+ File rootDir = new File(file.getAbsolutePath().substring(0, file.getAbsolutePath().length() - path.length()));
+ return new FileResourceIterator(rootDir, file, suffix);
+ }
+}
View
30 core/src/main/java/cucumber/io/ResourceIteratorFactory.java
@@ -0,0 +1,30 @@
+package cucumber.io;
+
+import java.net.URL;
+import java.util.Iterator;
+
+/**
+ * Factory contract for creating resource iterators.
+ */
+public interface ResourceIteratorFactory {
+
+ /**
+ * Gets a value indicating whether the factory can create iterators for the
+ * resource specified by the given URL.
+ *
+ * @param url The URL to check.
+ * @return True if the factory can create an iterator for the given URL.
+ */
+ boolean isFactoryFor(URL url);
+
+ /**
+ * Creates an iterator for the given URL with the path and suffix.
+ *
+ * @param url The URL.
+ * @param path The path.
+ * @param suffix The suffix.
+ * @return The iterator over the resources designated by the URL, path, and
+ * suffix.
+ */
+ Iterator<Resource> createIterator(URL url, String path, String suffix);
+}
View
38 core/src/main/java/cucumber/io/ZipResourceIteratorFactory.java
@@ -0,0 +1,38 @@
+package cucumber.io;
+
+import static cucumber.io.ClasspathIterable.filePath;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Iterator;
+
+import cucumber.runtime.CucumberException;
+
+/**
+ * Factory which creates {@link ZipResourceIterator}s for URL's with the "jar"
+ * protocol.
+ */
+public class ZipResourceIteratorFactory implements ResourceIteratorFactory {
+
+ /**
+ * Initializes a new instance of the ZipResourceIteratorFactory class.
+ */
+ public ZipResourceIteratorFactory() {
+ // intentionally empty
+ }
+
+ @Override
+ public boolean isFactoryFor(URL url) {
+ return "jar".equals(url.getProtocol());
+ }
+
+ @Override
+ public Iterator<Resource> createIterator(URL url, String path, String suffix) {
+ try {
+ String jarPath = filePath(url);
+ return new ZipResourceIterator(jarPath, path, suffix);
+ } catch (IOException e) {
+ throw new CucumberException(e);
+ }
+ }
+}
View
44 core/src/main/java/cucumber/io/ZipThenFileResourceIteratorFallback.java
@@ -0,0 +1,44 @@
+package cucumber.io;
+
+import java.net.URL;
+import java.util.Iterator;
+
+/**
+ * Resource iterator factory implementation which acts as a fallback when no
+ * other factories are found.
+ */
+public class ZipThenFileResourceIteratorFallback implements ResourceIteratorFactory {
+ /**
+ * The file resource iterator factory.
+ */
+ private final FileResourceIteratorFactory fileResourceIteratorFactory;
+
+ /**
+ * The ZIP resource iterator factory.
+ */
+ private final ZipResourceIteratorFactory zipResourceIteratorFactory;
+
+
+ /**
+ * Initializes a new instance of the ZipThenFileResourceIteratorFallback
+ * class.
+ */
+ public ZipThenFileResourceIteratorFallback() {
+ fileResourceIteratorFactory = new FileResourceIteratorFactory();
+ zipResourceIteratorFactory = new ZipResourceIteratorFactory();
+ }
+
+ @Override
+ public boolean isFactoryFor(URL url) {
+ return zipResourceIteratorFactory.isFactoryFor(url) || fileResourceIteratorFactory.isFactoryFor(url);
+ }
+
+ @Override
+ public Iterator<Resource> createIterator(URL url, String path, String suffix) {
+ if (zipResourceIteratorFactory.isFactoryFor(url)) {
+ return zipResourceIteratorFactory.createIterator(url, path, suffix);
+ } else {
+ return fileResourceIteratorFactory.createIterator(url, path, suffix);
+ }
+ }
+}
View
2  core/src/main/java/cucumber/table/DataTable.java
@@ -67,7 +67,7 @@ public DataTable(List<DataTableRow> gherkinRows, TableConverter tableConverter)
}
public <T> T convert(Type type) {
- return tableConverter.convert(type, this);
+ return tableConverter.<T>convert(type, this);
}
/**
View
27 core/src/test/java/cucumber/io/DelegatingResourceIteratorFactoryTest.java
@@ -0,0 +1,27 @@
+package cucumber.io;
+
+import static org.junit.Assert.assertTrue;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.Iterator;
+
+import org.junit.Test;
+
+import cucumber.io.TestResourceIteratorFactory.TestResourceIterator;
+
+
+public class DelegatingResourceIteratorFactoryTest {
+
+ @Test
+ public void should_load_test_resource_iterator() throws MalformedURLException {
+ ResourceIteratorFactory factory = new DelegatingResourceIteratorFactory();
+ URL url = new URL("file:///this/is/only/a/test");
+
+ assertTrue(factory.isFactoryFor(url));
+
+ Iterator<Resource> iterator = factory.createIterator(url, "test", "test");
+
+ assertTrue(iterator instanceof TestResourceIterator);
+ }
+}
View
8 core/src/test/java/cucumber/io/ResourceLoaderTest.java
@@ -4,13 +4,19 @@
import java.io.File;
import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
public class ResourceLoaderTest {
- private final File dir = new File(getClass().getProtectionDomain().getCodeSource().getLocation().getFile());
+ private final File dir;
+
+ public ResourceLoaderTest() throws UnsupportedEncodingException {
+ dir = new File(URLDecoder.decode(getClass().getProtectionDomain().getCodeSource().getLocation().getFile(), "UTF-8"));
+ }
@Test
public void loads_resources_from_filesystem_dir() {
View
43 core/src/test/java/cucumber/io/TestResourceIteratorFactory.java
@@ -0,0 +1,43 @@
+package cucumber.io;
+
+import java.net.URL;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+public class TestResourceIteratorFactory implements ResourceIteratorFactory
+{
+ /**
+ * Initializes a new instance of the TestResourceIteratorFactory class.
+ */
+ public TestResourceIteratorFactory() {
+ // intentionally empty
+ }
+
+ @Override
+ public boolean isFactoryFor(URL url) {
+ return "file".equals(url.getProtocol()) && url.getPath().endsWith("test");
+ }
+
+ @Override
+ public Iterator<Resource> createIterator(URL url, String path, String suffix) {
+ return new TestResourceIterator();
+ }
+
+ public static final class TestResourceIterator implements Iterator<Resource> {
+
+ @Override
+ public boolean hasNext() {
+ return false;
+ }
+
+ @Override
+ public Resource next() {
+ throw new NoSuchElementException();
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
View
1  core/src/test/resources/META-INF/services/cucumber.io.ResourceIteratorFactory
@@ -0,0 +1 @@
+cucumber.io.TestResourceIteratorFactory
Something went wrong with that request. Please try again.