Java tutorial
/* * This file is part of Spring Data Clusterpoint. * * The MIT License (MIT) * * Copyright (c) 2015 the author or authors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package io.twipple.springframework.data.clusterpoint.convert.support; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.GenericTypeResolver; import org.springframework.core.convert.converter.Converter; import org.springframework.core.convert.converter.ConverterFactory; import org.springframework.core.convert.converter.GenericConverter; import org.springframework.core.convert.support.GenericConversionService; import org.springframework.data.convert.ReadingConverter; import org.springframework.data.convert.WritingConverter; import org.springframework.data.mapping.model.SimpleTypeHolder; import org.springframework.data.util.CacheValue; import org.springframework.util.Assert; import javax.validation.constraints.NotNull; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Value object to capture custom conversion. * Types that can be mapped directly onto XML are considered simple ones, because they neither need deeper * inspection nor nested conversion. * * @author Olegs Briska */ public class CustomConversions { private static final Logger LOG = LoggerFactory.getLogger(CustomConversions.class); private static final String READ_CONVERTER_NOT_SIMPLE = "Registering converter from %s to %s as reading converter although it doesn't convert from a Clusterpoint supported type! You might wanna check you annotation setup at the converter implementation."; private static final String WRITE_CONVERTER_NOT_SIMPLE = "Registering converter from %s to %s as writing converter although it doesn't convert to a Clusterpoint supported type! You might wanna check you annotation setup at the converter implementation."; /** * Contains the simple type holder. */ private final SimpleTypeHolder simpleTypeHolder; private final List<Object> converters; private final Set<GenericConverter.ConvertiblePair> readingPairs; private final Set<GenericConverter.ConvertiblePair> writingPairs; private final Set<Class<?>> customSimpleTypes; private final ConcurrentMap<GenericConverter.ConvertiblePair, CacheValue<Class<?>>> customReadTargetTypes; /** * Create a new instance with no converters. */ public CustomConversions() { this(Collections.emptyList()); } /** * Create a new instance with a given list of conversers. * * @param converters the list of custom converters. */ public CustomConversions(@NotNull final List<?> converters) { Assert.notNull(converters); readingPairs = new LinkedHashSet<GenericConverter.ConvertiblePair>(); writingPairs = new LinkedHashSet<GenericConverter.ConvertiblePair>(); customSimpleTypes = new HashSet<Class<?>>(); customReadTargetTypes = new ConcurrentHashMap<GenericConverter.ConvertiblePair, CacheValue<Class<?>>>(); this.converters = new ArrayList<Object>(); this.converters.addAll(converters); this.converters.addAll(DateConverters.getConvertersToRegister()); for (Object converter : this.converters) { registerConversion(converter); } simpleTypeHolder = new SimpleTypeHolder(customSimpleTypes, true); } /** * Inspects the given {@link org.springframework.core.convert.converter.GenericConverter.ConvertiblePair} for ones * that have a source compatible type as source. Additionally checks assignability of the target type if one is * given. * * @param sourceType must not be {@literal null}. * @param requestedTargetType can be {@literal null}. * @param pairs must not be {@literal null}. * @return */ private static Class<?> getCustomTarget(@NotNull Class<?> sourceType, Class<?> requestedTargetType, @NotNull Iterable<GenericConverter.ConvertiblePair> pairs) { Assert.notNull(sourceType); Assert.notNull(pairs); for (GenericConverter.ConvertiblePair typePair : pairs) { if (typePair.getSourceType().isAssignableFrom(sourceType)) { Class<?> targetType = typePair.getTargetType(); if (requestedTargetType == null || targetType.isAssignableFrom(requestedTargetType)) { return targetType; } } } return null; } /** * Check that the given type is of "simple type". * * @param type the type to check. * @return if its simple type or not. */ public boolean isSimpleType(@NotNull Class<?> type) { Assert.notNull(type); return simpleTypeHolder.isSimpleType(type); } /** * Returns the simple type holder. * * @return the simple type holder. */ @NotNull public SimpleTypeHolder getSimpleTypeHolder() { return simpleTypeHolder; } /** * Populates the given {@link GenericConversionService} with the convertes registered. * * @param conversionService the service to register. */ public void registerConvertersIn(@NotNull GenericConversionService conversionService) { Assert.notNull(conversionService); for (Object converter : converters) { boolean added = false; if (converter instanceof Converter) { conversionService.addConverter((Converter<?, ?>) converter); added = true; } if (converter instanceof ConverterFactory) { conversionService.addConverterFactory((ConverterFactory<?, ?>) converter); added = true; } if (converter instanceof GenericConverter) { conversionService.addConverter((GenericConverter) converter); added = true; } if (!added) { throw new IllegalArgumentException( "Given set contains element that is neither Converter nor ConverterFactory!"); } } } /** * Registers a conversion for the given converter. Inspects either generics or the convertible pairs returned * by a {@link GenericConverter}. * * @param converter the converter to register. */ private void registerConversion(@NotNull Object converter) { Assert.notNull(converter); Class<?> type = converter.getClass(); boolean isWriting = type.isAnnotationPresent(WritingConverter.class); boolean isReading = type.isAnnotationPresent(ReadingConverter.class); if (converter instanceof GenericConverter) { GenericConverter genericConverter = (GenericConverter) converter; for (GenericConverter.ConvertiblePair pair : genericConverter.getConvertibleTypes()) { register(new ConverterRegistration(pair, isReading, isWriting)); } } else if (converter instanceof Converter) { Class<?>[] arguments = GenericTypeResolver.resolveTypeArguments(converter.getClass(), Converter.class); register(new ConverterRegistration(arguments[0], arguments[1], isReading, isWriting)); } else { throw new IllegalArgumentException("Unsupported Converter type!"); } } /** * Registers the given {@link ConverterRegistration} as reading or writing pair depending on the type sides being basic * Couchbase types. * * @param registration the registration. */ private void register(@NotNull ConverterRegistration registration) { Assert.notNull(registration); GenericConverter.ConvertiblePair pair = registration.getConvertiblePair(); if (registration.isReading()) { readingPairs.add(pair); if (LOG.isWarnEnabled() && !registration.isSimpleSourceType()) { LOG.warn(String.format(READ_CONVERTER_NOT_SIMPLE, pair.getSourceType(), pair.getTargetType())); } } if (registration.isWriting()) { writingPairs.add(pair); customSimpleTypes.add(pair.getSourceType()); if (LOG.isWarnEnabled() && !registration.isSimpleTargetType()) { LOG.warn(String.format(WRITE_CONVERTER_NOT_SIMPLE, pair.getSourceType(), pair.getTargetType())); } } } /** * Returns the target type to convert to in case we have a custom conversion registered to convert the given source * type into a Clusterpoint native one. * * @param sourceType must not be {@literal null} * @return */ public Class<?> getCustomWriteTarget(@NotNull Class<?> sourceType) { Assert.notNull(sourceType); return getCustomWriteTarget(sourceType, null); } /** * Returns the target type we can write an object of the given source type to. The returned type might be a subclass * oth the given expected type though. If {@code expectedTargetType} is {@literal null} we will simply return the * first target type matching or {@literal null} if no conversion can be found. * * @param sourceType must not be {@literal null} * @param requestedTargetType * @return */ public Class<?> getCustomWriteTarget(@NotNull Class<?> sourceType, Class<?> requestedTargetType) { Assert.notNull(sourceType); return getCustomTarget(sourceType, requestedTargetType, writingPairs); } /** * Returns whether we have a custom conversion registered to write into a Couchbase native type. The returned type might * be a subclass of the given expected type though. * * @param sourceType must not be {@literal null} * @return */ public boolean hasCustomWriteTarget(@NotNull Class<?> sourceType) { Assert.notNull(sourceType); return hasCustomWriteTarget(sourceType, null); } /** * Returns whether we have a custom conversion registered to write an object of the given source type into an object * of the given Couchbase native target type. * * @param sourceType must not be {@literal null}. * @param requestedTargetType * @return */ public boolean hasCustomWriteTarget(@NotNull Class<?> sourceType, Class<?> requestedTargetType) { Assert.notNull(sourceType); return getCustomWriteTarget(sourceType, requestedTargetType) != null; } /** * Returns whether we have a custom conversion registered to read the given source into the given target type. * * @param sourceType must not be {@literal null} * @param requestedTargetType must not be {@literal null} * @return */ public boolean hasCustomReadTarget(@NotNull Class<?> sourceType, @NotNull Class<?> requestedTargetType) { Assert.notNull(sourceType); Assert.notNull(requestedTargetType); return getCustomReadTarget(sourceType, requestedTargetType) != null; } /** * Returns the actual target type for the given {@code sourceType} and {@code requestedTargetType}. Note that the * returned {@link Class} could be an assignable type to the given {@code requestedTargetType}. * * @param sourceType must not be {@literal null}. * @param requestedTargetType can be {@literal null}. * @return */ private Class<?> getCustomReadTarget(@NotNull Class<?> sourceType, Class<?> requestedTargetType) { Assert.notNull(sourceType); if (requestedTargetType == null) { return null; } GenericConverter.ConvertiblePair lookupKey = new GenericConverter.ConvertiblePair(sourceType, requestedTargetType); CacheValue<Class<?>> readTargetTypeValue = customReadTargetTypes.get(lookupKey); if (readTargetTypeValue != null) { return readTargetTypeValue.getValue(); } readTargetTypeValue = CacheValue .<Class<?>>ofNullable(getCustomTarget(sourceType, requestedTargetType, readingPairs)); CacheValue<Class<?>> cacheValue = customReadTargetTypes.putIfAbsent(lookupKey, readTargetTypeValue); return cacheValue != null ? cacheValue.getValue() : readTargetTypeValue.getValue(); } }