All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.helidon.config.ConfigMapperManager Maven / Gradle / Ivy

There is a newer version: 4.1.1
Show newest version
/*
 * Copyright (c) 2017, 2023 Oracle and/or its affiliates.
 *
 * 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 io.helidon.config;

import java.lang.reflect.Array;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;

import io.helidon.common.GenericType;
import io.helidon.config.spi.ConfigMapper;
import io.helidon.config.spi.ConfigMapperProvider;

/**
 * Manages registered Mappers to be used by Config implementation.
 */
class ConfigMapperManager implements ConfigMapper {
    private static final Map, Class> REPLACED_TYPES = new HashMap<>();

    static {
        REPLACED_TYPES.put(byte.class, Byte.class);
        REPLACED_TYPES.put(short.class, Short.class);
        REPLACED_TYPES.put(int.class, Integer.class);
        REPLACED_TYPES.put(long.class, Long.class);
        REPLACED_TYPES.put(float.class, Float.class);
        REPLACED_TYPES.put(double.class, Double.class);
        REPLACED_TYPES.put(boolean.class, Boolean.class);
        REPLACED_TYPES.put(char.class, Character.class);
    }

    private final Map, Mapper> mappers;
    private final MapperProviders mapperProviders;

    ConfigMapperManager(MapperProviders mapperProviders) {
        this.mappers = new ConcurrentHashMap<>();
        this.mapperProviders = mapperProviders;
    }

    @Override
    public  T map(Config config, Class type) throws MissingValueException, ConfigMappingException {
        if (type.isArray()) {
            return mapArray(config, type);
        }
        return map(config, GenericType.create(supportedType(type)));
    }

    @SuppressWarnings("unchecked")
    private  T mapArray(Config config, Class type) {
        Class componentType = type.getComponentType();
        List listValue = config.asList(componentType).get();
        Object result = Array.newInstance(componentType, listValue.size());
        for (int i = 0; i < listValue.size(); i++) {
             Object component = listValue.get(i);
            Array.set(result, i, component);
        }
        return (T) result;
    }

    @Override
    public  T map(Config config, GenericType type) throws MissingValueException, ConfigMappingException {
        Mapper mapper = mappers.computeIfAbsent(type, theType -> findMapper(theType, config.key()));

        return cast(type, mapper.apply(config, this), config.key());
    }

    @Override
    public  T map(String value, Class type, String key) throws MissingValueException, ConfigMappingException {
        return map(simpleConfig(key, value), type);
    }

    @Override
    public  T map(String value, GenericType type, String key) throws MissingValueException, ConfigMappingException {
        return map(simpleConfig(key, value), type);
    }

    @SuppressWarnings("unchecked")
     Optional> mapper(GenericType type) {
        Mapper mapper = (Mapper) mappers.get(type);
        if (null == mapper) {
            return mapperProviders.findMapper(type, Config.Key.create(""));
        } else {
            return Optional.of(mapper);
        }
    }

    private  Mapper findMapper(GenericType type, Config.Key key) {
        return mapperProviders.findMapper(type, key)
                .orElseGet(() -> noMapper(type));
    }

    private  Mapper noMapper(GenericType type) {
        return new NoMapperFound<>(type);
    }

    @SuppressWarnings("unchecked")
    static  T cast(GenericType type, Object instance, Config.Key key) throws ConfigMappingException {
        try {
            return (T) instance;
        } catch (ClassCastException ex) {
            throw new ConfigMappingException(key,
                                             type,
                                             "Created instance is not assignable to the type.",
                                             ex);
        }
    }

    /**
     * Provides mapping from a class to a supported class.
     * This is used to map Java primitives to their respective object classes.
     *
     * @param type type to map
     * @param  type of the class
     * @return object type for primitives, or the same class if not a primitive
     */
    @SuppressWarnings("unchecked")
    public static  Class supportedType(Class type) {
        return (Class) REPLACED_TYPES.getOrDefault(type, type);
    }

    Config simpleConfig(String name, String stringValue) {
        return new SingleValueConfigImpl(this, name, stringValue);
    }

    @FunctionalInterface
    interface Mapper extends BiFunction {
        static  Mapper create(BiFunction function) {
            return function::apply;
        }

        static  Mapper create(Function function) {
            return (config, configMapper) -> function.apply(config);
        }
    }

    static final class MapperProviders {
        // functions that provide mapping functions based on the type expected
        private final LinkedList,
                Optional>>> providers = new LinkedList<>();

        private MapperProviders() {
        }

        static MapperProviders create() {
            return new MapperProviders();
        }

        void add(Function, Optional>> function) {
            this.providers.addFirst(function);
        }

        void add(ConfigMapperProvider provider) {
            add(new ProviderWrapper(provider));
        }

        void addAll(MapperProviders other) {
            LinkedList,
                    Optional>>> otherProviders = new LinkedList<>(other.providers);

            Collections.reverse(otherProviders);

            otherProviders
                    .forEach(this::add);
        }

