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