From a7d6c23a9ecf187323163914a8987ea553bc5588 Mon Sep 17 00:00:00 2001 From: Havoc Pennington Date: Sat, 26 Nov 2011 22:38:23 -0500 Subject: [PATCH] when loading resource, keep ClassLoader not class and pre-canonicalize resource name This makes the resource in the ConfigOrigin nice and predictable. It also lets us use the full ClassLoader API instead of only the Class methods that have the special resource name handling. --- .../com/typesafe/config/impl/Parseable.java | 65 ++++++++++++++----- .../com/typesafe/config/impl/ConfigTest.scala | 12 ++-- 2 files changed, 55 insertions(+), 22 deletions(-) diff --git a/config/src/main/java/com/typesafe/config/impl/Parseable.java b/config/src/main/java/com/typesafe/config/impl/Parseable.java index 9d840126..8acb2a8b 100644 --- a/config/src/main/java/com/typesafe/config/impl/Parseable.java +++ b/config/src/main/java/com/typesafe/config/impl/Parseable.java @@ -385,19 +385,19 @@ public abstract class Parseable implements ConfigParseable { } private final static class ParseableResource extends Parseable { - final private Class klass; + final private ClassLoader loader; final private String resource; - ParseableResource(Class klass, String resource, + ParseableResource(ClassLoader loader, String resource, ConfigParseOptions options) { - this.klass = klass; + this.loader = loader; this.resource = resource; postConstruct(options); } @Override protected Reader reader() throws IOException { - InputStream stream = klass.getResourceAsStream(resource); + InputStream stream = loader.getResourceAsStream(resource); if (stream == null) { throw new IOException("resource not found on classpath: " + resource); @@ -410,24 +410,28 @@ public abstract class Parseable implements ConfigParseable { return syntaxFromExtension(resource); } - @Override - ConfigParseable relativeTo(String filename) { - // not using File.isAbsolute because resource paths always use '/' - // on all platforms - if (filename.startsWith("/")) + static String parent(String resource) { + int i = resource.lastIndexOf('/'); + if (i < 0) { return null; + } else { + return resource.substring(0, i); + } + } + @Override + ConfigParseable relativeTo(String sibling) { // here we want to build a new resource name and let // the class loader have it, rather than getting the // url with getResource() and relativizing to that url. // This is needed in case the class loader is going to // search a classpath. - File parent = new File(resource).getParentFile(); + String parent = parent(resource); if (parent == null) - return newResource(klass, filename, options() + return newResource(loader, sibling, options() .setOriginDescription(null)); else - return newResource(klass, new File(parent, filename).getPath(), + return newResource(loader, parent + "/" + sibling, options().setOriginDescription(null)); } @@ -438,20 +442,49 @@ public abstract class Parseable implements ConfigParseable { @Override public URL url() { - return klass.getResource(resource); + return loader.getResource(resource); } @Override public String toString() { return getClass().getSimpleName() + "(" + resource + "," - + klass.getName() - + ")"; + + loader.getClass().getSimpleName() + ")"; } } public static Parseable newResource(Class klass, String resource, ConfigParseOptions options) { - return new ParseableResource(klass, resource, options); + return newResource(klass.getClassLoader(), convertResourceName(klass, resource), options); + } + + // this function is supposed to emulate the difference + // between Class.getResource and ClassLoader.getResource + // (unfortunately there doesn't seem to be public API for it). + // We're using it because the Class API is more limited, + // for example it lacks getResources(). So we want to be able to + // use ClassLoader directly. + private static String convertResourceName(Class klass, String resource) { + if (resource.startsWith("/")) { + // "absolute" resource, chop the slash + return resource.substring(1); + } else { + String className = klass.getName(); + int i = className.lastIndexOf('.'); + if (i < 0) { + // no package + return resource; + } else { + // need to be relative to the package + String packageName = className.substring(0, i); + String packagePath = packageName.replace('.', '/'); + return packagePath + "/" + resource; + } + } + } + + public static Parseable newResource(ClassLoader loader, String resource, + ConfigParseOptions options) { + return new ParseableResource(loader, resource, options); } private final static class ParseableProperties extends Parseable { diff --git a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala index b38eb2ef..93dc059b 100644 --- a/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala +++ b/config/src/test/scala/com/typesafe/config/impl/ConfigTest.scala @@ -766,18 +766,18 @@ class ConfigTest extends TestUtils { val conf = ConfigFactory.load("test01") val o1 = conf.getValue("ints.fortyTwo").origin() - assertEquals("/test01.conf: 3", o1.description) - assertEquals("/test01.conf", o1.resource) + assertEquals("test01.conf: 3", o1.description) + assertEquals("test01.conf", o1.resource) assertEquals(3, o1.lineNumber) val o2 = conf.getValue("fromJson1").origin() - assertEquals("/test01.json: 2", o2.description) - assertEquals("/test01.json", o2.resource) + assertEquals("test01.json: 2", o2.description) + assertEquals("test01.json", o2.resource) assertEquals(2, o2.lineNumber) val o3 = conf.getValue("fromProps.bool").origin() - assertEquals("/test01.properties", o3.description) - assertEquals("/test01.properties", o3.resource) + assertEquals("test01.properties", o3.description) + assertEquals("test01.properties", o3.resource) // we don't have line numbers for properties files assertEquals(-1, o3.lineNumber) }