        // generic type map, generic type method, specific type map, specific type method
         Optional> findMapper(GenericType type, Config.Key key) {
            return providers.stream()
                    .map(provider -> provider.apply(type))
                    .flatMap(Optional::stream)
                    .findFirst()
                    .map(mapper -> castMapper(type, mapper, key))
                    .map(Mapper::create);
        }

        @SuppressWarnings("unchecked")
        private static  BiFunction castMapper(GenericType type,
                                                                          BiFunction mapper,
                                                                          Config.Key key) {
            try {
                return (BiFunction) mapper;
            } catch (ClassCastException e) {
                throw new ConfigMappingException(key, type, "Mapper provider returned wrong mapper type", e);
            }
        }
    }

    /**
     * This simplified implementation of Config VALUE node to be used to represent a single string value,
     * to support easy mapping of String using methods expecting a Config.
     */
    static class SingleValueConfigImpl implements Config {

        private final ConfigMapperManager mapperManager;
        private final Key key;
        private final String value;
        private final Instant timestamp;

        SingleValueConfigImpl(ConfigMapperManager mapperManager, String key, String value) {
            this.mapperManager = mapperManager;
            this.key = Key.create(key);
            this.value = value;

            this.timestamp = Instant.now();
        }

        @Override
        public boolean hasValue() {
            return null != value;
        }

        @Override
        public Key key() {
            return key;
        }

        @Override
        public ConfigValue asString() {
            return ConfigValues.create(this, () -> Optional.ofNullable(value), Config::asString);
        }

        @Override
        public Type type() {
            return Type.VALUE;
        }

        @Override
        public Instant timestamp() {
            return timestamp;
        }

        @Override
        public Config root() {
            return this;
        }

        @Override
        public Config get(Key key) {
            if (key.isRoot()) {
                return this;
            } else {
                return Config.empty().get(this.key).get(key);
            }
        }

        @Override
        public Config detach() {
            if (key.isRoot()) {
                return this;
            } else {
                return new SingleValueConfigImpl(mapperManager, "", value);
            }
        }

        @Override
        public Stream traverse(Predicate predicate) {
            return Stream.empty();
        }

        @Override
        public  ConfigValue as(Class type) {
            return ConfigValues.create(this, type, mapperManager);
        }

        @Override
        public  ConfigValue as(Function mapper) {
            return ConfigValues.create(this, mapper);
        }

        @Override
        public  ConfigValue as(GenericType genericType) {
            return ConfigValues.create(this, genericType, mapperManager);
        }

        @Override
        public ConfigValue> asNodeList() throws ConfigMappingException {
            return ConfigValues.create(this, () -> Optional.of(List.of(this)), Config::asNodeList);
        }

        @Override
        public  ConfigValue> asList(Class type) throws ConfigMappingException {
            return ConfigValues.create(this, () -> as(type).map(List::of), config -> config.asList(type));
        }

        @Override
        public  ConfigValue> asList(Function mapper) throws ConfigMappingException {
            return ConfigValues.create(this, () -> as(mapper).map(List::of), config -> config.asList(mapper));
        }

        @Override
        public ConfigValue> asMap() {
            return ConfigValues.createMap(this, mapperManager);
        }

        @Override
        public  T convert(Class type, String value) throws ConfigMappingException {
            return mapperManager.map(
                    mapperManager.simpleConfig("", value),
                    type);
        }

        @Override
        public ConfigValue asNode() {
            return as(Config.class);
        }

        @Override
        public ConfigMapper mapper() {
            return mapperManager;
        }
    }

    // this class exists for debugging purposes - it is clearly seen that this mapper was not found
    // rather then having a lambda as a mapper
    private static final class NoMapperFound implements Mapper {
        private final GenericType type;

        private NoMapperFound(GenericType type) {
            this.type = type;
        }

        @Override
        public T apply(Config config, ConfigMapper configMapper) {
            throw new ConfigMappingException(config.key(), type, "No mapper configured");
        }

        @Override
        public String toString() {
            return "Mapper for " + type.getTypeName() + " is not defined";
        }
    }

    private static final class ProviderWrapper
            implements Function, Optional>> {
        private final ConfigMapperProvider provider;

        private ProviderWrapper(ConfigMapperProvider wrapped) {
            this.provider = wrapped;
        }

        @Override
        public Optional> apply(GenericType genericType) {
            // first try to get it from generic type mappers map
            BiFunction converter = provider.genericTypeMappers().get(genericType);

            if (null != converter) {
                return Optional.of(converter);
            }

            // second try to get it from generic type method
            Optional> mapper1 = provider.mapper(genericType);

            if (mapper1.isPresent()) {
                return mapper1;
            }

            if (!genericType.isClass()) {
                return Optional.empty();
            }

            // third try the specific class map
            Class rawType = genericType.rawType();

            Function configConverter = provider.mappers().get(rawType);

            if (null != configConverter) {
                return Optional.of((config, mapper) -> configConverter.apply(config));
            }

            // and last, the specific class method
            return provider.mapper(rawType)
                    .map(funct -> (config, mapper) -> funct.apply(config));
        }

        @Override
        public String toString() {
            return provider.toString();
        }
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy