View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements. See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache license, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License. You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the license for the specific language governing permissions and
15   * limitations under the license.
16   */
17  package org.apache.logging.log4j.core.config.plugins.convert;
18  
19  import java.lang.reflect.ParameterizedType;
20  import java.lang.reflect.Type;
21  import java.util.Collection;
22  import java.util.Map;
23  import java.util.UnknownFormatConversionException;
24  import java.util.concurrent.ConcurrentHashMap;
25  import java.util.concurrent.ConcurrentMap;
26  
27  import org.apache.logging.log4j.Logger;
28  import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
29  import org.apache.logging.log4j.core.config.plugins.util.PluginType;
30  import org.apache.logging.log4j.core.util.Assert;
31  import org.apache.logging.log4j.core.util.ReflectionUtil;
32  import org.apache.logging.log4j.core.util.TypeUtil;
33  import org.apache.logging.log4j.status.StatusLogger;
34  
35  /**
36   * Registry for {@link TypeConverter} plugins.
37   *
38   * @since 2.1
39   */
40  public class TypeConverterRegistry {
41  
42      private static final Logger LOGGER = StatusLogger.getLogger();
43      private static volatile TypeConverterRegistry INSTANCE;
44      private static final Object INSTANCE_LOCK = new Object();
45  
46      private final ConcurrentMap<Type, TypeConverter<?>> registry = new ConcurrentHashMap<Type, TypeConverter<?>>();
47  
48      /**
49       * Gets the singleton instance of the TypeConverterRegistry.
50       *
51       * @return the singleton instance.
52       */
53      public static TypeConverterRegistry getInstance() {
54          TypeConverterRegistry result = INSTANCE;
55          if (result == null) {
56              synchronized (INSTANCE_LOCK) {
57                  result = INSTANCE;
58                  if (result == null) {
59                      INSTANCE = result = new TypeConverterRegistry();
60                  }
61              }
62          }
63          return result;
64      }
65  
66      /**
67       * Finds a {@link TypeConverter} for the given {@link Type}, falling back to an assignment-compatible TypeConverter
68       * if none exist for the given type. That is, if the given Type does not have a TypeConverter, but another Type
69       * which can be assigned to the given Type <em>does</em> have a TypeConverter, then that TypeConverter will be
70       * used and registered.
71       *
72       * @param type the Type to find a TypeConverter for (must not be {@code null}).
73       * @return a TypeConverter for the given Type.
74       * @throws UnknownFormatConversionException if no TypeConverter can be found for the given type.
75       */
76      public TypeConverter<?> findCompatibleConverter(final Type type) {
77          Assert.requireNonNull(type, "No type was provided");
78          final TypeConverter<?> primary = registry.get(type);
79          // cached type converters
80          if (primary != null) {
81              return primary;
82          }
83          // dynamic enum support
84          if (type instanceof Class<?>) {
85              final Class<?> clazz = (Class<?>) type;
86              if (clazz.isEnum()) {
87                  @SuppressWarnings({"unchecked","rawtypes"})
88                  final EnumConverter<? extends Enum> converter = new EnumConverter(clazz.asSubclass(Enum.class));
89                  registry.putIfAbsent(type, converter);
90                  return converter;
91              }
92          }
93          // look for compatible converters
94          for (final Map.Entry<Type, TypeConverter<?>> entry : registry.entrySet()) {
95              final Type key = entry.getKey();
96              if (TypeUtil.isAssignable(type, key)) {
97                  LOGGER.debug("Found compatible TypeConverter<{}> for type [{}].", key, type);
98                  final TypeConverter<?> value = entry.getValue();
99                  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 }