001 /******************************************************************************* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 *******************************************************************************/ 019 package org.apache.commons.convert; 020 021 import java.lang.reflect.Modifier; 022 import java.util.Collections; 023 import java.util.HashSet; 024 import java.util.Iterator; 025 import java.util.Set; 026 import java.util.concurrent.ConcurrentHashMap; 027 028 import javax.imageio.spi.ServiceRegistry; 029 030 /** A <code>Converter</code> factory and utility class. 031 * <p>The factory loads {@link org.apache.commons.convert.Converter} instances 032 * via the Java service provider registry. Applications can extend the 033 * converter framework by:<br /> 034 * <ul> 035 * <li>Creating classes that implement the 036 * {@link org.apache.commons.convert.Converter} class</li> 037 * <li>Create a {@link org.apache.commons.convert.ConverterLoader} class that 038 * registers those classes</li> 039 * <li>Create a <code>META-INF/services/org.apache.commons.convert.ConverterLoader</code> 040 * file that contains the {@link org.apache.commons.convert.ConverterLoader} class name(s)</li> 041 * </ul></p> 042 * <p>To convert an object, call the static {@link #getConverter(Class, Class)} method to 043 * get a {@link org.apache.commons.convert.Converter} instance, then call the converter's 044 * {@link org.apache.commons.convert.Converter#convert(Object)} method. 045 * </p> 046 */ 047 public class Converters { 048 protected static final String DELIMITER = "->"; 049 protected static final ConcurrentHashMap<String, Converter<?, ?>> converterMap = new ConcurrentHashMap<String, Converter<?, ?>>(); 050 protected static final Set<ConverterCreator> creators = Collections.synchronizedSet(new HashSet<ConverterCreator>()); 051 protected static final Set<String> noConversions = Collections.synchronizedSet(new HashSet<String>()); 052 053 static { 054 registerCreator(new PassThruConverterCreator()); 055 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 056 Iterator<ConverterLoader> converterLoaders = ServiceRegistry.lookupProviders(ConverterLoader.class, loader); 057 while (converterLoaders.hasNext()) { 058 try { 059 ConverterLoader converterLoader = converterLoaders.next(); 060 converterLoader.loadConverters(); 061 } catch (Exception e) { 062 e.printStackTrace(); 063 } 064 } 065 } 066 067 private Converters() {} 068 069 070 /** Returns an appropriate <code>Converter</code> instance for 071 * <code>sourceClass</code> and <code>targetClass</code>. If no matching 072 * <code>Converter</code> is found, the method throws 073 * <code>ClassNotFoundException</code>. 074 * 075 * <p>This method is intended to be used when the source or 076 * target <code>Object</code> types are unknown at compile time. 077 * If the source and target <code>Object</code> types are known 078 * at compile time, then the appropriate converter instance should be used.</p> 079 * 080 * @param sourceClass The object class to convert from 081 * @param targetClass The object class to convert to 082 * @return A matching <code>Converter</code> instance 083 * @throws ClassNotFoundException 084 */ 085 public static <S, T> Converter<S, T> getConverter(Class<S> sourceClass, Class<T> targetClass) throws ClassNotFoundException { 086 String key = sourceClass.getName().concat(DELIMITER).concat(targetClass.getName()); 087 OUTER: 088 do { 089 Converter<?, ?> result = converterMap.get(key); 090 if (result != null) { 091 return Util.cast(result); 092 } 093 if (noConversions.contains(key)) { 094 throw new ClassNotFoundException("No converter found for " + key); 095 } 096 Class<?> foundSourceClass = null; 097 Converter<?, ?> foundConverter = null; 098 for (Converter<?, ?> value : converterMap.values()) { 099 if (value.canConvert(sourceClass, targetClass)) { 100 // this converter can deal with the source/target pair 101 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 foundSourceClass = value.getSourceClass(); 106 foundConverter = value; 107 } 108 } 109 } 110 if (foundConverter != null) { 111 converterMap.putIfAbsent(key, foundConverter); 112 continue OUTER; 113 } 114 for (ConverterCreator value : creators) { 115 result = createConverter(value, sourceClass, targetClass); 116 if (result != null) { 117 converterMap.putIfAbsent(key, result); 118 continue OUTER; 119 } 120 } 121 noConversions.add(key); 122 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 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 for (Class<?> clz: containerClass.getClasses()) { 139 try { 140 // non-abstract, which means no interfaces or abstract classes 141 if ((clz.getModifiers() & Modifier.ABSTRACT) == 0) { 142 Object value; 143 try { 144 value = clz.getConstructor().newInstance(); 145 } catch (NoSuchMethodException e) { 146 // ignore this, as this class might be some other helper class, 147 // with a non-pubilc constructor 148 continue; 149 } 150 if (value instanceof ConverterLoader) { 151 ConverterLoader loader = (ConverterLoader) value; 152 loader.loadConverters(); 153 } 154 } 155 } catch (Exception e) { 156 e.printStackTrace(); 157 } 158 } 159 } 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 creators.add(creator); 171 } 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 registerConverter(converter, converter.getSourceClass(), converter.getTargetClass()); 183 } 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 StringBuilder sb = new StringBuilder(); 197 if (sourceClass != null) { 198 sb.append(sourceClass.getName()); 199 } else { 200 sb.append("<null>"); 201 } 202 sb.append(DELIMITER); 203 if (targetClass != null) { 204 sb.append(targetClass.getName()); 205 } else { 206 sb.append("<null>"); 207 } 208 String key = sb.toString(); 209 converterMap.putIfAbsent(key, converter); 210 } 211 212 protected static class PassThruConverterCreator implements ConverterCreator{ 213 protected PassThruConverterCreator() { 214 } 215 216 public <S, T> Converter<S, T> createConverter(Class<S> sourceClass, Class<T> targetClass) { 217 if (sourceClass == targetClass || targetClass == Object.class || Util.instanceOf(sourceClass, targetClass)) { 218 return new PassThruConverter<S, T>(sourceClass, targetClass); 219 } else { 220 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 public PassThruConverter(Class<S> sourceClass, Class<T> targetClass) { 235 this.sourceClass = sourceClass; 236 this.targetClass = targetClass; 237 } 238 239 public boolean canConvert(Class<?> sourceClass, Class<?> targetClass) { 240 return this.sourceClass == sourceClass && this.targetClass == targetClass; 241 } 242 243 @SuppressWarnings("unchecked") 244 public T convert(S obj) throws ConversionException { 245 return (T) obj; 246 } 247 248 public Class<?> getSourceClass() { 249 return sourceClass; 250 } 251 252 public Class<?> getTargetClass() { 253 return targetClass; 254 } 255 } 256 }