ConversionContext.java

/*
 * Copyright 2017-2020 original authors
 *
 * 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
 *
 * https://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 io.micronaut.core.convert;

import io.micronaut.core.annotation.AnnotationMetadataProvider;
import io.micronaut.core.type.Argument;
import io.micronaut.core.type.TypeVariableResolver;
import io.micronaut.core.util.ArgumentUtils;
import io.micronaut.core.util.ArrayUtils;

import io.micronaut.core.annotation.Nullable;
import java.lang.annotation.Annotation;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;

/**
 * A conversion context is a context object supplied to a {@link TypeConverter} that allows more accurate conversion.
 *
 * @author Graeme Rocher
 * @since 1.0
 */
public interface ConversionContext extends AnnotationMetadataProvider, TypeVariableResolver, ErrorsContext {

    /**
     * The default conversion context.
     */
    ConversionContext DEFAULT = new ConversionContext() {
    };

    /**
     * Constant for Boolean argument.
     */
    ArgumentConversionContext<Boolean> BOOLEAN = ImmutableArgumentConversionContext.of(Argument.BOOLEAN);

    /**
     * Constant for Integer argument.
     */
    ArgumentConversionContext<Integer> INT = ImmutableArgumentConversionContext.of(Argument.INT);

    /**
     * Constant for Long argument.
     */
    ArgumentConversionContext<Long> LONG = ImmutableArgumentConversionContext.of(Argument.LONG);

    /**
     * Constant for String argument.
     */
    ArgumentConversionContext<String> STRING = ImmutableArgumentConversionContext.of(Argument.STRING);

    /**
     * Constant for {@code List<String>} argument.
     */
    ArgumentConversionContext<List<String>> LIST_OF_STRING = ImmutableArgumentConversionContext.of(Argument.LIST_OF_STRING);

    /**
     * Constant for {@code List<String>} argument.
     */
    ArgumentConversionContext<Map> MAP = ImmutableArgumentConversionContext.of(Argument.of(Map.class));

    /**
     * In the case where the type to be converted contains generic type arguments this map will return
     * the concrete types of those arguments. For example for the {@link Map} type two keys will be present
     * called 'K' and 'V' with the actual types of the key and value.
     *
     * @return A map of type variables
     */
    @Override
    default Map<String, Argument<?>> getTypeVariables() {
        return Collections.emptyMap();
    }

    /**
     * @return The locale to use
     */
    default Locale getLocale() {
        return Locale.getDefault();
    }

    /**
     * @return The standard charset used in conversion
     */
    default Charset getCharset() {
        return StandardCharsets.UTF_8;
    }

    /**
     * Augment this context with data for the given argument.
     *
     * @param <T>      type Generic
     * @param argument The argument
     * @return The conversion context
     */
    @SuppressWarnings("unchecked")
    default <T> ArgumentConversionContext<T> with(Argument<T> argument) {

        ConversionContext childContext = ConversionContext.of(argument);
        ConversionContext thisContext = this;
        return new DefaultArgumentConversionContext(argument, thisContext.getLocale(), thisContext.getCharset()) {
            @Override
            public <T extends Annotation> T synthesize(Class<T> annotationClass) {
                T annotation = childContext.synthesize(annotationClass);
                if (annotation == null) {
                    return thisContext.synthesize(annotationClass);
                }
                return annotation;
            }

            @Override
            public Annotation[] synthesizeAll() {
                return ArrayUtils.concat(childContext.synthesizeAll(), thisContext.synthesizeAll());
            }

            @Override
            public Annotation[] synthesizeDeclared() {
                return ArrayUtils.concat(childContext.synthesizeDeclared(), thisContext.synthesizeDeclared());
            }

            @Override
            public void reject(Exception exception) {
                thisContext.reject(exception);
            }

            @Override
            public void reject(Object value, Exception exception) {
                thisContext.reject(value, exception);
            }

            @Override
            public Iterator<ConversionError> iterator() {
                return thisContext.iterator();
            }

            @Override
            public Optional<ConversionError> getLastError() {
                return thisContext.getLastError();
            }
        };
    }

    /**
     * Create a simple {@link ConversionContext} for the given generic type variables.
     *
     * @param typeVariables The type variables
     * @return The conversion context
     */
    static ConversionContext of(Map<String, Argument<?>> typeVariables) {
        return new ConversionContext() {
            @Override
            public Map<String, Argument<?>> getTypeVariables() {
                return typeVariables;
            }

        };
    }

    /**
     * Create a new simple {@link ConversionContext} for the given generic type variables.
     *
     * <p>NOTE: The instance returned by this method is NOT thread safe and should be shared
     * via static state or between threads. Consider using {@link io.micronaut.core.convert.ImmutableArgumentConversionContext} for this case.</p>
     *
     * @param <T>      type Generic
     * @param argument The argument
     * @return The conversion context
     */
    static <T> ArgumentConversionContext<T> of(Argument<T> argument) {
        return of(argument, null, null);
    }

    /**
     * Create a simple {@link ConversionContext} for the given generic type variables.
     *
     * <p>NOTE: The instance returned by this method is NOT thread safe and should be shared
     * via static state or between threads. Consider using {@link io.micronaut.core.convert.ImmutableArgumentConversionContext} for this case.</p>
     *
     * @param <T>      type Generic
     * @param argument The argument
     * @return The conversion context
     */
    static <T> ArgumentConversionContext<T> of(Class<T> argument) {
        ArgumentUtils.requireNonNull("argument", argument);
        return of(Argument.of(argument), null, null);
    }

    /**
     * Create a simple {@link ConversionContext} for the given generic type variables.
     *
     * <p>NOTE: The instance returned by this method is NOT thread safe and should be shared
     * via static state or between threads. Consider using {@link io.micronaut.core.convert.ImmutableArgumentConversionContext} for this case.</p>
     *
     * @param <T>      type Generic
     * @param argument The argument
     * @param locale   The locale
     * @return The conversion context
     */
    static <T> ArgumentConversionContext of(Argument<T> argument, @Nullable Locale locale) {
        return of(argument, locale, null);
    }

    /**
     * Create a simple {@link ConversionContext} for the given generic type variables.
     *
     * <p>NOTE: The instance returned by this method is NOT thread safe and should be shared
     * via static state or between threads. Consider using {@link io.micronaut.core.convert.ImmutableArgumentConversionContext} for this case.</p>
     *
     * @param <T>      type Generic
     * @param argument The argument
     * @param locale   The locale
     * @param charset  The charset
     * @return The conversion context
     */
    static <T> ArgumentConversionContext<T> of(Argument<T> argument, @Nullable Locale locale, @Nullable Charset charset) {
        ArgumentUtils.requireNonNull("argument", argument);
        Charset finalCharset = charset != null ? charset : StandardCharsets.UTF_8;
        Locale finalLocale = locale != null ? locale : Locale.getDefault();
        return new DefaultArgumentConversionContext<>(argument, finalLocale, finalCharset);
    }
}