blob: 6dcf6141f2f2400b0454f57b7a470a54d9bfe65f [file] [log] [blame]
Stuart McCulloch26e7a5a2011-10-17 10:31:43 +00001package aQute.bnd.annotation.metatype;
2
3import java.lang.reflect.*;
4import java.util.*;
5import java.util.regex.*;
6
7public class Configurable<T> {
8
9
10
11
12 public static <T> T createConfigurable(Class<T> c, Map<?, ?> properties) {
13 Object o = Proxy.newProxyInstance(c.getClassLoader(), new Class<?>[] { c },
14 new ConfigurableHandler(properties, c.getClassLoader()));
15 return c.cast(o);
16 }
17
18 public static <T> T createConfigurable(Class<T> c, Dictionary<?, ?> properties) {
19 Map<Object,Object> alt = new HashMap<Object,Object>();
20 for( Enumeration<?> e = properties.keys(); e.hasMoreElements(); ) {
21 Object key = e.nextElement();
22 alt.put(key, properties.get(key));
23 }
24 return createConfigurable(c, alt);
25 }
26
27 static class ConfigurableHandler implements InvocationHandler {
28 final Map<?, ?> properties;
29 final ClassLoader loader;
30
31 ConfigurableHandler(Map<?, ?> properties, ClassLoader loader) {
32 this.properties = properties;
33 this.loader = loader;
34 }
35
36 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
37 Meta.AD ad = method.getAnnotation(Meta.AD.class);
38 String id = Configurable.mangleMethodName(method.getName());
39
40 if (ad != null && !ad.id().equals(Meta.NULL))
41 id = ad.id();
42
43 Object o = properties.get(id);
44
45 if (o == null) {
46 if (ad != null) {
47 if (ad.required())
48 throw new IllegalStateException("Attribute is required but not set "
49 + method.getName());
50
51 o = ad.deflt();
52 if (o.equals(Meta.NULL))
53 o = null;
54 }
55 }
56 if (o == null) {
57 if (method.getReturnType().isPrimitive()
58 || Number.class.isAssignableFrom(method.getReturnType())) {
59
60 o = "0";
61 } else
62 return null;
63 }
64
65 return convert(method.getGenericReturnType(), o);
66 }
67
68 @SuppressWarnings( { "unchecked" }) public Object convert(Type type, Object o)
69 throws Exception {
70 if (type instanceof ParameterizedType) {
71 ParameterizedType pType = (ParameterizedType) type;
72 return convert(pType, o);
73 }
74
75 if (type instanceof GenericArrayType) {
76 GenericArrayType gType = (GenericArrayType) type;
77 return convertArray(gType.getGenericComponentType(), o);
78 }
79
80 Class<?> resultType = (Class<?>) type;
81
82 if (resultType.isArray()) {
83 return convertArray(resultType.getComponentType(), o);
84 }
85
86 Class<?> actualType = o.getClass();
87 if (actualType.isAssignableFrom(resultType))
88 return o;
89
90 if (resultType == boolean.class || resultType == Boolean.class) {
91 if ( actualType == boolean.class || actualType == Boolean.class)
92 return o;
93
94 if (Number.class.isAssignableFrom(actualType)) {
95 double b = ((Number) o).doubleValue();
96 if (b == 0)
97 return false;
98 else
99 return true;
100 }
101 return true;
102
103 } else if (resultType == byte.class || resultType == Byte.class) {
104 if (Number.class.isAssignableFrom(actualType))
105 return ((Number) o).byteValue();
106 resultType = Byte.class;
107 } else if (resultType == char.class) {
108 resultType = Character.class;
109 } else if (resultType == short.class) {
110 if (Number.class.isAssignableFrom(actualType))
111 return ((Number) o).shortValue();
112 resultType = Short.class;
113 } else if (resultType == int.class) {
114 if (Number.class.isAssignableFrom(actualType))
115 return ((Number) o).intValue();
116 resultType = Integer.class;
117 } else if (resultType == long.class) {
118 if (Number.class.isAssignableFrom(actualType))
119 return ((Number) o).longValue();
120 resultType = Long.class;
121 } else if (resultType == float.class) {
122 if (Number.class.isAssignableFrom(actualType))
123 return ((Number) o).floatValue();
124 resultType = Float.class;
125 } else if (resultType == double.class) {
126 if (Number.class.isAssignableFrom(actualType))
127 return ((Number) o).doubleValue();
128 resultType = Double.class;
129 }
130
131 if (resultType.isPrimitive())
132 throw new IllegalArgumentException("Unknown primitive: " + resultType);
133
134 if (Number.class.isAssignableFrom(resultType) && actualType == Boolean.class) {
135 Boolean b = (Boolean) o;
136 o = b ? "1" : "0";
137 } else if (actualType == String.class) {
138 String input = (String) o;
139 if (Enum.class.isAssignableFrom(resultType)) {
140 return Enum.valueOf((Class<Enum>) resultType, input);
141 }
142 if (resultType == Class.class && loader != null) {
143 return loader.loadClass(input);
144 }
145 if (resultType == Pattern.class) {
146 return Pattern.compile(input);
147 }
148 }
149
150 try {
151 Constructor<?> c = resultType.getConstructor(String.class);
152 return c.newInstance(o.toString());
153 } catch (Throwable t) {
154 // handled on next line
155 }
156 throw new IllegalArgumentException("No conversion to " + resultType + " from "
157 + actualType + " value " + o);
158 }
159
160 private Object convert(ParameterizedType pType, Object o) throws InstantiationException,
161 IllegalAccessException, Exception {
162 Class<?> resultType = (Class<?>) pType.getRawType();
163 if (Collection.class.isAssignableFrom(resultType)) {
164 Collection<?> input = toCollection(o);
165 if (resultType.isInterface()) {
166 if (resultType == Collection.class || resultType == List.class)
167 resultType = ArrayList.class;
168 else if (resultType == Set.class || resultType == SortedSet.class)
169 resultType = TreeSet.class;
170 else if (resultType == Queue.class /*|| resultType == Deque.class*/)
171 resultType = LinkedList.class;
172 else if (resultType == Queue.class /*|| resultType == Deque.class*/)
173 resultType = LinkedList.class;
174 else
175 throw new IllegalArgumentException(
176 "Unknown interface for a collection, no concrete class found: "
177 + resultType);
178 }
179
180 @SuppressWarnings("unchecked") Collection<Object> result = (Collection<Object>) resultType
181 .newInstance();
182 Type componentType = pType.getActualTypeArguments()[0];
183
184 for (Object i : input) {
185 result.add(convert(componentType, i));
186 }
187 return result;
188 } else if (pType.getRawType() == Class.class) {
189 return loader.loadClass(o.toString());
190 }
191 throw new IllegalArgumentException("cannot convert to " + pType
192 + " because it uses generics and is not a Collection");
193 }
194
195 Object convertArray(Type componentType, Object o) throws Exception {
196 Collection<?> input = toCollection(o);
197 Class<?> componentClass = getRawClass(componentType);
198 Object array = Array.newInstance(componentClass, input.size());
199
200 int i = 0;
201 for (Object next : input) {
202 Array.set(array, i++, convert(componentType, next));
203 }
204 return array;
205 }
206
207 private Class<?> getRawClass(Type type) {
208 if (type instanceof Class)
209 return (Class<?>) type;
210
211 if (type instanceof ParameterizedType)
212 return (Class<?>) ((ParameterizedType) type).getRawType();
213
214 throw new IllegalArgumentException(
215 "For the raw type, type must be ParamaterizedType or Class but is " + type);
216 }
217
218 private Collection<?> toCollection(Object o) {
219 if (o instanceof Collection)
220 return (Collection<?>) o;
221
222 if (o.getClass().isArray()) {
223 if ( o.getClass().getComponentType().isPrimitive()) {
224 int length = Array.getLength(o);
225 List<Object> result = new ArrayList<Object>(length);
226 for ( int i=0; i<length; i++) {
227 result.add( Array.get(o, i));
228 }
229 return result;
230 } else
231 return Arrays.asList((Object[]) o);
232 }
233
234 if ( o instanceof String) {
235 String s = (String)o;
236 if (s.indexOf('|')>0)
237 return Arrays.asList(s.split("\\|"));
238 }
239 return Arrays.asList(o);
240 }
241
242 }
243
244
245 public static String mangleMethodName(String id) {
246 StringBuilder sb = new StringBuilder(id);
247 for ( int i =0; i<sb.length(); i++) {
248 char c = sb.charAt(i);
249 boolean twice = i < sb.length()-1 && sb.charAt(i+1) ==c;
250 if ( c == '$' || c == '_') {
251 if ( twice )
252 sb.deleteCharAt(i+1);
253 else
254 if ( c == '$')
255 sb.deleteCharAt(i--); // Remove dollars
256 else
257 sb.setCharAt(i, '.'); // Make _ into .
258 }
259 }
260 return sb.toString();
261 }
262}