| /* |
| * 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.tamaya.spisupport; |
| |
| import org.apache.tamaya.ConfigException; |
| import org.apache.tamaya.TypeLiteral; |
| import org.apache.tamaya.spi.ConversionContext; |
| import org.apache.tamaya.spi.PropertyConverter; |
| import org.apache.tamaya.spi.ServiceContext; |
| |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.lang.reflect.Type; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| import java.util.*; |
| import java.util.concurrent.ConcurrentHashMap; |
| import java.util.concurrent.locks.Lock; |
| import java.util.concurrent.locks.ReadWriteLock; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| /** |
| * Manager that deals with {@link org.apache.tamaya.spi.PropertyConverter} instances. |
| * This class is thread-safe. |
| */ |
| public class PropertyConverterManager { |
| /** |
| * The logger used. |
| */ |
| private static final Logger LOG = Logger.getLogger(PropertyConverterManager.class.getName()); |
| /** |
| * The registered converters. |
| */ |
| private final Map<TypeLiteral<?>, List<PropertyConverter<?>>> converters = new ConcurrentHashMap<>(); |
| /** |
| * The transitive converters. |
| */ |
| private final Map<TypeLiteral<?>, List<PropertyConverter<?>>> transitiveConverters = new ConcurrentHashMap<>(); |
| /** |
| * The lock used. |
| */ |
| private final ReadWriteLock lock = new ReentrantReadWriteLock(); |
| |
| private static final Comparator<Object> PRIORITY_COMPARATOR = new Comparator<Object>() { |
| |
| @Override |
| public int compare(Object o1, Object o2) { |
| int prio = PriorityServiceComparator.getPriority(o1) - PriorityServiceComparator.getPriority(o2); |
| if (prio < 0) { |
| return 1; |
| } else if (prio > 0) { |
| return -1; |
| } else { |
| return o1.getClass().getSimpleName().compareTo(o2.getClass().getSimpleName()); |
| } |
| } |
| }; |
| |
| private final ServiceContext serviceContext; |
| |
| /** |
| * Creates a new instance. |
| * @param serviceContext the Service context, not null. |
| */ |
| public PropertyConverterManager(ServiceContext serviceContext) { |
| this(serviceContext, false); |
| } |
| |
| /** |
| * Creates a new instance. |
| * @param serviceContext the Service context, not null. |
| * @param init if true, the converters are loaded eagerly. |
| */ |
| public PropertyConverterManager(ServiceContext serviceContext, boolean init) { |
| this.serviceContext = Objects.requireNonNull(serviceContext); |
| if (init) { |
| initConverters(); |
| } |
| } |
| |
| /** |
| * Registers the default converters provided out of the box. |
| */ |
| @SuppressWarnings({ "rawtypes", "unchecked" }) |
| protected void initConverters() { |
| for (PropertyConverter conv : serviceContext.getServices(PropertyConverter.class)) { |
| Type type = TypeLiteral.getGenericInterfaceTypeParameters(conv.getClass(), PropertyConverter.class)[0]; |
| register(TypeLiteral.of(type), conv); |
| } |
| } |
| |
| |
| /** |
| * Registers a new converters instance. |
| * |
| * @param targetType the target type, not {@code null}. |
| * @param converter the converters, not {@code null}. |
| * @param <T> the type. |
| */ |
| @SuppressWarnings("unchecked") |
| public <T> void register(TypeLiteral<T> targetType, PropertyConverter<T> converter) { |
| Objects.requireNonNull(converter); |
| Lock writeLock = lock.writeLock(); |
| try { |
| writeLock.lock(); |
| List<PropertyConverter<?>> converters = List.class.cast(this.converters.get(targetType)); |
| if(converters!=null && converters.contains(converter)){ |
| return; |
| } |
| List<PropertyConverter<?>> newConverters = new ArrayList<>(); |
| if (converters != null) { |
| newConverters.addAll(converters); |
| } |
| if(!newConverters.contains(converter)) { |
| newConverters.add(converter); |
| } |
| Collections.sort(newConverters, PRIORITY_COMPARATOR); |
| this.converters.put(targetType, Collections.unmodifiableList(newConverters)); |
| // evaluate transitive closure for all inherited supertypes and implemented interfaces |
| // direct implemented interfaces |
| for (Class<?> ifaceType : targetType.getRawType().getInterfaces()) { |
| converters = List.class.cast(this.transitiveConverters.get(TypeLiteral.of(ifaceType))); |
| newConverters = new ArrayList<>(); |
| if (converters != null) { |
| newConverters.addAll(converters); |
| } |
| newConverters.add(converter); |
| Collections.sort(newConverters, PRIORITY_COMPARATOR); |
| this.transitiveConverters.put(TypeLiteral.of(ifaceType), Collections.unmodifiableList(newConverters)); |
| } |
| Class<?> superClass = targetType.getRawType().getSuperclass(); |
| while (superClass != null && !superClass.equals(Object.class)) { |
| converters = List.class.cast(this.transitiveConverters.get(TypeLiteral.of(superClass))); |
| newConverters = new ArrayList<>(); |
| if (converters != null) { |
| newConverters.addAll(converters); |
| } |
| newConverters.add(converter); |
| Collections.sort(newConverters, PRIORITY_COMPARATOR); |
| this.transitiveConverters.put(TypeLiteral.of(superClass), Collections.unmodifiableList(newConverters)); |
| for (Class<?> ifaceType : superClass.getInterfaces()) { |
| converters = List.class.cast(this.transitiveConverters.get(TypeLiteral.of(ifaceType))); |
| newConverters = new ArrayList<>(); |
| if (converters != null) { |
| newConverters.addAll(converters); |
| } |
| newConverters.add(converter); |
| Collections.sort(newConverters, PRIORITY_COMPARATOR); |
| this.transitiveConverters.put(TypeLiteral.of(ifaceType), Collections.unmodifiableList(newConverters)); |
| } |
| superClass = superClass.getSuperclass(); |
| } |
| } finally { |
| writeLock.unlock(); |
| } |
| } |
| |
| /** |
| * Allows to evaluate if a given target type is supported. |
| * |
| * @param targetType the target type, not {@code null}. |
| * @return true, if a converter for the given type is registered or a default one can be created. |
| */ |
| public boolean isTargetTypeSupported(TypeLiteral<?> targetType) { |
| return converters.containsKey(targetType) || transitiveConverters.containsKey(targetType) || createDefaultPropertyConverter(targetType) != null; |
| } |
| |
| /** |
| * Get a map of all property converters currently registered. This will not contain the converters that |
| * may be created, when an instance is adapted, which provides a String constructor or compatible |
| * factory methods taking a single String instance. |
| * |
| * @return the current map of instantiated and registered converters. |
| * @see #createDefaultPropertyConverter(org.apache.tamaya.TypeLiteral) |
| */ |
| public Map<TypeLiteral<?>, List<PropertyConverter<?>>> getPropertyConverters() { |
| Lock readLock = lock.readLock(); |
| try { |
| readLock.lock(); |
| return new HashMap<>(this.converters); |
| } finally { |
| readLock.unlock(); |
| } |
| } |
| |
| |
| /** |
| * Get the createList of all current registered converters for the given target type. |
| * |
| * <p> |
| * If no converters are registered, the component tries to createObject and register dynamic |
| * converters based on String constructor or static factory methods available. |
| * The converters provided are of the following type and returned in the following order: |
| * <ul> |
| * <li>Converters mapped explicitly to the required target type are returned first, ordered |
| * by decreasing priority. This means, if explicit converters are registered these are used |
| * primarily for converting a createValue.</li> |
| * <li>The target type of each explicitly registered converter can also be transitively mapped to |
| * 1) all directly implemented interfaces, 2) all its superclasses (except Object), 3) all the interfaces |
| * implemented by its superclasses. This group of transitive converters is returned similarly in the |
| * order already mentioned, whereas here a priority based on decreasing ordering is applied.</li> |
| * <li>java.lang wrapper classes and native types are automatically mapped.</li> |
| * <li>If no explicit converters are registered, for Enum types a default implementation is provided that |
| * compares the configuration values with the different enum members defined (case sensitive mapping).</li> |
| * </ul> |
| * <p> |
| * The above createList of directly registered mappings are always tried first, before any transitive mapping |
| * should be used. Also in all cases @Priority annotations are honored for ordering of the converters in place. |
| * Transitive conversion is supported for all directly implemented interfaces (including inherited ones) and |
| * the inheritance hierarchy (exception Object). Superinterfaces of implemented interfaces are ignored. |
| * |
| * @param targetType the target type, not {@code null}. |
| * @param <T> the type class |
| * @return the ordered createList of converters (may be empty for not convertible types). |
| * @see #createDefaultPropertyConverter(org.apache.tamaya.TypeLiteral) |
| */ |
| public <T> List<PropertyConverter<T>> getPropertyConverters(TypeLiteral<T> targetType) { |
| Lock readLock = lock.readLock(); |
| List<PropertyConverter<T>> converterList = new ArrayList<>(); |
| // direct mapped converters |
| try { |
| readLock.lock(); |
| addConvertersToList(List.class.cast(this.converters.get(targetType)), converterList); |
| addConvertersToList(List.class.cast(this.transitiveConverters.get(targetType)), converterList); |
| } finally { |
| readLock.unlock(); |
| } |
| // handling of java.lang wrapper classes |
| TypeLiteral<T> boxedType = mapBoxedType(targetType); |
| if (boxedType != null) { |
| try { |
| readLock.lock(); |
| addConvertersToList(List.class.cast(this.converters.get(boxedType)), converterList); |
| } finally { |
| readLock.unlock(); |
| } |
| } |
| if (converterList.isEmpty() && !TypeLiteral.of(String.class).equals(targetType)) { |
| // adding any converters created on the fly, e.g. for enum types. |
| PropertyConverter<T> defaultConverter = createDefaultPropertyConverter(targetType); |
| if (defaultConverter != null) { |
| register(targetType, defaultConverter); |
| try { |
| readLock.lock(); |
| addConvertersToList(List.class.cast(this.converters.get(targetType)), converterList); |
| } finally { |
| readLock.unlock(); |
| } |
| } |
| } |
| // check for parametrized types, ignoring param type |
| // direct mapped converters |
| if(targetType.getType()!=null) { |
| try { |
| readLock.lock(); |
| addConvertersToList(List.class.cast(this.converters.get( |
| TypeLiteral.of(targetType.getRawType()))), converterList); |
| } finally { |
| readLock.unlock(); |
| } |
| } |
| return converterList; |
| } |
| |
| private <T> void addConvertersToList(Collection<PropertyConverter<T>> converters, List<PropertyConverter<T>> converterList) { |
| if (converters != null) { |
| for(PropertyConverter<T> conv:converters) { |
| if(!converterList.contains(conv)) { |
| converterList.add(conv); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Maps native types to the corresponding boxed types. |
| * |
| * @param targetType the native type. |
| * @param <T> the type |
| * @return the boxed type, or null. |
| */ |
| @SuppressWarnings("unchecked") |
| private <T> TypeLiteral<T> mapBoxedType(TypeLiteral<T> targetType) { |
| Type parameterType = targetType.getType(); |
| if (parameterType == int.class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Integer.class)); |
| } |
| if (parameterType == short.class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Short.class)); |
| } |
| if (parameterType == byte.class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Byte.class)); |
| } |
| if (parameterType == long.class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Long.class)); |
| } |
| if (parameterType == boolean.class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Boolean.class)); |
| } |
| if (parameterType == char.class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Character.class)); |
| } |
| if (parameterType == float.class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Float.class)); |
| } |
| if (parameterType == double.class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Double.class)); |
| } |
| if (parameterType == int[].class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Integer[].class)); |
| } |
| if (parameterType == short[].class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Short[].class)); |
| } |
| if (parameterType == byte[].class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Byte[].class)); |
| } |
| if (parameterType == long[].class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Long[].class)); |
| } |
| if (parameterType == boolean[].class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Boolean[].class)); |
| } |
| if (parameterType == char[].class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Character[].class)); |
| } |
| if (parameterType == float[].class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Float[].class)); |
| } |
| if (parameterType == double[].class) { |
| return TypeLiteral.class.cast(TypeLiteral.of(Double[].class)); |
| } |
| return null; |
| } |
| |
| /** |
| * Creates a dynamic {@link PropertyConverter} for the given target type. |
| * |
| * @param targetType the target type |
| * @param <T> the type class |
| * @return a new converters, or null. |
| */ |
| protected <T> PropertyConverter<T> createDefaultPropertyConverter(final TypeLiteral<T> targetType) { |
| if (Enum.class.isAssignableFrom(targetType.getRawType())) { |
| return new EnumConverter<>(targetType.getRawType()); |
| } |
| PropertyConverter<T> converter = null; |
| final Method factoryMethod = getFactoryMethod(targetType.getRawType(), "of", "createValue", "instanceOf", "getInstance", "from", "fromString", "parse"); |
| if (factoryMethod != null) { |
| converter = new DefaultPropertyConverter<>(factoryMethod, targetType.getRawType()); |
| } |
| if (converter == null) { |
| final Constructor<T> constr; |
| try { |
| constr = targetType.getRawType().getDeclaredConstructor(String.class); |
| } catch (NoSuchMethodException e) { |
| LOG.log(Level.FINEST, "No matching constructor for " + targetType, e); |
| return null; |
| } |
| converter = new PropertyConverter<T>() { |
| @Override |
| public T convert(String value, ConversionContext context) { |
| AccessController.doPrivileged(new PrivilegedAction<Object>() { |
| @Override |
| public Object run() { |
| AccessController.doPrivileged(new PrivilegedAction<Object>() { |
| @Override |
| public Object run() { |
| constr.setAccessible(true); |
| return null; |
| } |
| }); |
| return null; |
| } |
| }); |
| try { |
| return constr.newInstance(value); |
| } catch (Exception e) { |
| LOG.log(Level.SEVERE, "Error creating new PropertyConverter instance " + targetType, e); |
| } |
| return null; |
| } |
| }; |
| } |
| return converter; |
| } |
| |
| /** |
| * Tries to evaluate a factory method that can be used to createObject an instance based on a String. |
| * |
| * @param type the target type |
| * @param methodNames the possible static method names |
| * @return the first method found, or null. |
| */ |
| private Method getFactoryMethod(Class<?> type, String... methodNames) { |
| Method m; |
| for (String name : methodNames) { |
| try { |
| m = type.getDeclaredMethod(name, String.class); |
| return m; |
| } catch (NoSuchMethodException | RuntimeException e) { |
| LOG.finest("No such factory method found on type: " + type.getName() + ", methodName: " + name); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public boolean equals(Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (!(o instanceof PropertyConverterManager)) { |
| return false; |
| } |
| PropertyConverterManager that = (PropertyConverterManager) o; |
| return converters.equals(that.converters); |
| |
| } |
| |
| @Override |
| public int hashCode() { |
| return converters.hashCode(); |
| } |
| |
| |
| |
| /** |
| * Default converter implementation performing several lookups for String conversion |
| * options. |
| * @param <T> |
| */ |
| private static class DefaultPropertyConverter<T> implements PropertyConverter<T> { |
| |
| private final Method factoryMethod; |
| private final Class<T> targetType; |
| |
| DefaultPropertyConverter(Method factoryMethod, Class<T> targetType){ |
| this.factoryMethod = Objects.requireNonNull(factoryMethod); |
| this.targetType = Objects.requireNonNull(targetType); |
| } |
| |
| @Override |
| public T convert(String value, ConversionContext context) { |
| context.addSupportedFormats(getClass(), "<String -> " + factoryMethod.toGenericString()); |
| if (!Modifier.isStatic(factoryMethod.getModifiers())) { |
| throw new ConfigException(factoryMethod.toGenericString() + |
| " is not a static method. Only static " + |
| "methods can be used as factory methods."); |
| } |
| try { |
| AccessController.doPrivileged(new PrivilegedAction<Object>() { |
| @Override |
| public Object run() { |
| factoryMethod.setAccessible(true); |
| return null; |
| } |
| }); |
| Object invoke = factoryMethod.invoke(null, value); |
| return targetType.cast(invoke); |
| } catch (Exception e) { |
| throw new ConfigException("Failed to decode '" + value + "'", e); |
| } |
| } |
| } |
| |
| } |