/** * 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.aries.blueprint.container; import java.io.ByteArrayInputStream; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.math.BigDecimal; import java.math.BigInteger; import java.security.AccessControlContext; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.util.ArrayList; import java.util.Collection; import java.util.Dictionary; import java.util.Enumeration; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.regex.Pattern; import org.apache.aries.blueprint.container.BeanRecipe.UnwrapperedBeanHolder; import org.apache.aries.blueprint.container.GenericType.BoundType; import org.apache.aries.blueprint.di.CollectionRecipe; import org.apache.aries.blueprint.di.MapRecipe; import org.apache.aries.blueprint.services.ExtendedBlueprintContainer; import org.apache.aries.blueprint.utils.ReflectionUtils; import org.osgi.service.blueprint.container.Converter; import org.osgi.service.blueprint.container.ReifiedType; import static org.apache.aries.blueprint.utils.ReflectionUtils.getRealCause; /** * Implementation of the Converter. * * This object contains all the registered Converters which can be registered * by using {@link #registerConverter(Converter)} * and unregistered using {@link #unregisterConverter(Converter)}. * * Each {@link org.osgi.service.blueprint.container.BlueprintContainer} has its own AggregateConverter * used to register converters defined by the related blueprint bundle. * * @version $Rev$, $Date$ */ public class AggregateConverter implements Converter { /** * Objects implementing this interface will bypass the default conversion rules * and be called directly to transform into the expected type. */ public static interface Convertible { Object convert(ReifiedType type) throws Exception; } private static class ConversionResult { public final Converter converter; public final Object value; public ConversionResult(Converter converter, Object value) { this.converter = converter; this.value = value; } } private ExtendedBlueprintContainer blueprintContainer; private List<Converter> converters = new ArrayList<Converter>(); public AggregateConverter(ExtendedBlueprintContainer blueprintContainer) { this.blueprintContainer = blueprintContainer; } public void registerConverter(Converter converter) { converters.add(converter); } public void unregisterConverter(Converter converter) { converters.remove(converter); } public boolean canConvert(Object fromValue, final ReifiedType toType) { if (fromValue == null) { return true; } else if (fromValue instanceof UnwrapperedBeanHolder) { fromValue = ((UnwrapperedBeanHolder) fromValue).unwrapperedBean; } if (isAssignable(fromValue, toType)) { return true; } final Object toTest = fromValue; boolean canConvert = false; AccessControlContext acc = blueprintContainer.getAccessControlContext(); if (acc == null) { canConvert = canConvertWithConverters(toTest, toType); } else { canConvert = AccessController.doPrivileged(new PrivilegedAction<Boolean>() { public Boolean run() { return canConvertWithConverters(toTest, toType); } }, acc); } if (canConvert) { return true; } // TODO implement better logic ?! try { convert(toTest, toType); return true; } catch (Exception e) { return false; } } public Object convert(Object fromValue, final ReifiedType type) throws Exception { // Discard null values if (fromValue == null) { return null; } // First convert service proxies if (fromValue instanceof Convertible) { return ((Convertible) fromValue).convert(type); } else if (fromValue instanceof UnwrapperedBeanHolder) { UnwrapperedBeanHolder holder = (UnwrapperedBeanHolder) fromValue; if (isAssignable(holder.unwrapperedBean, type)) { return BeanRecipe.wrap(holder, type.getRawClass()); } else { fromValue = BeanRecipe.wrap(holder, Object.class); } } else if (isAssignable(fromValue, type)) { // If the object is an instance of the type, just return it return fromValue; } final Object finalFromValue = fromValue; ConversionResult result = null; AccessControlContext acc = blueprintContainer.getAccessControlContext(); if (acc == null) { result = convertWithConverters(fromValue, type); } else { result = AccessController.doPrivileged(new PrivilegedExceptionAction<ConversionResult>() { public ConversionResult run() throws Exception { return convertWithConverters(finalFromValue, type); } }, acc); } if (result == null) { if (fromValue instanceof Number && Number.class.isAssignableFrom(unwrap(toClass(type)))) { return convertToNumber((Number) fromValue, toClass(type)); } else if (fromValue instanceof String) { return convertFromString((String) fromValue, toClass(type), blueprintContainer); } else if (toClass(type).isArray() && (fromValue instanceof Collection || fromValue.getClass().isArray())) { return convertToArray(fromValue, type); } else if (Map.class.isAssignableFrom(toClass(type)) && (fromValue instanceof Map || fromValue instanceof Dictionary)) { return convertToMap(fromValue, type); } else if (Dictionary.class.isAssignableFrom(toClass(type)) && (fromValue instanceof Map || fromValue instanceof Dictionary)) { return convertToDictionary(fromValue, type); } else if (Collection.class.isAssignableFrom(toClass(type)) && (fromValue instanceof Collection || fromValue.getClass().isArray())) { return convertToCollection(fromValue, type); } else { throw new Exception("Unable to convert value " + fromValue + " to type " + type); } } return result.value; } private Converter selectMatchingConverter(Object source, ReifiedType type) { for (Converter converter : converters) { if (converter.canConvert(source, type)) { return converter; } } return null; } private boolean canConvertWithConverters(Object source, ReifiedType type) { return selectMatchingConverter(source,type) != null; } private ConversionResult convertWithConverters(Object source, ReifiedType type) throws Exception { Converter converter = selectMatchingConverter(source,type); if (converter == null) return null; Object value = converter.convert(source, type); return new ConversionResult(converter,value); } public Object convertToNumber(Number value, Class toType) throws Exception { toType = unwrap(toType); if (AtomicInteger.class == toType) { return new AtomicInteger((Integer) convertToNumber(value, Integer.class)); } else if (AtomicLong.class == toType) { return new AtomicLong((Long) convertToNumber(value, Long.class)); } else if (Integer.class == toType) { return value.intValue(); } else if (Short.class == toType) { return value.shortValue(); } else if (Long.class == toType) { return value.longValue(); } else if (Float.class == toType) { return value.floatValue(); } else if (Double.class == toType) { return value.doubleValue(); } else if (Byte.class == toType) { return value.byteValue(); } else if (BigInteger.class == toType) { return new BigInteger(value.toString()); } else if (BigDecimal.class == toType) { return new BigDecimal(value.toString()); } else { throw new Exception("Unable to convert number " + value + " to " + toType); } } public Object convertFromString(String value, Class toType, Object loader) throws Exception { toType = unwrap(toType); if (ReifiedType.class == toType) { try { return GenericType.parse(value, loader); } catch (ClassNotFoundException e) { throw new Exception("Unable to convert", e); } } else if (Class.class == toType) { try { return GenericType.parse(value, loader).getRawClass(); } catch (ClassNotFoundException e) { throw new Exception("Unable to convert", e); } } else if (Locale.class == toType) { String[] tokens = value.split("_"); if (tokens.length == 1) { return new Locale(tokens[0]); } else if (tokens.length == 2) { return new Locale(tokens[0], tokens[1]); } else if (tokens.length == 3) { return new Locale(tokens[0], tokens[1], tokens[2]); } else { throw new Exception("Invalid locale string:" + value); } } else if (Pattern.class == toType) { return Pattern.compile(value); } else if (Properties.class == toType) { Properties props = new Properties(); ByteArrayInputStream in = new ByteArrayInputStream(value.getBytes("UTF8")); props.load(in); return props; } else if (Boolean.class == toType) { if ("yes".equalsIgnoreCase(value) || "true".equalsIgnoreCase(value) || "on".equalsIgnoreCase(value)) { return Boolean.TRUE; } else if ("no".equalsIgnoreCase(value) || "false".equalsIgnoreCase(value) || "off".equalsIgnoreCase(value)) { return Boolean.FALSE; } else { throw new RuntimeException("Invalid boolean value: " + value); } } else if (Integer.class == toType) { return Integer.valueOf(value); } else if (Short.class == toType) { return Short.valueOf(value); } else if (Long.class == toType) { return Long.valueOf(value); } else if (Float.class == toType) { return Float.valueOf(value); } else if (Double.class == toType) { return Double.valueOf(value); } else if (Character.class == toType) { if (value.length() == 6 && value.startsWith("\\u")) { int code = Integer.parseInt(value.substring(2), 16); return (char)code; } else if (value.length() == 1) { return value.charAt(0); } else { throw new Exception("Invalid value for character type: " + value); } } else if (Byte.class == toType) { return Byte.valueOf(value); } else if (Enum.class.isAssignableFrom(toType)) { return Enum.valueOf((Class<Enum>) toType, value); } else { return createObject(value, toType); } } private Object createObject(String value, Class type) throws Exception { if (type.isInterface() || Modifier.isAbstract(type.getModifiers())) { throw new Exception("Unable to convert value " + value + " to type " + type + ". Type " + type + " is an interface or an abstract class"); } Constructor constructor = null; try { constructor = type.getConstructor(String.class); } catch (NoSuchMethodException e) { throw new RuntimeException("Unable to convert to " + type); } try { return ReflectionUtils.newInstance(blueprintContainer.getAccessControlContext(), constructor, value); } catch (Exception e) { throw new Exception("Unable to convert ", getRealCause(e)); } } private Object convertToCollection(Object obj, ReifiedType type) throws Exception { ReifiedType valueType = type.getActualTypeArgument(0); Collection newCol = (Collection) ReflectionUtils.newInstance(blueprintContainer.getAccessControlContext(), CollectionRecipe.getCollection(toClass(type))); if (obj.getClass().isArray()) { for (int i = 0; i < Array.getLength(obj); i++) { try { Object ov = Array.get(obj, i); Object cv = convert(ov, valueType); newCol.add(cv); } catch (Exception t) { throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting array element)", t); } } return newCol; } else { boolean converted = !toClass(type).isAssignableFrom(obj.getClass()); for (Object item : (Collection) obj) { try { Object cv = convert(item, valueType); converted |= item != cv; newCol.add(cv); } catch (Exception t) { throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting collection entry)", t); } } return converted ? newCol : obj; } } private Object convertToDictionary(Object obj, ReifiedType type) throws Exception { ReifiedType keyType = type.getActualTypeArgument(0); ReifiedType valueType = type.getActualTypeArgument(1); if (obj instanceof Dictionary) { Dictionary newDic = new Hashtable(); Dictionary dic = (Dictionary) obj; boolean converted = false; for (Enumeration keyEnum = dic.keys(); keyEnum.hasMoreElements();) { Object key = keyEnum.nextElement(); try { Object nk = convert(key, keyType); Object ov = dic.get(key); Object nv = convert(ov, valueType); newDic.put(nk, nv); converted |= nk != key || nv != ov; } catch (Exception t) { throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting map entry)", t); } } return converted ? newDic : obj; } else { Dictionary newDic = new Hashtable(); for (Map.Entry e : ((Map<Object,Object>) obj).entrySet()) { try { newDic.put(convert(e.getKey(), keyType), convert(e.getValue(), valueType)); } catch (Exception t) { throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting map entry)", t); } } return newDic; } } private Object convertToMap(Object obj, ReifiedType type) throws Exception { ReifiedType keyType = type.getActualTypeArgument(0); ReifiedType valueType = type.getActualTypeArgument(1); Map newMap = (Map) ReflectionUtils.newInstance(blueprintContainer.getAccessControlContext(), MapRecipe.getMap(toClass(type))); if (obj instanceof Dictionary) { Dictionary dic = (Dictionary) obj; for (Enumeration keyEnum = dic.keys(); keyEnum.hasMoreElements();) { Object key = keyEnum.nextElement(); try { newMap.put(convert(key, keyType), convert(dic.get(key), valueType)); } catch (Exception t) { throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting map entry)", t); } } return newMap; } else { boolean converted = false; for (Map.Entry e : ((Map<Object,Object>) obj).entrySet()) { try { Object nk = convert(e.getKey(), keyType); Object nv = convert(e.getValue(), valueType); converted |= nk != e.getKey() || nv != e.getValue(); newMap.put(nk, nv); } catch (Exception t) { throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting map entry)", t); } } return converted ? newMap : obj; } } private Object convertToArray(Object obj, ReifiedType type) throws Exception { if (obj instanceof Collection) { obj = ((Collection) obj).toArray(); } if (!obj.getClass().isArray()) { throw new Exception("Unable to convert from " + obj + " to " + type); } ReifiedType componentType; if (type.size() > 0) { componentType = type.getActualTypeArgument(0); } else { componentType = new GenericType(type.getRawClass().getComponentType()); } Object array = Array.newInstance(toClass(componentType), Array.getLength(obj)); boolean converted = array.getClass() != obj.getClass(); for (int i = 0; i < Array.getLength(obj); i++) { try { Object ov = Array.get(obj, i); Object nv = convert(ov, componentType); converted |= nv != ov; Array.set(array, i, nv); } catch (Exception t) { throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting array element)", t); } } return converted ? array : obj; } public static boolean isAssignable(Object source, ReifiedType target) { if (source == null) { return true; } if (target.size() == 0) { return unwrap(target.getRawClass()).isAssignableFrom(unwrap(source.getClass())); } else { return isTypeAssignable(getType(source), target); } } private static ReifiedType getType(Object source) { if (source instanceof Class) { return new GenericType(Class.class, new GenericType((Class) source)); } else { return new GenericType(source.getClass()); } } public static boolean isTypeAssignable(ReifiedType from, ReifiedType to) { if (from.equals(to)) { return true; } if (from.getRawClass() == to.getRawClass()) { if (to.size() == 0) { return true; } if (from.size() == to.size()) { boolean ok = true; for (int i = 0; i < from.size(); i++) { ReifiedType tf = from.getActualTypeArgument(i); ReifiedType tt = to.getActualTypeArgument(i); if (!isWildcardCompatible(tf, tt)) { ok = false; break; } } if (ok) { return true; } } } else { ReifiedType t = getExactSuperType(from, to.getRawClass()); if (t != null) { return isTypeAssignable(t, to); } } return false; } private static ReifiedType getExactSuperType(ReifiedType from, Class<?> to) { if (from.getRawClass() == to) { return from; } if (!to.isAssignableFrom(from.getRawClass())) { return null; } for (ReifiedType superType: getExactDirectSuperTypes(from)) { ReifiedType result = getExactSuperType(superType, to); if (result != null) return result; } return null; } public static ReifiedType[] getExactDirectSuperTypes(ReifiedType type) { Class<?> clazz = type.getRawClass(); Type[] superInterfaces = clazz.getGenericInterfaces(); Type superClass = clazz.getGenericSuperclass(); // the only supertype of an interface without superinterfaces is Object if (superClass == null && superInterfaces.length == 0 && clazz.isInterface()) { return new ReifiedType[] { new GenericType(Object.class) }; } ReifiedType[] result; int resultIndex; if (superClass == null) { result = new ReifiedType[superInterfaces.length]; resultIndex = 0; } else { result = new ReifiedType[superInterfaces.length + 1]; resultIndex = 1; result[0] = mapTypeParameters(superClass, type); } for (Type superInterface : superInterfaces) { result[resultIndex++] = mapTypeParameters(superInterface, type); } return result; } private static ReifiedType mapTypeParameters(Type toMapType, ReifiedType typeAndParams) { if (typeAndParams.size() == 0 && typeAndParams.getRawClass().getTypeParameters().length > 0) { // Missing generics information, return erased type return new GenericType(new GenericType(toMapType).getRawClass()); } if (toMapType instanceof Class) { return new GenericType(toMapType); } Map<TypeVariable<?>, GenericType> map = new HashMap<TypeVariable<?>, GenericType>(); for (int i = 0; i < typeAndParams.size(); i++) { map.put(typeAndParams.getRawClass().getTypeParameters()[i], (GenericType) typeAndParams.getActualTypeArgument(i)); } return map(map, toMapType); } private static GenericType map(Map<TypeVariable<?>, GenericType> map, Type type) { if (type instanceof Class) { return new GenericType(type); } if (type instanceof TypeVariable) { TypeVariable<?> tv = (TypeVariable<?>) type; if (!map.containsKey(type)) { throw new IllegalArgumentException("Unable to resolve TypeVariable: " + tv); } return map.get(type); } if (type instanceof ParameterizedType) { ParameterizedType pType = (ParameterizedType) type; GenericType[] args = new GenericType[pType.getActualTypeArguments().length]; for (int i = 0; i < args.length; i++) { args[i] = map(map, pType.getActualTypeArguments()[i]); } return new GenericType((Class<?>) pType.getRawType(), args); } throw new RuntimeException("not implemented: mapping " + type.getClass() + " (" + type + ")"); } private static boolean isWildcardCompatible(ReifiedType from, ReifiedType to) { BoundType fromBoundType = GenericType.boundType(from); BoundType toBoundType = GenericType.boundType(to); if (toBoundType == BoundType.Extends) { return fromBoundType != BoundType.Super && isTypeAssignable(from, GenericType.bound(to)); } else if (toBoundType == BoundType.Super) { return fromBoundType != BoundType.Extends && isTypeAssignable(GenericType.bound(to), from); } else { return fromBoundType == BoundType.Exact && GenericType.bound(from).equals(GenericType.bound(to)); } } private static Class unwrap(Class c) { Class u = primitives.get(c); return u != null ? u : c; } private static final Map<Class, Class> primitives; static { primitives = new HashMap<Class, Class>(); primitives.put(byte.class, Byte.class); primitives.put(short.class, Short.class); primitives.put(char.class, Character.class); primitives.put(int.class, Integer.class); primitives.put(long.class, Long.class); primitives.put(float.class, Float.class); primitives.put(double.class, Double.class); primitives.put(boolean.class, Boolean.class); } public Object convert(Object source, Type target) throws Exception { return convert( source, new GenericType(target)); } private Class toClass(ReifiedType type) { return type.getRawClass(); } }