Skip to content

Commit

Permalink
Add API to read Config using PathAnchor resolutions (#212)
Browse files Browse the repository at this point in the history
* use getConstructor for plugin instantiation

* add API for reading Config from Config.Resource

* fallback to getDeclaredConstructor after trying getConstructor
  • Loading branch information
eiennohito authored Jun 23, 2023
1 parent ec7f79f commit 5ac1371
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 1 deletion.
53 changes: 52 additions & 1 deletion src/main/java/com/worksap/nlp/sudachi/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import javax.json.Json;
import javax.json.JsonArrayBuilder;
import javax.json.JsonObject;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
Expand Down Expand Up @@ -221,6 +222,43 @@ public static Config fromClasspathMerged(String name) throws IOException {
return fromClasspathMerged(Config.class.getClassLoader(), name);
}

/**
* Create Config object from the passed resource. Resources are created by
* {@link PathAnchor}.
*
* @param resource
* resource object capturing
* @param anchor
* object that will be used for path resolution and class loading
* @return parsed Config object
* @param <T>
* can be anything
* @throws IOException
* when IO fails
* @throws FileNotFoundException
* if resource can not be found
* @see PathAnchor#resource(String)
*/
public static <T> Config fromResource(Resource<T> resource, PathAnchor anchor) throws IOException {
if (resource instanceof Resource.Classpath) {
return fromClasspath((URL) resource.repr(), anchor);
}
if (resource instanceof Resource.Filesystem) {
return fromFile((Path) resource.repr(), anchor);
}
if (resource instanceof Resource.Ready) {
Object ready = resource.repr();
if (ready instanceof Config) {
return (Config) ready;
}
throw new ClassCastException("expected Config, was " + ready.getClass());
}
if (resource instanceof Resource.NotFound) {
throw new FileNotFoundException(resource.toString());
}
throw new IllegalStateException("should not happen");
}

/**
* Parses all config files with the specified name in the classpath, merging
* them. Files are loaded with the provided classloader.
Expand Down Expand Up @@ -694,7 +732,7 @@ public T instantiate(PathAnchor anchor) {

T result;
try {
Constructor<? extends T> constructor = clz.getDeclaredConstructor();
Constructor<? extends T> constructor = lookupConstructor(clz);
result = constructor.newInstance();
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException
| InvocationTargetException e) {
Expand All @@ -706,6 +744,19 @@ public T instantiate(PathAnchor anchor) {
return result;
}

private Constructor<? extends T> lookupConstructor(Class<? extends T> clz) throws NoSuchMethodException {
try {
return clz.getConstructor();
} catch (NoSuchMethodException ignored) {
try {
return clz.getDeclaredConstructor();
} catch (NoSuchMethodException | SecurityException ignored2) {
throw new NoSuchElementException("class " + clz
+ " did not have accessible no-arg constructor or security policy forbids access");
}
}
}

/**
* Add a string value to the plugin configuration
*
Expand Down
39 changes: 39 additions & 0 deletions src/test/java/com/worksap/nlp/sudachi/ConfigTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@

package com.worksap.nlp.sudachi

import com.worksap.nlp.sudachi.Config.Resource
import com.worksap.nlp.sudachi.dictionary.build.res
import java.io.FileNotFoundException
import java.net.URL
import java.nio.file.Path
import java.nio.file.Paths
Expand Down Expand Up @@ -144,4 +147,40 @@ class ConfigTest {
cfg.anchoredWith(PathAnchor.filesystem("test"))
assertIs<PathAnchor.Chain>(cfg.anchor)
}

@Test
fun fromResourceFile() {
val anchor = PathAnchor.filesystem("src/test/resources")
val cfgFile = anchor.resource<Any>("sudachi.json")
val cfg = Config.fromResource(cfgFile, anchor)
assertEquals(1, cfg.userDictionaries.size)
}

@Test
fun fromResourceClasspath() {
val anchor = PathAnchor.classpath(javaClass.classLoader)
val cfgFile = anchor.resource<Any>("sudachi.json")
val cfg = Config.fromResource(cfgFile, anchor)
assertEquals(1, cfg.userDictionaries.size)
}

@Test
fun fromReady() {
val configRes = Resource.ready(Config.defaultConfig())
val cfg = Config.fromResource(configRes, PathAnchor.none())
assertEquals(1, cfg.userDictionaries.size)
}

@Test
fun fromReadyInvalidClass() {
val res = Resource.ready(10)
assertFailsWith<ClassCastException> { Config.fromResource(res, PathAnchor.none()) }
}

@Test
fun fromReadyNotFound() {
val anchor = PathAnchor.none()
val res = anchor.resource<Any>("not_found.resource")
assertFailsWith<FileNotFoundException> { Config.fromResource(res, anchor) }
}
}

0 comments on commit 5ac1371

Please sign in to comment.