Coverage Report - org.apache.commons.convert.Converters
 
Classes in this File Line Coverage Branch Coverage Complexity
Converters
95%
63/66
89%
25/28
2.786
Converters$PassThruConverter
100%
8/8
100%
4/4
2.786
Converters$PassThruConverterCreator
100%
5/5
83%
5/6
2.786
 
 1  
 /*******************************************************************************
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements.  See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership.  The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License.  You may obtain a copy of the License at
 9  
  *
 10  
  * http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied.  See the License for the
 16  
  * specific language governing permissions and limitations
 17  
  * under the License.
 18  
  *******************************************************************************/
 19  
 package org.apache.commons.convert;
 20  
 
 21  
 import java.lang.reflect.Modifier;
 22  
 import java.util.Collections;
 23  
 import java.util.HashSet;
 24  
 import java.util.Iterator;
 25  
 import java.util.Set;
 26  
 import java.util.concurrent.ConcurrentHashMap;
 27  
 
 28  
 import javax.imageio.spi.ServiceRegistry;
 29  
 
 30  
 /** A <code>Converter</code> factory and utility class.
 31  
  * <p>The factory loads {@link org.apache.commons.convert.Converter} instances
 32  
  * via the Java service provider registry. Applications can extend the
 33  
  * converter framework by:<br />
 34  
  * <ul>
 35  
  * <li>Creating classes that implement the
 36  
  * {@link org.apache.commons.convert.Converter} class</li>
 37  
  * <li>Create a {@link org.apache.commons.convert.ConverterLoader} class that
 38  
  * registers those classes</li>
 39  
  * <li>Create a <code>META-INF/services/org.apache.commons.convert.ConverterLoader</code>
 40  
  * file that contains the {@link org.apache.commons.convert.ConverterLoader} class name(s)</li>
 41  
  * </ul></p>
 42  
  * <p>To convert an object, call the static {@link #getConverter(Class, Class)} method to
 43  
  * get a {@link org.apache.commons.convert.Converter} instance, then call the converter's
 44  
  * {@link org.apache.commons.convert.Converter#convert(Object)} method.
 45  
  * </p>
 46  
  */
 47  
 public class Converters {
 48  
     protected static final String DELIMITER = "->";
 49  1
     protected static final ConcurrentHashMap<String, Converter<?, ?>> converterMap = new ConcurrentHashMap<String, Converter<?, ?>>();
 50  1
     protected static final Set<ConverterCreator> creators = Collections.synchronizedSet(new HashSet<ConverterCreator>());
 51  1
     protected static final Set<String> noConversions = Collections.synchronizedSet(new HashSet<String>());
 52  
 
 53  
     static {
 54  1
         registerCreator(new PassThruConverterCreator());
 55  1
         ClassLoader loader = Thread.currentThread().getContextClassLoader();
 56  1
         Iterator<ConverterLoader> converterLoaders = ServiceRegistry.lookupProviders(ConverterLoader.class, loader);
 57  7
         while (converterLoaders.hasNext()) {
 58  
             try {
 59  6
                 ConverterLoader converterLoader = converterLoaders.next();
 60  6
                 converterLoader.loadConverters();
 61  0
             } catch (Exception e) {
 62  0
                 e.printStackTrace();
 63  6
             }
 64  
         }
 65  1
     }
 66  
 
 67  1
     private Converters() {}
 68  
 
 69  
 
 70  
     /** Returns an appropriate <code>Converter</code> instance for
 71  
      * <code>sourceClass</code> and <code>targetClass</code>. If no matching
 72  
      * <code>Converter</code> is found, the method throws
 73  
      * <code>ClassNotFoundException</code>.
 74  
      *
 75  
      * <p>This method is intended to be used when the source or
 76  
      * target <code>Object</code> types are unknown at compile time.
 77  
      * If the source and target <code>Object</code> types are known
 78  
      * at compile time, then the appropriate converter instance should be used.</p>
 79  
      *
 80  
      * @param sourceClass The object class to convert from
 81  
      * @param targetClass The object class to convert to
 82  
      * @return A matching <code>Converter</code> instance
 83  
      * @throws ClassNotFoundException
 84  
      */
 85  
     public static <S, T> Converter<S, T> getConverter(Class<S> sourceClass, Class<T> targetClass) throws ClassNotFoundException {
 86  332
         String key = sourceClass.getName().concat(DELIMITER).concat(targetClass.getName());
 87  
         OUTER:
 88  
             do {
 89  390
                 Converter<?, ?> result = converterMap.get(key);
 90  390
                 if (result != null) {
 91  329
                     return Util.cast(result);
 92  
                 }
 93  61
                 if (noConversions.contains(key)) {
 94  1
                     throw new ClassNotFoundException("No converter found for " + key);
 95  
                 }
 96  60
                 Class<?> foundSourceClass = null;
 97  60
                 Converter<?, ?> foundConverter = null;
 98  60
                 for (Converter<?, ?> value : converterMap.values()) {
 99  12169
                     if (value.canConvert(sourceClass, targetClass)) {
 100  
                         // this converter can deal with the source/target pair
 101  21
                         if (foundSourceClass == null || foundSourceClass.isAssignableFrom(value.getSourceClass())) {
 102  
                             // remember the current target source class; if we find another converter, check
 103  
                             // to see if it's source class is assignable to this one, and if so, it means it's
 104  
                             // a child class, so we'll then take that converter.
 105  21
                             foundSourceClass = value.getSourceClass();
 106  21
                             foundConverter = value;
 107  
                         }
 108  
                     }
 109  
                 }
 110  60
                 if (foundConverter != null) {
 111  20
                     converterMap.putIfAbsent(key, foundConverter);
 112  20
                     continue OUTER;
 113  
                 }
 114  40
                 for (ConverterCreator value : creators) {
 115  173
                     result = createConverter(value, sourceClass, targetClass);
 116  173
                     if (result != null) {
 117  38
                         converterMap.putIfAbsent(key, result);
 118  38
                         continue OUTER;
 119  
                     }
 120  
                 }
 121  2
                 noConversions.add(key);
 122  2
                 throw new ClassNotFoundException("No converter found for " + key);
 123  
             } while (true);
 124  
     }
 125  
 
 126  
     private static <S, SS extends S, T, TT extends T> Converter<SS, TT> createConverter(ConverterCreator creater, Class<SS> sourceClass, Class<TT> targetClass) {
 127  173
         return creater.createConverter(sourceClass, targetClass);
 128  
     }
 129  
 
 130  
     /** Load all classes that implement {@link org.apache.commons.convert.Converter} and are
 131  
      * contained in <code>containerClass</code>.
 132  
      *
 133  
      * @param containerClass A class that contains {@link org.apache.commons.convert.Converter}
 134  
      * implementations
 135  
      */
 136  
     public static void loadContainedConverters(Class<?> containerClass) {
 137  
         // This only returns public classes and interfaces
 138  171
         for (Class<?> clz: containerClass.getClasses()) {
 139  
             try {
 140  
                 // non-abstract, which means no interfaces or abstract classes
 141  159
                 if ((clz.getModifiers() & Modifier.ABSTRACT) == 0) {
 142  
                     Object value;
 143  
                     try {
 144  153
                         value = clz.getConstructor().newInstance();
 145  19
                     } catch (NoSuchMethodException e) {
 146  
                         // ignore this, as this class might be some other helper class,
 147  
                         // with a non-pubilc constructor
 148  19
                         continue;
 149  134
                     }
 150  134
                     if (value instanceof ConverterLoader) {
 151  134
                         ConverterLoader loader = (ConverterLoader) value;
 152  134
                         loader.loadConverters();
 153  
                     }
 154  
                 }
 155  1
             } catch (Exception e) {
 156  1
                 e.printStackTrace();
 157  139
             }
 158  
         }
 159  12
     }
 160  
 
 161  
     /** Registers a <code>ConverterCreator</code> instance to be used by the
 162  
      * {@link org.apache.commons.convert.Converters#getConverter(Class, Class)}
 163  
      * method, when a converter can't be found.
 164  
      *
 165  
      * @param <S> The source object type
 166  
      * @param <T> The target object type
 167  
      * @param creator The <code>ConverterCreator</code> instance to register
 168  
      */
 169  
     public static <S, T> void registerCreator(ConverterCreator creator) {
 170  8
         creators.add(creator);
 171  8
     }
 172  
 
 173  
     /** Registers a <code>Converter</code> instance to be used by the
 174  
      * {@link org.apache.commons.convert.Converters#getConverter(Class, Class)}
 175  
      * method.
 176  
      *
 177  
      * @param <S> The source object type
 178  
      * @param <T> The target object type
 179  
      * @param converter The <code>Converter</code> instance to register
 180  
      */
 181  
     public static <S, T> void registerConverter(Converter<S, T> converter) {
 182  333
         registerConverter(converter, converter.getSourceClass(), converter.getTargetClass());
 183  333
     }
 184  
 
 185  
     /** Registers a <code>Converter</code> instance to be used by the
 186  
      * {@link org.apache.commons.convert.Converters#getConverter(Class, Class)}
 187  
      * method.
 188  
      * 
 189  
      * @param <S> The source object type
 190  
      * @param <T> The target object type
 191  
      * @param converter
 192  
      * @param sourceClass
 193  
      * @param targetClass
 194  
      */
 195  
     public static <S, T> void registerConverter(Converter<S, T> converter, Class<?> sourceClass, Class<?> targetClass) {
 196  333
         StringBuilder sb = new StringBuilder();
 197  333
         if (sourceClass != null) {
 198  332
             sb.append(sourceClass.getName());
 199  
         } else {
 200  1
             sb.append("<null>");
 201  
         }
 202  333
         sb.append(DELIMITER);
 203  333
         if (targetClass != null) {
 204  333
             sb.append(targetClass.getName());
 205  
         } else {
 206  0
             sb.append("<null>");
 207  
         }
 208  333
         String key = sb.toString();
 209  333
         converterMap.putIfAbsent(key, converter);
 210  333
     }
 211  
 
 212  
     protected static class PassThruConverterCreator implements ConverterCreator{
 213  1
         protected PassThruConverterCreator() {
 214  1
         }
 215  
 
 216  
         public <S, T> Converter<S, T> createConverter(Class<S> sourceClass, Class<T> targetClass) {
 217  29
             if (sourceClass == targetClass || targetClass == Object.class || Util.instanceOf(sourceClass, targetClass)) {
 218  15
                 return new PassThruConverter<S, T>(sourceClass, targetClass);
 219  
             } else {
 220  14
                 return null;
 221  
             }
 222  
         }
 223  
     }
 224  
 
 225  
     /** Pass-through converter used when the source and target java object
 226  
      * types are the same. The <code>convert</code> method returns the
 227  
      * source object.
 228  
      *
 229  
      */
 230  
     protected static class PassThruConverter<S, T> implements Converter<S, T> {
 231  
         private final Class<S> sourceClass;
 232  
         private final Class<T> targetClass;
 233  
 
 234  15
         public PassThruConverter(Class<S> sourceClass, Class<T> targetClass) {
 235  15
             this.sourceClass = sourceClass;
 236  15
             this.targetClass = targetClass;
 237  15
         }
 238  
 
 239  
         public boolean canConvert(Class<?> sourceClass, Class<?> targetClass) {
 240  166
             return this.sourceClass == sourceClass && this.targetClass == targetClass;
 241  
         }
 242  
 
 243  
         @SuppressWarnings("unchecked")
 244  
         public T convert(S obj) throws ConversionException {
 245  16
             return (T) obj;
 246  
         }
 247  
 
 248  
         public Class<?> getSourceClass() {
 249  7
             return sourceClass;
 250  
         }
 251  
 
 252  
         public Class<?> getTargetClass() {
 253  7
             return targetClass;
 254  
         }
 255  
     }
 256  
 }