content: /**
   *  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 static org.apache.aries.blueprint.utils.ReflectionUtils.getRealCause;
  
  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;
  
  /**
   * 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();
      }
      
  }