blob: e1ef7303ae004ce8b833c1c969e2ac1dc6e50e95 [file] [log] [blame]
/**
* 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.Type;
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.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Hashtable;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
import java.math.BigInteger;
import java.math.BigDecimal;
import org.apache.aries.blueprint.ExtendedBlueprintContainer;
import org.apache.aries.blueprint.di.CollectionRecipe;
import org.apache.aries.blueprint.di.MapRecipe;
import org.apache.aries.blueprint.utils.ReflectionUtils;
import static org.apache.aries.blueprint.utils.ReflectionUtils.getRealCause;
import org.osgi.service.blueprint.container.ReifiedType;
import org.osgi.service.blueprint.container.Converter;
/**
* 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 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(final Object fromValue, final ReifiedType toType) {
if (fromValue == null) {
return true;
}
if (isAssignable(fromValue, toType)) {
return true;
}
boolean canConvert = false;
AccessControlContext acc = blueprintContainer.getAccessControlContext();
if (acc == null) {
canConvert = canConvertWithConverters(fromValue, toType);
} else {
canConvert = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
public Boolean run() {
return canConvertWithConverters(fromValue, toType);
}
}, acc);
}
if (canConvert) {
return true;
}
// TODO implement better logic ?!
try {
convert(fromValue, toType);
return true;
} catch (Exception e) {
return false;
}
}
public Object convert(final 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);
}
// If the object is an instance of the type, just return it
if (isAssignable(fromValue, type)) {
return fromValue;
}
Object value = null;
AccessControlContext acc = blueprintContainer.getAccessControlContext();
if (acc == null) {
value = convertWithConverters(fromValue, type);
} else {
value = AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() {
public Object run() throws Exception {
return convertWithConverters(fromValue, type);
}
}, acc);
}
if (value == 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 value;
}
private boolean canConvertWithConverters(Object source, ReifiedType type) {
for (Converter converter : converters) {
if (converter.canConvert(source, type)) {
return true;
}
}
return false;
}
private Object convertWithConverters(Object source, ReifiedType type) throws Exception {
Object value = null;
for (Converter converter : converters) {
if (converter.canConvert(source, type)) {
value = converter.convert(source, type);
if (value != null) {
return value;
}
}
}
return 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 {
newCol.add(convert(Array.get(obj, i), valueType));
} catch (Exception t) {
throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting array element)", t);
}
}
} else {
for (Object item : (Collection) obj) {
try {
newCol.add(convert(item, valueType));
} catch (Exception t) {
throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting collection entry)", t);
}
}
}
return newCol;
}
private Object convertToDictionary(Object obj, ReifiedType type) throws Exception {
ReifiedType keyType = type.getActualTypeArgument(0);
ReifiedType valueType = type.getActualTypeArgument(1);
Dictionary newDic = new Hashtable();
if (obj instanceof Dictionary) {
Dictionary dic = (Dictionary) obj;
for (Enumeration keyEnum = dic.keys(); keyEnum.hasMoreElements();) {
Object key = keyEnum.nextElement();
try {
newDic.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);
}
}
} else {
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);
}
}
} else {
for (Map.Entry e : ((Map<Object,Object>) obj).entrySet()) {
try {
newMap.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 newMap;
}
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));
for (int i = 0; i < Array.getLength(obj); i++) {
try {
Array.set(array, i, convert(Array.get(obj, i), componentType));
} catch (Exception t) {
throw new Exception("Unable to convert from " + obj + " to " + type + "(error converting array element)", t);
}
}
return array;
}
public static boolean isAssignable(Object source, ReifiedType target) {
return source == null
|| (target.size() == 0
&& unwrap(target.getRawClass()).isAssignableFrom(unwrap(source.getClass())));
}
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();
}
}