io.twipple.springframework.data.clusterpoint.convert.support.CustomConversions.java Source code

Java tutorial

Introduction

Here is the source code for io.twipple.springframework.data.clusterpoint.convert.support.CustomConversions.java

Source

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