blob: 824da874fe4d3899776cd5f83a8fd789e264dcdf [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*******************************************************************************/
package org.ofbiz.base.conversion;
import java.lang.reflect.Modifier;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.ofbiz.base.lang.SourceMonitored;
import org.ofbiz.base.util.Debug;
import org.ofbiz.base.util.ObjectType;
import org.ofbiz.base.util.UtilGenerics;
/** A <code>Converter</code> factory and repository. */
@SourceMonitored
public class Converters {
protected static final String module = Converters.class.getName();
protected static final String DELIMITER = "->";
protected static final ConcurrentHashMap<String, Converter<?, ?>> converterMap = new ConcurrentHashMap<String, Converter<?, ?>>();
protected static final Set<ConverterCreator> creators = new HashSet<ConverterCreator>();
protected static final Set<String> noConversions = new HashSet<String>();
static {
registerCreator(new PassThruConverterCreator());
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Iterator<ConverterLoader> converterLoaders = ServiceLoader.load(ConverterLoader.class, loader).iterator();
while (converterLoaders.hasNext()) {
try {
ConverterLoader converterLoader = converterLoaders.next();
converterLoader.loadConverters();
} catch (Exception e) {
Debug.logError(e, module);
}
}
}
private Converters() {}
/** Returns an appropriate <code>Converter</code> instance for
* <code>sourceClass</code> and <code>targetClass</code>. If no matching
* <code>Converter</code> is found, the method throws
* <code>ClassNotFoundException</code>.
*
* <p>This method is intended to be used when the source or
* target <code>Object</code> types are unknown at compile time.
* If the source and target <code>Object</code> types are known
* at compile time, then one of the "ready made" converters should be used.</p>
*
* @param sourceClass The object class to convert from
* @param targetClass The object class to convert to
* @return A matching <code>Converter</code> instance
* @throws ClassNotFoundException
*/
public static <S, T> Converter<S, T> getConverter(Class<S> sourceClass, Class<T> targetClass) throws ClassNotFoundException {
String key = sourceClass.getName().concat(DELIMITER).concat(targetClass.getName());
if (Debug.verboseOn()) {
Debug.logVerbose("Getting converter: " + key, module);
}
OUTER:
do {
Converter<?, ?> result = converterMap.get(key);
if (result != null) {
return UtilGenerics.cast(result);
}
if (noConversions.contains(key)) {
throw new ClassNotFoundException("No converter found for " + key);
}
Class<?> foundSourceClass = null;
Converter<?, ?> foundConverter = null;
for (Converter<?, ?> value : converterMap.values()) {
if (value.canConvert(sourceClass, targetClass)) {
// this converter can deal with the source/target pair
if (foundSourceClass == null || foundSourceClass.isAssignableFrom(value.getSourceClass())) {
// remember the current target source class; if we find another converter, check
// to see if it's source class is assignable to this one, and if so, it means it's
// a child class, so we'll then take that converter.
foundSourceClass = value.getSourceClass();
foundConverter = value;
}
}
}
if (foundConverter != null) {
converterMap.putIfAbsent(key, foundConverter);
continue OUTER;
}
for (ConverterCreator value : creators) {
result = createConverter(value, sourceClass, targetClass);
if (result != null) {
converterMap.putIfAbsent(key, result);
continue OUTER;
}
}
boolean addedToSet = false;
synchronized (noConversions) {
addedToSet = noConversions.add(key);
}
if (addedToSet) {
Debug.logWarning("*** No converter found, converting from " +
sourceClass.getName() + " to " + targetClass.getName() +
". Please report this message to the developer community so " +
"a suitable converter can be created. ***", module);
}
throw new ClassNotFoundException("No converter found for " + key);
} while (true);
}
private static <S, SS extends S, T, TT extends T> Converter<SS, TT> createConverter(ConverterCreator creater, Class<SS> sourceClass, Class<TT> targetClass) {
return creater.createConverter(sourceClass, targetClass);
}
/** Load all classes that implement <code>Converter</code> and are
* contained in <code>containerClass</code>.
*
* @param containerClass
*/
public static void loadContainedConverters(Class<?> containerClass) {
// This only returns -public- classes and interfaces
for (Class<?> clz: containerClass.getClasses()) {
try {
// non-abstract, which means no interfaces or abstract classes
if ((clz.getModifiers() & Modifier.ABSTRACT) == 0) {
Object value;
try {
value = clz.getConstructor().newInstance();
} catch (NoSuchMethodException e) {
// ignore this, as this class might be some other helper class,
// with a non-pubilc constructor
continue;
}
if (value instanceof ConverterLoader) {
ConverterLoader loader = (ConverterLoader) value;
loader.loadConverters();
}
}
} catch (Exception e) {
Debug.logError(e, module);
}
}
}
/** Registers a <code>ConverterCreater</code> instance to be used by the
* {@link org.ofbiz.base.conversion.Converters#getConverter(Class, Class)}
* method, when a converter can't be found.
*
* @param <S> The source object type
* @param <T> The target object type
* @param creator The <code>ConverterCreater</code> instance to register
*/
public static <S, T> void registerCreator(ConverterCreator creator) {
synchronized (creators) {
creators.add(creator);
}
}
/** Registers a <code>Converter</code> instance to be used by the
* {@link org.ofbiz.base.conversion.Converters#getConverter(Class, Class)}
* method.
*
* @param <S> The source object type
* @param <T> The target object type
* @param converter The <code>Converter</code> instance to register
*/
public static <S, T> void registerConverter(Converter<S, T> converter) {
registerConverter(converter, converter.getSourceClass(), converter.getTargetClass());
}
public static <S, T> void registerConverter(Converter<S, T> converter, Class<?> sourceClass, Class<?> targetClass) {
StringBuilder sb = new StringBuilder();
if (sourceClass != null) {
sb.append(sourceClass.getName());
} else {
sb.append("<null>");
}
sb.append(DELIMITER);
sb.append(targetClass.getName());
String key = sb.toString();
if (converterMap.putIfAbsent(key, converter) == null) {
Debug.logVerbose("Registered converter " + converter.getClass().getName(), module);
}
}
protected static class PassThruConverterCreator implements ConverterCreator{
protected PassThruConverterCreator() {
}
public <S, T> Converter<S, T> createConverter(Class<S> sourceClass, Class<T> targetClass) {
if (ObjectType.instanceOf(sourceClass, targetClass)) {
return new PassThruConverter<S, T>(sourceClass, targetClass);
} else {
return null;
}
}
}
/** Pass thru converter used when the source and target java object
* types are the same. The <code>convert</code> method returns the
* source object.
*
*/
protected static class PassThruConverter<S, T> implements Converter<S, T> {
private final Class<S> sourceClass;
private final Class<T> targetClass;
public PassThruConverter(Class<S> sourceClass, Class<T> targetClass) {
this.sourceClass = sourceClass;
this.targetClass = targetClass;
}
public boolean canConvert(Class<?> sourceClass, Class<?> targetClass) {
return this.sourceClass == sourceClass && this.targetClass == targetClass;
}
@SuppressWarnings("unchecked")
public T convert(S obj) throws ConversionException {
return (T) obj;
}
@SuppressWarnings("unchecked")
public T convert(Class<? extends T> targetClass, S obj) throws ConversionException {
return (T) obj;
}
public Class<?> getSourceClass() {
return sourceClass;
}
public Class<?> getTargetClass() {
return targetClass;
}
}
}