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 */
017package org.apache.logging.log4j.core.config.plugins.convert;
018
019import java.lang.reflect.ParameterizedType;
020import java.lang.reflect.Type;
021import java.util.Collection;
022import java.util.Map;
023import java.util.Objects;
024import java.util.UnknownFormatConversionException;
025import java.util.concurrent.ConcurrentHashMap;
026import java.util.concurrent.ConcurrentMap;
027
028import org.apache.logging.log4j.Logger;
029import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
030import org.apache.logging.log4j.core.config.plugins.util.PluginType;
031import org.apache.logging.log4j.core.util.ReflectionUtil;
032import org.apache.logging.log4j.core.util.TypeUtil;
033import org.apache.logging.log4j.status.StatusLogger;
034
035/**
036 * Registry for {@link TypeConverter} plugins.
037 *
038 * @since 2.1
039 */
040public 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<>();
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        Objects.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                synchronized (INSTANCE_LOCK) {
090                    return registerConverter(type, converter);
091                }
092            }
093        }
094        // look for compatible converters
095        for (final Map.Entry<Type, TypeConverter<?>> entry : registry.entrySet()) {
096            final Type key = entry.getKey();
097            if (TypeUtil.isAssignable(type, key)) {
098                LOGGER.debug("Found compatible TypeConverter<{}> for type [{}].", key, type);
099                final TypeConverter<?> value = entry.getValue();
100                synchronized (INSTANCE_LOCK) {
101                    return registerConverter(type, value);
102                }
103            }
104        }
105        throw new UnknownFormatConversionException(type.toString());
106    }
107
108    private TypeConverterRegistry() {
109        LOGGER.trace("TypeConverterRegistry initializing.");
110        final PluginManager manager = new PluginManager(TypeConverters.CATEGORY);
111        manager.collectPlugins();
112        loadKnownTypeConverters(manager.getPlugins().values());
113        registerPrimitiveTypes();
114    }
115
116    private void loadKnownTypeConverters(final Collection<PluginType<?>> knownTypes) {
117        for (final PluginType<?> knownType : knownTypes) {
118            final Class<?> clazz = knownType.getPluginClass();
119            if (TypeConverter.class.isAssignableFrom(clazz)) {
120                @SuppressWarnings("rawtypes")
121                final Class<? extends TypeConverter> pluginClass =  clazz.asSubclass(TypeConverter.class);
122                final Type conversionType = getTypeConverterSupportedType(pluginClass);
123                final TypeConverter<?> converter = ReflectionUtil.instantiate(pluginClass);
124                registerConverter(conversionType, converter);
125            }
126        }
127    }
128
129    /**
130     * Attempts to register the given converter and returns the effective
131     * converter associated with the given type.
132     * <p>
133     * Registration will fail if there already exists a converter for the given
134     * type and neither the existing, nor the provided converter extends from {@link Comparable}.
135     */
136    private TypeConverter<?> registerConverter(
137            final Type conversionType,
138            final TypeConverter<?> converter) {
139        final TypeConverter<?> conflictingConverter = registry.get(conversionType);
140        if (conflictingConverter != null) {
141            final boolean overridable;
142            if (converter instanceof Comparable) {
143                @SuppressWarnings("unchecked")
144                final Comparable<TypeConverter<?>> comparableConverter =
145                        (Comparable<TypeConverter<?>>) converter;
146                overridable = comparableConverter.compareTo(conflictingConverter) < 0;
147            } else if (conflictingConverter instanceof Comparable) {
148                @SuppressWarnings("unchecked")
149                final Comparable<TypeConverter<?>> comparableConflictingConverter =
150                        (Comparable<TypeConverter<?>>) conflictingConverter;
151                overridable = comparableConflictingConverter.compareTo(converter) > 0;
152            } else {
153                overridable = false;
154            }
155            if (overridable) {
156                LOGGER.debug(
157                        "Replacing TypeConverter [{}] for type [{}] with [{}] after comparison.",
158                        conflictingConverter, conversionType, converter);
159                registry.put(conversionType, converter);
160                return converter;
161            } else {
162                LOGGER.warn(
163                        "Ignoring TypeConverter [{}] for type [{}] that conflicts with [{}], since they are not comparable.",
164                        converter, conversionType, conflictingConverter);
165                return conflictingConverter;
166            }
167        } else {
168            registry.put(conversionType, converter);
169            return converter;
170        }
171    }
172
173    private static Type getTypeConverterSupportedType(@SuppressWarnings("rawtypes") final Class<? extends TypeConverter> typeConverterClass) {
174        for (final Type type : typeConverterClass.getGenericInterfaces()) {
175            if (type instanceof ParameterizedType) {
176                final ParameterizedType pType = (ParameterizedType) type;
177                if (TypeConverter.class.equals(pType.getRawType())) {
178                    // TypeConverter<T> has only one type argument (T), so return that
179                    return pType.getActualTypeArguments()[0];
180                }
181            }
182        }
183        return Void.TYPE;
184    }
185
186    private void registerPrimitiveTypes() {
187        registerTypeAlias(Boolean.class, Boolean.TYPE);
188        registerTypeAlias(Byte.class, Byte.TYPE);
189        registerTypeAlias(Character.class, Character.TYPE);
190        registerTypeAlias(Double.class, Double.TYPE);
191        registerTypeAlias(Float.class, Float.TYPE);
192        registerTypeAlias(Integer.class, Integer.TYPE);
193        registerTypeAlias(Long.class, Long.TYPE);
194        registerTypeAlias(Short.class, Short.TYPE);
195    }
196
197    private void registerTypeAlias(final Type knownType, final Type aliasType) {
198        registry.putIfAbsent(aliasType, registry.get(knownType));
199    }
200
201}