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    }