Use local copy of latest bndlib code for pre-release testing purposes
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1347815 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/bundleplugin/src/main/java/aQute/configurable/Config.java b/bundleplugin/src/main/java/aQute/configurable/Config.java
new file mode 100644
index 0000000..bc2aebe
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/configurable/Config.java
@@ -0,0 +1,13 @@
+package aQute.configurable;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME)
+public @interface Config {
+ String NULL = "<<NULL>>";
+
+ boolean required() default false;
+ String description() default "";
+ String deflt() default NULL;
+ String id() default NULL;
+}
diff --git a/bundleplugin/src/main/java/aQute/configurable/Configurable.java b/bundleplugin/src/main/java/aQute/configurable/Configurable.java
new file mode 100644
index 0000000..79af122
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/configurable/Configurable.java
@@ -0,0 +1,368 @@
+package aQute.configurable;
+
+import java.io.*;
+import java.lang.reflect.*;
+import java.lang.reflect.Proxy;
+import java.net.*;
+import java.util.*;
+import java.util.regex.*;
+
+public class Configurable<T> {
+
+ public static <T> T createConfigurable(Class<T> c, Map< ? , ? > properties) {
+ Object o = Proxy.newProxyInstance(c.getClassLoader(),
+ new Class< ? >[] {c},
+ new ConfigurableHandler(properties, c.getClassLoader()));
+ return c.cast(o);
+ }
+
+ public static <T> T createConfigurable(Class<T> c,
+ Dictionary< ? , ? > properties) {
+ Map<Object, Object> alt = new HashMap<Object, Object>();
+ for (Enumeration< ? > e = properties.keys(); e.hasMoreElements();) {
+ Object key = e.nextElement();
+ alt.put(key, properties.get(key));
+ }
+ return createConfigurable(c, alt);
+ }
+
+ static class ConfigurableHandler implements InvocationHandler {
+ final Map< ? , ? > properties;
+ final ClassLoader loader;
+
+ ConfigurableHandler(Map< ? , ? > properties, ClassLoader loader) {
+ this.properties = properties;
+ this.loader = loader;
+ }
+
+ public Object invoke(Object proxy, Method method, Object[] args)
+ throws Throwable {
+ Config ad = method.getAnnotation(Config.class);
+ String id = Configurable.mangleMethodName(method.getName());
+
+ if (ad != null && !ad.id().equals(Config.NULL))
+ id = ad.id();
+
+ Object o = properties.get(id);
+ if (o == null && args != null && args.length == 1)
+ o = args[0];
+
+ if (o == null) {
+ if (ad != null) {
+ if (ad.required())
+ throw new IllegalStateException(
+ "Attribute is required but not set "
+ + method.getName());
+
+ o = ad.deflt();
+ if (o.equals(Config.NULL))
+ o = null;
+ }
+ }
+ if (o == null) {
+ Class< ? > rt = method.getReturnType();
+ if (rt == boolean.class || rt == Boolean.class)
+ return false;
+ if (method.getReturnType().isPrimitive()
+ || Number.class
+ .isAssignableFrom(method.getReturnType())) {
+
+ o = "0";
+ }
+ else
+ return null;
+ }
+
+ if (args != null && args.length == 1) {
+ String s = (String) convert(String.class, o);
+
+ // Allow a base to be specified for File and URL
+ if (method.getReturnType() == File.class
+ && args[0].getClass() == File.class) {
+ return new File((File) args[0], s);
+ }
+ else
+ if (method.getReturnType() == URL.class
+ && args[0].getClass() == File.class) {
+ return new URL(((File) args[0]).toURI().toURL(), s);
+ }
+ else
+ if (method.getReturnType() == URL.class
+ && args[0].getClass() == URL.class) {
+ return new URL((URL) args[0], s);
+ }
+ }
+ return convert(method.getGenericReturnType(), o);
+ }
+
+ @SuppressWarnings({"unchecked", "rawtypes"})
+ public Object convert(Type type, Object o) throws Exception {
+
+ // TODO type variables
+ // TODO wildcards
+
+ if (type instanceof ParameterizedType) {
+ ParameterizedType pType = (ParameterizedType) type;
+ return convert(pType, o);
+ }
+
+ if (type instanceof GenericArrayType) {
+ GenericArrayType gType = (GenericArrayType) type;
+ return convertArray(gType.getGenericComponentType(), o);
+ }
+
+ Class< ? > resultType = (Class< ? >) type;
+
+ if (resultType.isArray()) {
+ return convertArray(resultType.getComponentType(), o);
+ }
+
+ Class< ? > actualType = o.getClass();
+ if (actualType.isAssignableFrom(resultType))
+ return o;
+
+ if (resultType == boolean.class || resultType == Boolean.class) {
+ if (actualType == boolean.class || actualType == Boolean.class)
+ return o;
+
+ if (Number.class.isAssignableFrom(actualType)) {
+ double b = ((Number) o).doubleValue();
+ if (b == 0)
+ return false;
+ return true;
+ }
+ return true;
+
+ }
+ else
+ if (resultType == byte.class || resultType == Byte.class) {
+ if (Number.class.isAssignableFrom(actualType))
+ return ((Number) o).byteValue();
+ resultType = Byte.class;
+ }
+ else
+ if (resultType == char.class) {
+ resultType = Character.class;
+ }
+ else
+ if (resultType == short.class) {
+ if (Number.class.isAssignableFrom(actualType))
+ return ((Number) o).shortValue();
+ resultType = Short.class;
+ }
+ else
+ if (resultType == int.class) {
+ if (Number.class.isAssignableFrom(actualType))
+ return ((Number) o).intValue();
+ resultType = Integer.class;
+ }
+ else
+ if (resultType == long.class) {
+ if (Number.class
+ .isAssignableFrom(actualType))
+ return ((Number) o).longValue();
+ resultType = Long.class;
+ }
+ else
+ if (resultType == float.class) {
+ if (Number.class
+ .isAssignableFrom(actualType))
+ return ((Number) o).floatValue();
+ resultType = Float.class;
+ }
+ else
+ if (resultType == double.class) {
+ if (Number.class
+ .isAssignableFrom(actualType))
+ return ((Number) o)
+ .doubleValue();
+ resultType = Double.class;
+ }
+
+ if (resultType.isPrimitive())
+ throw new IllegalArgumentException("Unknown primitive: "
+ + resultType);
+
+ if (Number.class.isAssignableFrom(resultType)
+ && actualType == Boolean.class) {
+ Boolean b = (Boolean) o;
+ o = b ? "1" : "0";
+ }
+ else
+ if (actualType == String.class) {
+ String input = (String) o;
+ if (Enum.class.isAssignableFrom(resultType)) {
+ return Enum.valueOf((Class<Enum>) resultType, input);
+ }
+ if (resultType == Class.class && loader != null) {
+ return loader.loadClass(input);
+ }
+ if (resultType == Pattern.class) {
+ return Pattern.compile(input);
+ }
+ }
+
+ try {
+ Constructor< ? > c = resultType.getConstructor(String.class);
+ return c.newInstance(o.toString());
+ }
+ catch (Throwable t) {
+ // handled on next line
+ }
+ throw new IllegalArgumentException("No conversion to " + resultType
+ + " from " + actualType + " value " + o);
+ }
+
+ private Object convert(ParameterizedType pType, Object o)
+ throws InstantiationException, IllegalAccessException,
+ Exception {
+ Class< ? > resultType = (Class< ? >) pType.getRawType();
+ if (Collection.class.isAssignableFrom(resultType)) {
+ Collection< ? > input = toCollection(o);
+ if (resultType.isInterface()) {
+ if (resultType == Collection.class
+ || resultType == List.class)
+ resultType = ArrayList.class;
+ else
+ if (resultType == Set.class
+ || resultType == SortedSet.class)
+ resultType = TreeSet.class;
+ else
+ if (resultType == Queue.class /*
+ * || resultType ==
+ * Deque.class
+ */)
+ resultType = LinkedList.class;
+ else
+ if (resultType == Queue.class /*
+ * || resultType ==
+ * Deque.class
+ */)
+ resultType = LinkedList.class;
+ else
+ throw new IllegalArgumentException(
+ "Unknown interface for a collection, no concrete class found: "
+ + resultType);
+ }
+
+ @SuppressWarnings("unchecked")
+ Collection<Object> result = (Collection<Object>) resultType
+ .newInstance();
+ Type componentType = pType.getActualTypeArguments()[0];
+
+ for (Object i : input) {
+ result.add(convert(componentType, i));
+ }
+ return result;
+ }
+ else
+ if (pType.getRawType() == Class.class) {
+ return loader.loadClass(o.toString());
+ }
+ if (Map.class.isAssignableFrom(resultType)) {
+ Map< ? , ? > input = toMap(o);
+ if (resultType.isInterface()) {
+ if (resultType == SortedMap.class)
+ resultType = TreeMap.class;
+ else
+ if (resultType == Map.class)
+ resultType = LinkedHashMap.class;
+ else
+ throw new IllegalArgumentException(
+ "Unknown interface for a collection, no concrete class found: "
+ + resultType);
+ }
+ @SuppressWarnings("unchecked")
+ Map<Object, Object> result = (Map<Object, Object>) resultType
+ .newInstance();
+ Type keyType = pType.getActualTypeArguments()[0];
+ Type valueType = pType.getActualTypeArguments()[1];
+
+ for (Map.Entry< ? , ? > entry : input.entrySet()) {
+ result.put(convert(keyType, entry.getKey()),
+ convert(valueType, entry.getValue()));
+ }
+ return result;
+ }
+ throw new IllegalArgumentException(
+ "cannot convert to "
+ + pType
+ + " because it uses generics and is not a Collection or a map");
+ }
+
+ Object convertArray(Type componentType, Object o) throws Exception {
+ Collection< ? > input = toCollection(o);
+ Class< ? > componentClass = getRawClass(componentType);
+ Object array = Array.newInstance(componentClass, input.size());
+
+ int i = 0;
+ for (Object next : input) {
+ Array.set(array, i++, convert(componentType, next));
+ }
+ return array;
+ }
+
+ private Class< ? > getRawClass(Type type) {
+ if (type instanceof Class)
+ return (Class< ? >) type;
+
+ if (type instanceof ParameterizedType)
+ return (Class< ? >) ((ParameterizedType) type).getRawType();
+
+ throw new IllegalArgumentException(
+ "For the raw type, type must be ParamaterizedType or Class but is "
+ + type);
+ }
+
+ private Collection< ? > toCollection(Object o) {
+ if (o instanceof Collection)
+ return (Collection< ? >) o;
+
+ if (o.getClass().isArray()) {
+ if (o.getClass().getComponentType().isPrimitive()) {
+ int length = Array.getLength(o);
+ List<Object> result = new ArrayList<Object>(length);
+ for (int i = 0; i < length; i++) {
+ result.add(Array.get(o, i));
+ }
+ return result;
+ }
+ return Arrays.asList((Object[]) o);
+ }
+
+ if (o instanceof String) {
+ String s = (String) o;
+ if (s.indexOf('|') > 0)
+ return Arrays.asList(s.split("\\|"));
+ }
+ return Arrays.asList(o);
+ }
+
+ private Map< ? , ? > toMap(Object o) {
+ if (o instanceof Map)
+ return (Map< ? , ? >) o;
+
+ throw new IllegalArgumentException("Cannot convert " + o
+ + " to a map as requested");
+ }
+
+ }
+
+ public static String mangleMethodName(String id) {
+ StringBuilder sb = new StringBuilder(id);
+ for (int i = 0; i < sb.length(); i++) {
+ char c = sb.charAt(i);
+ boolean twice = i < sb.length() - 1 && sb.charAt(i + 1) == c;
+ if (c == '$' || c == '_') {
+ if (twice)
+ sb.deleteCharAt(i + 1);
+ else
+ if (c == '$')
+ sb.deleteCharAt(i--); // Remove dollars
+ else
+ sb.setCharAt(i, '.'); // Make _ into .
+ }
+ }
+ return sb.toString();
+ }
+}
diff --git a/bundleplugin/src/main/java/aQute/configurable/packageinfo b/bundleplugin/src/main/java/aQute/configurable/packageinfo
new file mode 100644
index 0000000..9ad81f6
--- /dev/null
+++ b/bundleplugin/src/main/java/aQute/configurable/packageinfo
@@ -0,0 +1 @@
+version 1.0.0