From c9ad158a75fd80c463eb890f7f6fe07b125e8191 Mon Sep 17 00:00:00 2001 From: Ketan Padegaonkar Date: Thu, 1 Oct 2015 16:55:54 +0530 Subject: [PATCH] Nested configuration interfaces (#129) This is only an initial draft to get feedback (please don't shoot me :) This works by doing 2 things - * a converter that checks if the target type is an implementation of the `Config` interface. If it is, then any further conversion is handed off to `ConfigFactory` with a (configurable)namespace prefix. * The properties manager, that looks up `@Source` annotation will lookup the annotation on all interfaces. Usage - ```properties #/etc/app.properties app.db.username = app.db.password = app.jetty.host = app.jetty.port = ``` ```java @Config.Sources({"/etc/app.properties"}) public interface BaseConfig extends Accessible { } public interface AppConfig extends BaseConfig { @DefaultValue("app.db") Database db(); @DefaultValue("app.jetty") Jetty jetty(); } public interface Database extends Accessible { @Key("${ns}.username") String host(); @Key("${ns}.password") String password(); } public interface Jetty extends Accessible { @Key("${ns}.host") String host(); @Key("${ns}.port") int port(); } ``` --- .../java/org/aeonbits/owner/Converters.java | 35 +++++++++++++------ .../org/aeonbits/owner/PropertiesManager.java | 18 +++++++++- .../org/aeonbits/owner/util/Reflection.java | 21 +++++++++++ 3 files changed, 63 insertions(+), 11 deletions(-) diff --git a/owner/src/main/java/org/aeonbits/owner/Converters.java b/owner/src/main/java/org/aeonbits/owner/Converters.java index b5160d84..9c0927bc 100644 --- a/owner/src/main/java/org/aeonbits/owner/Converters.java +++ b/owner/src/main/java/org/aeonbits/owner/Converters.java @@ -9,6 +9,7 @@ package org.aeonbits.owner; import org.aeonbits.owner.Config.ConverterClass; +import org.aeonbits.owner.util.Reflection; import java.beans.PropertyEditor; import java.beans.PropertyEditorManager; @@ -17,14 +18,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; -import java.util.SortedSet; -import java.util.TreeSet; +import java.util.*; import static java.lang.reflect.Modifier.isStatic; import static org.aeonbits.owner.Converters.SpecialValue.NULL; @@ -144,8 +138,29 @@ Object tryConvert(Method targetMethod, Class targetType, String text) { } }, - PROPERTY_EDITOR { + CLASS_IMPLEMENTING_CONFIG_INTERFACE { + @Override + Object tryConvert(Method targetMethod, Class targetType, String text) { + if (!Reflection.getAllInterfaces(targetType).contains(Config.class)) return SKIP; + try { + Map imports = new HashMap(); + String namespace = ConfigFactory.getProperty("nested.config.prefix"); + if (namespace == null) { + namespace = "ns"; + } + imports.put(namespace, text); + Object result = ConfigFactory.create((Class) targetType, imports); + if (result == null) { + return null; + } + return result; + } catch (Exception e) { + return SKIP; + } + } + }, + PROPERTY_EDITOR { @Override Object tryConvert(Method targetMethod, Class targetType, String text) { if (!canUsePropertyEditors()) @@ -180,7 +195,7 @@ private boolean isPropertyEditorDisabled() { PRIMITIVE { @Override Object tryConvert(Method targetMethod, Class targetType, String text) { - if (! targetType.isPrimitive()) return SKIP; + if (!targetType.isPrimitive()) return SKIP; if (targetType == Byte.TYPE) return Byte.parseByte(text); if (targetType == Short.TYPE) return Short.parseShort(text); if (targetType == Integer.TYPE) return Integer.parseInt(text); diff --git a/owner/src/main/java/org/aeonbits/owner/PropertiesManager.java b/owner/src/main/java/org/aeonbits/owner/PropertiesManager.java index b9073734..07163a23 100644 --- a/owner/src/main/java/org/aeonbits/owner/PropertiesManager.java +++ b/owner/src/main/java/org/aeonbits/owner/PropertiesManager.java @@ -15,6 +15,7 @@ import org.aeonbits.owner.event.RollbackOperationException; import org.aeonbits.owner.event.TransactionalPropertyChangeListener; import org.aeonbits.owner.event.TransactionalReloadListener; +import org.aeonbits.owner.util.Reflection; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; @@ -25,6 +26,7 @@ import java.io.PrintWriter; import java.io.Reader; import java.io.Serializable; +import java.lang.annotation.Annotation; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.net.URI; @@ -102,7 +104,7 @@ public boolean remove(Object o) { this.imports = imports; ConfigURIFactory urlFactory = new ConfigURIFactory(clazz.getClassLoader(), expander); - uris = toURIs(clazz.getAnnotation(Sources.class), urlFactory); + uris = toURIs(getAnnotationFromSuperInterfaces(clazz, Sources.class), urlFactory); LoadPolicy loadPolicy = clazz.getAnnotation(LoadPolicy.class); loadType = (loadPolicy != null) ? loadPolicy.value() : FIRST; @@ -122,6 +124,20 @@ public void run() { } } + private T getAnnotationFromSuperInterfaces(Class clazz, Class annotationClass) { + T annotation = (T) clazz.getAnnotation(annotationClass); + if (annotation == null) { + List allInterfaces = Reflection.getAllInterfaces(clazz); + for (Class anInterface : allInterfaces) { + if (anInterface.getAnnotation(annotationClass) != null) { + annotation = (T) anInterface.getAnnotation(annotationClass); + break; + } + } + } + return annotation; + } + private List toURIs(Sources sources, ConfigURIFactory uriFactory) { String[] specs = specs(sources, uriFactory); List result = new ArrayList(); diff --git a/owner/src/main/java/org/aeonbits/owner/util/Reflection.java b/owner/src/main/java/org/aeonbits/owner/util/Reflection.java index 2ee235ed..3bf33fa7 100644 --- a/owner/src/main/java/org/aeonbits/owner/util/Reflection.java +++ b/owner/src/main/java/org/aeonbits/owner/util/Reflection.java @@ -9,6 +9,8 @@ package org.aeonbits.owner.util; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; /** * @author Luigi R. Viggiano @@ -65,4 +67,23 @@ public static Object invokeDefaultMethod(Object proxy, Method method, Object[] a return JAVA_8_SUPPORT.invokeDefaultMethod(proxy, method, args); } + public static List getAllInterfaces(Class cls) { + List list = new ArrayList(); + while (cls != null) { + for (Class anInterface : cls.getInterfaces()) { + if (!list.contains(anInterface)) { + list.add(anInterface); + } + List superInterfaces = getAllInterfaces(anInterface); + for (Class superInterface : superInterfaces) { + if (!list.contains(superInterface)) { + list.add(superInterface); + } + } + } + cls = cls.getSuperclass(); + } + return list; + } + }