| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.felix.dm.impl; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.invoke.MethodHandles; |
| import java.lang.invoke.MethodType; |
| import java.lang.reflect.Array; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.GenericArrayType; |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Proxy; |
| import java.lang.reflect.Type; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Dictionary; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.LinkedHashMap; |
| import java.util.LinkedList; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Queue; |
| import java.util.Set; |
| import java.util.SortedMap; |
| import java.util.SortedSet; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| |
| /** |
| * Provides a way for creating type-safe configurations from a {@link Map} or {@link Dictionary}. |
| * <p> |
| * This class takes a map or dictionary along with a class, the configuration-type, and returns a proxy that converts |
| * method calls from the configuration-type to lookups in the map or dictionary. The results of these lookups are then |
| * converted to the expected return type of the invoked configuration method.<br> |
| * As proxies are returned, no implementations of the desired configuration-type are necessary! |
| * </p> |
| * <p> |
| * The lookups performed are based on the name of the method called on the configuration type. The method names are |
| * "mangled" to the following form: <tt>[lower case letter] [any valid character]*</tt>. Method names starting with |
| * <tt>get</tt> or <tt>is</tt> (JavaBean convention) are stripped from these prefixes. For example: given a dictionary |
| * with the key <tt>"foo"</tt> can be accessed from a configuration-type using the following method names: |
| * <tt>foo()</tt>, <tt>getFoo()</tt> and <tt>isFoo()</tt>. |
| * |
| * If the property name contains some dots, the the following conventions are used: |
| * <ul> |
| * <li>camel casing: if a property contains multiple words separated by dots, then you can indicate words boundaries using medial capitalization. |
| * For example, the property "foo.bar" could be accessed with a method name like "fooBar()" or "getFooBar()". |
| * <li> use underscore to wrap dots: underscore ("_") found in method names are converted to ".", unless they are followed by another underscore. |
| * (in this case, the double "__" is then converted to single underscore ("_"). |
| * For Example: foo_bar() will be mapped to "foo.bar" property, and foo__bar() will be mapped to "foo_bar" property. |
| * </ul> |
| * </p> |
| * <p> |
| * The return values supported are: primitive types (or their object wrappers), strings, enums, arrays of |
| * primitives/strings, {@link Collection} types, {@link Map} types, {@link Class}es and interfaces. When an interface is |
| * returned, it is treated equally to a configuration type, that is, it is returned as a proxy. |
| * </p> |
| * <p> |
| * Arrays can be represented either as comma-separated values, optionally enclosed in square brackets. For example: |
| * <tt>[ a, b, c ]</tt> and <tt>a, b,c</tt> are both considered an array of length 3 with the values "a", "b" and "c". |
| * Alternatively, you can append the array index to the key in the dictionary to obtain the same: a dictionary with |
| * "arr.0" => "a", "arr.1" => "b", "arr.2" => "c" would result in the same array as the earlier examples. |
| * </p> |
| * <p> |
| * Maps can be represented as single string values similarly as arrays, each value consisting of both the key and value |
| * separated by a dot. Optionally, the value can be enclosed in curly brackets. Similar to array, you can use the same |
| * dot notation using the keys. For example, a dictionary with <tt>"map" => "{key1.value1, key2.value2}"</tt> and a |
| * dictionary with <tt>"map.key1" => "value1", "map2.key2" => "value2"</tt> result in the same map being returned. |
| * Instead of a map, you could also define an interface with the methods <tt>getKey1()</tt> and <tt>getKey2</tt> and use |
| * that interface as return type instead of a {@link Map}. |
| * </p> |
| * <p> |
| * In case a lookup does not yield a value from the underlying map or dictionary, the following rules are applied: |
| * <ol> |
| * <li>primitive types yield their default value, as defined by the Java Specification; |
| * <li>string, {@link Class}es and enum values yield <code>null</code>; |
| * <li>for arrays, collections and maps, an empty array/collection/map is returned; |
| * <li>for other interface types that are treated as configuration type a null-object is returned. |
| * </ol> |
| * </p> |
| */ |
| public final class Configurable { |
| |
| static class ConfigHandler implements InvocationHandler { |
| private final ClassLoader m_cl; |
| private final Map<?, ?> m_config; |
| private Class<?> m_configType; |
| |
| /** Constant for the single element method */ |
| private static final String VALUE_METHOD = "value"; |
| |
| /** Constant for the prefix constant. */ |
| private static final String PREFIX_CONSTANT = "PREFIX_"; |
| |
| /** Capture all methods defined by the annotation interface */ |
| private static final Set<Method> ANNOTATION_METHODS = new HashSet<Method>(); |
| static |
| { |
| for(final Method m : Annotation.class.getMethods()) |
| { |
| ANNOTATION_METHODS.add(m); |
| } |
| } |
| |
| public ConfigHandler(Class<?> type, ClassLoader cl, Map<?, ?> config) { |
| m_configType = type; |
| m_cl = cl; |
| m_config = config; |
| } |
| |
| @Override |
| public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { |
| if (method.getName().equals("toString")) { |
| return m_config.toString(); |
| } |
| String name = getPropertyName(method.getName()); |
| |
| Object result = convert(method.getGenericReturnType(), name, m_config.get(name), false /* useImplicitDefault */); |
| if (result == null) { |
| Object defaultValue = getDefaultValue(proxy, args, method, name); |
| if (defaultValue != null) { |
| return defaultValue; |
| } |
| } |
| |
| return result; |
| } |
| |
| @SuppressWarnings("unchecked") |
| private Object convertParameterizedType(ParameterizedType type, String key, Object value, boolean useImplicitDefault) throws Exception { |
| Class<?> resultType = (Class<?>) type.getRawType(); |
| if (Class.class.isAssignableFrom(resultType)) { |
| if (value == null) { |
| return null; |
| } |
| return m_cl.loadClass(value.toString()); |
| } |
| else if (Collection.class.isAssignableFrom(resultType)) { |
| Collection<?> input = toCollection(key, value); |
| if (input == null && ! useImplicitDefault) { |
| return null; |
| } |
| |
| 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 = LinkedList.class; |
| } |
| else if (resultType.isInterface()) { |
| throw new RuntimeException("Unknown collection interface: " + resultType); |
| } |
| |
| Collection<Object> result = (Collection<Object>) resultType.newInstance(); |
| if (input != null) { |
| Type componentType = type.getActualTypeArguments()[0]; |
| for (Object i : input) { |
| result.add(convert(componentType, key, i, false /* useImplicitDefault */)); |
| } |
| } |
| return result; |
| } |
| else if (Map.class.isAssignableFrom(resultType)) { |
| Map<?, ?> input = toMap(key, value); |
| if (input == null && ! useImplicitDefault) { |
| return null; |
| } |
| |
| if (resultType == SortedMap.class) { |
| resultType = TreeMap.class; |
| } |
| else if (resultType == Map.class) { |
| resultType = LinkedHashMap.class; |
| } |
| else if (resultType.isInterface()) { |
| throw new RuntimeException("Unknown map interface: " + resultType); |
| } |
| |
| Map<Object, Object> result = (Map<Object, Object>) resultType.newInstance(); |
| Type keyType = type.getActualTypeArguments()[0]; |
| Type valueType = type.getActualTypeArguments()[1]; |
| |
| if (input != null) { |
| for (Map.Entry<?, ?> entry : input.entrySet()) { |
| result.put(convert(keyType, key, entry.getKey(), false /* useImplicitDefault */), convert(valueType, key, entry.getValue(), false /* useImplicitDefault */)); |
| } |
| } |
| return result; |
| } |
| |
| throw new RuntimeException("Unhandled type: " + type); |
| } |
| |
| @SuppressWarnings({ "unchecked", "rawtypes" }) |
| private Object convert(Type type, String key, Object value, boolean useImplicitDefault) throws Exception { |
| if (type instanceof ParameterizedType) { |
| return convertParameterizedType((ParameterizedType) type, key, value, useImplicitDefault); |
| } |
| if (type instanceof GenericArrayType) { |
| return convertArray(((GenericArrayType) type).getGenericComponentType(), key, value, useImplicitDefault); |
| } |
| Class<?> resultType = (Class<?>) type; |
| if (resultType.isArray()) { |
| return convertArray(resultType.getComponentType(), key, value, useImplicitDefault); |
| } |
| if (resultType.isInstance(value)) { |
| return value; |
| } |
| |
| if (Boolean.class.equals(resultType) || Boolean.TYPE.equals(resultType)) { |
| if (value == null) { |
| return useImplicitDefault && resultType.isPrimitive() ? DEFAULT_BOOLEAN : null; |
| } |
| return Boolean.valueOf(value.toString()); |
| } |
| else if (Byte.class.equals(resultType) || Byte.TYPE.equals(resultType)) { |
| if (value == null) { |
| return useImplicitDefault && resultType.isPrimitive() ? DEFAULT_BYTE : null; |
| } |
| if (value instanceof Number) { |
| return ((Number) value).byteValue(); |
| } |
| return Byte.valueOf(value.toString()); |
| } |
| else if (Short.class.equals(resultType) || Short.TYPE.equals(resultType)) { |
| if (value == null) { |
| return useImplicitDefault && resultType.isPrimitive() ? DEFAULT_SHORT : null; |
| } |
| if (value instanceof Number) { |
| return ((Number) value).shortValue(); |
| } |
| return Short.valueOf(value.toString()); |
| } |
| else if (Integer.class.equals(resultType) || Integer.TYPE.equals(resultType)) { |
| if (value == null) { |
| return useImplicitDefault && resultType.isPrimitive() ? DEFAULT_INT : null; |
| } |
| if (value instanceof Number) { |
| return ((Number) value).intValue(); |
| } |
| return Integer.valueOf(value.toString()); |
| } |
| else if (Long.class.equals(resultType) || Long.TYPE.equals(resultType)) { |
| if (value == null) { |
| return useImplicitDefault && resultType.isPrimitive() ? DEFAULT_LONG : null; |
| } |
| if (value instanceof Number) { |
| return ((Number) value).longValue(); |
| } |
| return Long.valueOf(value.toString()); |
| } |
| else if (Float.class.equals(resultType) || Float.TYPE.equals(resultType)) { |
| if (value == null) { |
| return useImplicitDefault && resultType.isPrimitive() ? DEFAULT_FLOAT : null; |
| } |
| if (value instanceof Number) { |
| return ((Number) value).floatValue(); |
| } |
| return Float.valueOf(value.toString()); |
| } |
| else if (Double.class.equals(resultType) || Double.TYPE.equals(resultType)) { |
| if (value == null) { |
| return useImplicitDefault && resultType.isPrimitive() ? DEFAULT_DOUBLE : null; |
| } |
| if (value instanceof Number) { |
| return ((Number) value).doubleValue(); |
| } |
| return Double.valueOf(value.toString()); |
| } |
| else if (Number.class.equals(resultType)) { |
| if (value == null) { |
| return null; |
| } |
| String numStr = value.toString(); |
| if (numStr.indexOf('.') > 0) { |
| return Double.valueOf(numStr); |
| } |
| return Long.valueOf(numStr); |
| } |
| else if (String.class.isAssignableFrom(resultType)) { |
| return value == null ? null : value.toString(); |
| } |
| else if (Enum.class.isAssignableFrom(resultType)) { |
| if (value == null) { |
| return null; |
| } |
| Class<Enum> enumType = (Class<Enum>) resultType; |
| return Enum.valueOf(enumType, value.toString().toUpperCase()); |
| } |
| else if (resultType.isInterface()) { |
| Map<?, ?> map = toMap(key, value); |
| if (map == null) { |
| return useImplicitDefault ? create(resultType, Collections.emptyMap()) : null; |
| } |
| return create(resultType, map); |
| } |
| |
| throw new RuntimeException("Unhandled type: " + type); |
| } |
| |
| private Object convertArray(Type type, String key, Object value, boolean useImplicitDefault) throws Exception { |
| if (value instanceof String) { |
| String str = (String) value; |
| if (type == Byte.class || type == byte.class) { |
| return str.getBytes("UTF-8"); |
| } |
| if (type == Character.class || type == char.class) { |
| return str.toCharArray(); |
| } |
| } |
| |
| Collection<?> input = toCollection(key, value); |
| if (input == null && useImplicitDefault) { |
| input = Collections.emptyList(); |
| } |
| if (input == null) { |
| return null; |
| } |
| |
| Class<?> componentClass = getRawClass(type); |
| Object array = Array.newInstance(componentClass, input.size()); |
| |
| int i = 0; |
| for (Object next : input) { |
| Array.set(array, i++, convert(type, key, next, false /* useImplicitDefault */)); |
| } |
| return array; |
| } |
| |
| private Object getDefaultValue(Object proxy, Object[] args, Method method, String key) throws Throwable { |
| Object def = null; |
| // Handle cases where the method is part of an annotation or is a java8 default method. |
| Class<?> methodClass = method.getDeclaringClass(); |
| if (methodClass.isAnnotation()) { |
| // the config type is an annotation: simply invoke the default value |
| def = method.getDefaultValue(); |
| } else if (method.isDefault()) { |
| if (System.getProperty("java.version", "1.8").startsWith("1.8")) { |
| // The config type is a java8 interface with a default method, invoke it. |
| // But it's challenging to invoke a default method from a dynamic proxy ... we have to use the MethodHandles. |
| // see https://zeroturnaround.com/rebellabs/recognize-and-conquer-java-proxies-default-methods-and-method-handles |
| |
| Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class); |
| constructor.setAccessible(true); |
| def = constructor.newInstance(methodClass, MethodHandles.Lookup.PRIVATE) |
| .unreflectSpecial(method, methodClass) |
| .bindTo(proxy) |
| .invokeWithArguments(args); |
| } else { |
| // see https://dzone.com/articles/correct-reflective-access-to-interface-default-methods |
| def = MethodHandles.lookup() |
| .findSpecial(methodClass, |
| method.getName(), |
| MethodType.methodType(method.getReturnType(), method.getParameterTypes()), |
| methodClass) |
| .bindTo(proxy) |
| .invokeWithArguments(); |
| } |
| } |
| return convert(method.getGenericReturnType(), key, def, true /* useImplicitDefault */); |
| } |
| |
| private Class<?> getRawClass(Type type) { |
| if (type instanceof ParameterizedType) { |
| return (Class<?>) ((ParameterizedType) type).getRawType(); |
| } |
| if (type instanceof Class) { |
| return (Class<?>) type; |
| } |
| throw new RuntimeException("Unhandled type: " + type); |
| } |
| |
| private Collection<?> toCollection(String prefix, Object value) { |
| if (value instanceof Collection) { |
| return (Collection<?>) value; |
| } |
| |
| if (value == null) { |
| List<Object> result = new ArrayList<>(); |
| |
| String needle = prefix.concat("."); |
| for (Map.Entry<?, ?> entry : m_config.entrySet()) { |
| String key = entry.getKey().toString(); |
| if (!key.startsWith(needle)) { |
| continue; |
| } |
| |
| int idx = 0; |
| try { |
| idx = Integer.parseInt(key.substring(needle.length())); |
| } |
| catch (NumberFormatException e) { |
| // Ignore |
| } |
| |
| result.add(Math.min(result.size(), idx), entry.getValue()); |
| } |
| |
| return result.size() == 0 ? null : result; |
| } |
| |
| if (value.getClass().isArray()) { |
| if (value.getClass().getComponentType().isPrimitive()) { |
| int length = Array.getLength(value); |
| List<Object> result = new ArrayList<Object>(length); |
| for (int i = 0; i < length; i++) { |
| result.add(Array.get(value, i)); |
| } |
| return result; |
| } |
| return Arrays.asList((Object[]) value); |
| } |
| |
| if (value instanceof String) { |
| String str = (String) value; |
| if (str.startsWith("[") && str.endsWith("]")) { |
| str = str.substring(1, str.length() - 1); |
| } |
| // don't split in case we are parsing an empty [] list, in which case we need to return an empty list. |
| return str.length() == 0 ? Collections.emptyList() : Arrays.asList(str.split("\\s*,\\s*")); |
| } |
| |
| return Arrays.asList(value); |
| } |
| |
| private Map<?, ?> toMap(String prefix, Object value) { |
| if (value instanceof Map) { |
| return (Map<?, ?>) value; |
| } |
| |
| Map<String, Object> result = new HashMap<>(); |
| if (value == null) { |
| String needle = prefix.concat("."); |
| for (Map.Entry<?, ?> entry : m_config.entrySet()) { |
| String key = entry.getKey().toString(); |
| if (key.startsWith(needle)) { |
| result.put(key.substring(needle.length()), entry.getValue()); |
| } |
| } |
| if (result.size() == 0) { |
| return null; |
| } |
| } |
| else if (value instanceof String) { |
| String str = (String) value; |
| if (str.startsWith("{") && str.endsWith("}")) { |
| str = str.substring(1, str.length() - 1); |
| } |
| for (String entry : str.split("\\s*,\\s*")) { |
| String[] pair = entry.split("\\s*\\.\\s*", 2); |
| if (pair.length == 2) { |
| result.put(pair[0], pair[1]); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| private String getPropertyName(String methodName) { |
| // First, check if the config type defines a standard PREFIX_ string. |
| String prefix = getPrefix(m_configType); |
| |
| // If the configuration type is a single valued annotation, derive the property name |
| // from the interface name, using OSGi R7 Scr convention |
| |
| if (isSingleElementAnnotation(m_configType) && methodName.equals(VALUE_METHOD)) { |
| String propertyName = mapTypeNameToKey(m_configType.getSimpleName()); |
| return prefix == null ? propertyName : prefix.concat(propertyName); |
| } |
| |
| // Now, derive the property name from the method name, using simple javabean convention. |
| // i.e: fooBar() or getFooBar() will map to "fooBar" property. |
| |
| String javaBeanMethodName = derivePropertyNameUsingJavaBeanConvention(methodName); |
| if (hasValueFor(javaBeanMethodName)) { |
| // there is a value in the actual configuration for the derived property name. |
| return javaBeanMethodName; |
| } |
| |
| // Derive the property name from the method name, using javabeans and/or camel casing convention, |
| // where each capital letter is assumed to map a "dot". |
| // i.e: fooBar() or getFooBar() will map to "foo.bar" property. |
| |
| String camelCasePropertyName = derivePropertyNameUsingCamelCaseConvention(javaBeanMethodName); |
| if (hasValueFor(camelCasePropertyName)) { |
| // there is a value in the actual configuration for the derived property name. |
| return camelCasePropertyName; |
| } |
| |
| // Derive the property name from the method name, using OSGi metatype convention, |
| // where a "_" is mapped to a dot, except if the understcore is followed by another undescore |
| // (in this case, the double "__" is replaced by "_"). |
| // i.e: foo_bar() will map to "foo.bar" property and foo__bar() will map to "foo_bar" property. |
| |
| String metaTypePropertyName = derivePropertyNameUsingMetaTypeConvention(methodName); |
| if (hasValueFor(metaTypePropertyName)) { |
| // there is a value in the actual configuration for the derived property name. |
| return metaTypePropertyName; |
| } |
| |
| // No value could be found, return by default a property name derived from javabean convention. |
| return javaBeanMethodName; |
| } |
| |
| private String derivePropertyNameUsingJavaBeanConvention(String methodName) { |
| StringBuilder sb = new StringBuilder(methodName); |
| |
| if (methodName.startsWith("get")) { |
| sb.delete(0, 3); |
| } else if (methodName.startsWith("is")) { |
| sb.delete(0, 2); |
| } |
| |
| char c = sb.charAt(0); |
| if (Character.isUpperCase(c)) { |
| sb.setCharAt(0, Character.toLowerCase(c)); |
| } |
| |
| return (sb.toString()); |
| } |
| |
| private String derivePropertyNameUsingCamelCaseConvention(String methodName) { |
| StringBuilder sb = new StringBuilder(methodName); |
| for (int i = 0; i < sb.length(); i++) { |
| char c = sb.charAt(i); |
| if (Character.isUpperCase(c)) { |
| // camel casing: replace fooBar -> foo.bar |
| sb.setCharAt(i, Character.toLowerCase(c)); |
| sb.insert(i, "."); |
| } |
| } |
| return sb.toString(); |
| } |
| |
| // see metatype spec, chapter 105.9.2 in osgi r6 cmpn. |
| private String derivePropertyNameUsingMetaTypeConvention(String methodName) { |
| StringBuilder sb = new StringBuilder(methodName); |
| // replace "__" by "_" or "_" by ".": foo_bar -> foo.bar; foo__BAR_zoo -> foo_BAR.zoo |
| for (int i = 0; i < sb.length(); i ++) { |
| if (sb.charAt(i) == '_') { |
| if (i < (sb.length() - 1) && sb.charAt(i+1) == '_') { |
| // replace foo__bar -> foo_bar |
| sb.replace(i, i+2, "_"); |
| } else { |
| // replace foo_bar -> foo.bar |
| sb.replace(i, i+1, "."); |
| } |
| } else if (sb.charAt(i) == '$') { |
| if (i < (sb.length() - 1) && sb.charAt(i+1) == '$') { |
| // replace foo__bar -> foo_bar |
| sb.replace(i, i+2, "$"); |
| } else { |
| // remove single dollar character. |
| sb.delete(i, i+1); |
| } |
| } |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Checks if a property name has a given value. This method takes care about special array values (arr.0, arr.1,...) |
| * and about map values (map.key1, map.key2, ...). |
| * |
| * @param property name |
| * @return true if the given property has a value in the actual configuration, false if not. |
| */ |
| private boolean hasValueFor(String property) |
| { |
| if (m_config.containsKey(property)) { |
| return true; |
| } |
| String needle = property.concat("."); |
| for (Map.Entry<?, ?> entry : m_config.entrySet()) { |
| String key = entry.getKey().toString(); |
| if (key.startsWith(needle)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| // Code derived from Apache Felix SCR (See org.apache.felix.scr.impl.inject.Annotations.java) |
| private String getPrefix(Class<?> clazz) |
| { |
| try |
| { |
| final Field f = clazz.getField(PREFIX_CONSTANT); |
| if ( Modifier.isStatic(f.getModifiers()) |
| && Modifier.isPublic(f.getModifiers()) |
| && Modifier.isFinal(f.getModifiers()) |
| && String.class.isAssignableFrom(f.getType())) |
| { |
| final Object value = f.get(null); |
| if ( value != null ) |
| { |
| return value.toString(); |
| } |
| } |
| } |
| catch ( final Exception ignore) |
| { |
| // ignore |
| } |
| return null; |
| } |
| |
| /** |
| * Check whether the provided type is a single element annotation. |
| * A single element annotation has a method named "value" and all |
| * other annotation methods must have a default value. |
| * @param clazz The provided type |
| * @return {@code true} if the type is a single element annotation. |
| */ |
| static public boolean isSingleElementAnnotation(final Class<?> clazz) |
| { |
| boolean result = false; |
| if ( clazz.isAnnotation() ) |
| { |
| result = true; |
| boolean hasValue = false; |
| for ( final Method method: clazz.getMethods() ) |
| { |
| // filter out methods from Annotation |
| boolean isFromAnnotation = false; |
| for(final Method objMethod : ANNOTATION_METHODS) |
| { |
| if ( objMethod.getName().equals(method.getName()) |
| && Arrays.equals(objMethod.getParameterTypes(), method.getParameterTypes()) ) |
| { |
| isFromAnnotation = true; |
| break; |
| } |
| } |
| if ( isFromAnnotation ) |
| { |
| continue; |
| } |
| if ( VALUE_METHOD.equals(method.getName()) ) |
| { |
| hasValue = true; |
| continue; |
| } |
| if ( method.getDefaultValue() == null ) |
| { |
| result = false; |
| break; |
| } |
| |
| } |
| if ( result ) |
| { |
| result = hasValue; |
| } |
| |
| } |
| return result; |
| } |
| |
| static String mapTypeNameToKey(String name) |
| { |
| final StringBuilder sb = new StringBuilder(); |
| boolean lastLow = false; |
| for(final char c : name.toCharArray()) |
| { |
| if ( lastLow && (Character.isLetter(c) || Character.isDigit(c)) && Character.isUpperCase(c) ) |
| { |
| sb.append('.'); |
| } |
| lastLow = false; |
| if ( (Character.isLetter(c) || Character.isDigit(c)) && Character.isLowerCase(c)) |
| { |
| lastLow = true; |
| } |
| sb.append(Character.toLowerCase(c)); |
| } |
| return sb.toString(); |
| } |
| |
| } |
| |
| private static final Boolean DEFAULT_BOOLEAN = Boolean.FALSE; |
| private static final Byte DEFAULT_BYTE = new Byte((byte) 0); |
| private static final Short DEFAULT_SHORT = new Short((short) 0); |
| private static final Integer DEFAULT_INT = new Integer(0); |
| private static final Long DEFAULT_LONG = new Long(0); |
| private static final Float DEFAULT_FLOAT = new Float(0.0f); |
| private static final Double DEFAULT_DOUBLE = new Double(0.0); |
| |
| /** |
| * Creates a configuration for a given type backed by a given dictionary. |
| * |
| * @param type the configuration class, cannot be <code>null</code>; |
| * @param config the configuration to wrap, cannot be <code>null</code>. |
| * @param serviceProperties the component service properties, cannot be <code>null</code>. |
| * @return an instance of the given type that wraps the given configuration. |
| */ |
| public static <T> T create(Class<T> type, Dictionary<?, ?> config, Dictionary<?,?> serviceProperties) { |
| Map<Object, Object> map = new HashMap<Object, Object>(); |
| for (Enumeration<?> e = serviceProperties.keys(); e.hasMoreElements();) { |
| Object key = e.nextElement(); |
| map.put(key, serviceProperties.get(key)); |
| } |
| for (Enumeration<?> e = config.keys(); e.hasMoreElements();) { |
| Object key = e.nextElement(); |
| map.put(key, config.get(key)); |
| } |
| return create(type, map); |
| } |
| |
| /** |
| * Creates a configuration for a given type backed by a given map. |
| * |
| * @param type the configuration class, cannot be <code>null</code>; |
| * @param config the configuration to wrap, cannot be <code>null</code>. |
| * @return an instance of the given type that wraps the given configuration. |
| */ |
| public static <T> T create(Class<T> type, Map<?, ?> config) { |
| ClassLoader cl = type.getClassLoader(); |
| Object result = Proxy.newProxyInstance(cl, new Class<?>[] { type }, new ConfigHandler(type, cl, config)); |
| return type.cast(result); |
| } |
| } |