    001    /*
    002     * Licensed to the Apache Software Foundation (ASF) under one or more
    003     * contributor license agreements. See the NOTICE file distributed with
    004     * this work for additional information regarding copyright ownership.
    005     * The ASF licenses this file to You under the Apache license, Version 2.0
    006     * (the "License"); you may not use this file except in compliance with
    007     * the License. You may obtain a copy of the License at
    008     *
    009     *      http://www.apache.org/licenses/LICENSE-2.0
    010     *
    011     * Unless required by applicable law or agreed to in writing, software
    012     * distributed under the License is distributed on an "AS IS" BASIS,
    013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014     * See the license for the specific language governing permissions and
    015     * limitations under the license.
    016     */
    017    package org.apache.logging.log4j.core.config.plugins.convert;
    018
    019    import java.lang.reflect.ParameterizedType;
    020    import java.lang.reflect.Type;
    021    import java.util.Collection;
    022    import java.util.Map;
    023    import java.util.UnknownFormatConversionException;
    024    import java.util.concurrent.ConcurrentHashMap;
    025    import java.util.concurrent.ConcurrentMap;
    026
    027    import org.apache.logging.log4j.Logger;
    028    import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
    029    import org.apache.logging.log4j.core.config.plugins.util.PluginType;
    030    import org.apache.logging.log4j.core.util.Assert;
    031    import org.apache.logging.log4j.core.util.ReflectionUtil;
    032    import org.apache.logging.log4j.core.util.TypeUtil;
    033    import org.apache.logging.log4j.status.StatusLogger;
    034
    035    /**
    036     * Registry for {@link TypeConverter} plugins.
    037     *
    038     * @since 2.1
    039     */
    040    public class TypeConverterRegistry {
    041
    042        private static final Logger LOGGER = StatusLogger.getLogger();
    043        private static volatile TypeConverterRegistry INSTANCE;
    044        private static final Object INSTANCE_LOCK = new Object();
    045
    046        private final ConcurrentMap<Type, TypeConverter<?>> registry = new ConcurrentHashMap<Type, TypeConverter<?>>();
    047
    048        /**
    049         * Gets the singleton instance of the TypeConverterRegistry.
    050         *
    051         * @return the singleton instance.
    052         */
    053        public static TypeConverterRegistry getInstance() {
    054            TypeConverterRegistry result = INSTANCE;
    055            if (result == null) {
    056                synchronized (INSTANCE_LOCK) {
    057                    result = INSTANCE;
    058                    if (result == null) {
    059                        INSTANCE = result = new TypeConverterRegistry();
    060                    }
    061                }
    062            }
    063            return result;
    064        }
    065
    066        /**
    067         * Finds a {@link TypeConverter} for the given {@link Type}, falling back to an assignment-compatible TypeConverter
    068         * if none exist for the given type. That is, if the given Type does not have a TypeConverter, but another Type
    069         * which can be assigned to the given Type <em>does</em> have a TypeConverter, then that TypeConverter will be
    070         * used and registered.
    071         *
    072         * @param type the Type to find a TypeConverter for (must not be {@code null}).
    073         * @return a TypeConverter for the given Type.
    074         * @throws UnknownFormatConversionException if no TypeConverter can be found for the given type.
    075         */
    076        public TypeConverter<?> findCompatibleConverter(final Type type) {
    077            Assert.requireNonNull(type, "No type was provided");
    078            final TypeConverter<?> primary = registry.get(type);
    079            // cached type converters
    080            if (primary != null) {
    081                return primary;
    082            }
    083            // dynamic enum support
    084            if (type instanceof Class<?>) {
    085                final Class<?> clazz = (Class<?>) type;
    086                if (clazz.isEnum()) {
    087                    @SuppressWarnings({"unchecked","rawtypes"})
    088                    final EnumConverter<? extends Enum> converter = new EnumConverter(clazz.asSubclass(Enum.class));
    089                    registry.putIfAbsent(type, converter);
    090                    return converter;
    091                }
    092            }
    093            // look for compatible converters
    094            for (final Map.Entry<Type, TypeConverter<?>> entry : registry.entrySet()) {
    095                final Type key = entry.getKey();
    096                if (TypeUtil.isAssignable(type, key)) {
    097                    LOGGER.debug("Found compatible TypeConverter<{}> for type [{}].", key, type);
    098                    final TypeConverter<?> value = entry.getValue();
    099                    registry.putIfAbsent(type, value);
    100                    return value;
    101                }
    102            }
    103            throw new UnknownFormatConversionException(type.toString());
    104        }
    105
    106        private TypeConverterRegistry() {
    107            LOGGER.debug("TypeConverterRegistry initializing.");
    108            final PluginManager manager = new PluginManager(TypeConverters.CATEGORY);
    109            manager.collectPlugins();
    110            loadKnownTypeConverters(manager.getPlugins().values());
    111            registerPrimitiveTypes();
    112        }
    113
    114        private void loadKnownTypeConverters(final Collection<PluginType<?>> knownTypes) {
    115            for (final PluginType<?> knownType : knownTypes) {
    116                final Class<?> clazz = knownType.getPluginClass();
    117                if (TypeConverter.class.isAssignableFrom(clazz)) {
    118                    @SuppressWarnings("rawtypes")
    119                    final Class<? extends TypeConverter> pluginClass =  clazz.asSubclass(TypeConverter.class);
    120                    final Type conversionType = getTypeConverterSupportedType(pluginClass);
    121                    final TypeConverter<?> converter = ReflectionUtil.instantiate(pluginClass);
    122                    if (registry.putIfAbsent(conversionType, converter) != null) {
    123                        LOGGER.warn("Found a TypeConverter [{}] for type [{}] that already exists.", converter,
    124                            conversionType);
    125                    }
    126                }
    127            }
    128        }
    129
    130        private static Type getTypeConverterSupportedType(@SuppressWarnings("rawtypes") final Class<? extends TypeConverter> typeConverterClass) {
    131            for (final Type type : typeConverterClass.getGenericInterfaces()) {
    132                if (type instanceof ParameterizedType) {
    133                    final ParameterizedType pType = (ParameterizedType) type;
    134                    if (TypeConverter.class.equals(pType.getRawType())) {
    135                        // TypeConverter<T> has only one type argument (T), so return that
    136                        return pType.getActualTypeArguments()[0];
    137                    }
    138                }
    139            }
    140            return Void.TYPE;
    141        }
    142
    143        private void registerPrimitiveTypes() {
    144            registerTypeAlias(Boolean.class, Boolean.TYPE);
    145            registerTypeAlias(Byte.class, Byte.TYPE);
    146            registerTypeAlias(Character.class, Character.TYPE);
    147            registerTypeAlias(Double.class, Double.TYPE);
    148            registerTypeAlias(Float.class, Float.TYPE);
    149            registerTypeAlias(Integer.class, Integer.TYPE);
    150            registerTypeAlias(Long.class, Long.TYPE);
    151            registerTypeAlias(Short.class, Short.TYPE);
    152        }
    153
    154        private void registerTypeAlias(final Type knownType, final Type aliasType) {
    155            registry.putIfAbsent(aliasType, registry.get(knownType));
    156        }
    157
    158    }