| /******************************************************************************* |
| * 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; |
| } |
| } |
| } |