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}