/* * Copyright 2013 Gordon Burgett and individual contributors * * Licensed 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.xflatdb.xflat.convert; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * The default implementation of {@link ConversionService}. This conversion * service manages a mapping of conversions to their converters, and can perform * no conversions initially. * @author gordon */ public class DefaultConversionService implements ConversionService { private Map<Class<?>, ConverterEntry<?>> converters = new HashMap<>(); private ReadWriteLock converterLock = new ReentrantReadWriteLock(); /** * Creates a new DefaultConversionService with no registered conversions. */ public DefaultConversionService(){ } @Override public boolean canConvert(Class<?> source, Class<?> target) { converterLock.readLock().lock(); try{ ConverterEntry<?> entry = converters.get(target); if(entry == null) return false; return entry.hasConverter(source); }finally{ converterLock.readLock().unlock(); } } @Override public <T> T convert(Object source, Class<T> target) throws ConversionException { Converter<?, ?> converter; converterLock.readLock().lock(); try{ ConverterEntry<T> entry = (ConverterEntry<T>)converters.get(target); if(entry == null){ if(source == null){ //no special converter for null, just convert to null. return null; } //else throw new ConversionNotSupportedException("No converters for target " + target); } if(source == null){ converter = entry.getNullConverter(); if(converter == null){ //no special converter for null, assume null return null; } } else{ converter = entry.getConverter(source.getClass()); if(converter == null){ throw new ConversionNotSupportedException("No converter for source type " + source.getClass()); } } }finally{ converterLock.readLock().unlock(); } return ((Converter<Object, T>)converter).convert(source); } @Override public <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter) { ConverterEntry<T> entry; converterLock.readLock().lock(); try{ entry = (ConverterEntry<T>)this.converters.get(targetType); }finally{ converterLock.readLock().unlock(); } if(entry == null){ converterLock.writeLock().lock(); try{ //doublecheck after locking entry = (ConverterEntry<T>)this.converters.get(targetType); if(entry == null){ entry = new ConverterEntry(targetType); this.converters.put(targetType, entry); } }finally{ converterLock.writeLock().unlock(); } } //concurrency managed by the entry entry.putConverter(sourceType, converter); } @Override public void removeConverter(Class<?> sourceType, Class<?> targetType) { ConverterEntry<?> entry; converterLock.readLock().lock(); try{ entry = this.converters.get(targetType); }finally{ converterLock.readLock().unlock(); } if(entry == null) return; //concurrency managed by the entry boolean isEmpty = entry.removeConverter(sourceType); if(isEmpty){ converterLock.writeLock().lock(); try{ //doublecheck inside lock if(entry.isEmpty()){ this.converters.remove(targetType); } }finally{ converterLock.writeLock().unlock(); } } } private static class ConverterEntry<T> { private Class<T> targetClass; public Class<T> getTargetClass(){ return targetClass; } private Map<Class<?>, Converter<?, ?>> converters = new HashMap<>(); private Converter<?, ?> nullConverter; public ConverterEntry(Class<T> targetClass){ this.targetClass = targetClass; } /** * Gets a converter for the source class to the target class * defined by this entry. * @param <S> The generic type of the source class. * @param clazz The source class * @return A converter for the source class to this entry's target class */ public synchronized <S> Converter<?, ?> getConverter(Class<S> clazz){ Converter<?, ?> ret = converters.get(clazz); return ret; } /** * Gets the converter for a null object. * @return */ public synchronized Converter<?, ?> getNullConverter(){ return nullConverter; } /** * Returns true iff this entry has a converter for the source class * @param clazz * @return */ public synchronized boolean hasConverter(Class<?> clazz){ return converters.containsKey(clazz); } /** * Sets a converter * @param <S> * @param clazz * @param converter */ public synchronized <S> void putConverter(Class<S> clazz, Converter<? super S, ? extends T> converter){ if(clazz == null){ nullConverter = converter; return; } this.converters.put(clazz, converter); } /** * Removes the converter associated with the given class * @param clazz */ public synchronized boolean removeConverter(Class<?> clazz){ if(clazz == null){ nullConverter = null; return this.converters.isEmpty(); } this.converters.remove(clazz); return this.converters.isEmpty() && nullConverter == null; } public synchronized boolean isEmpty(){ return this.converters.isEmpty() && nullConverter == null; } } }