io.helidon.config.ConfigMapperManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of helidon-config Show documentation
Show all versions of helidon-config Show documentation
Configuration Core module.
/*
* Copyright (c) 2017, 2020 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 extends BiFunction> 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 extends BiFunction>>> providers = new LinkedList<>();
private MapperProviders() {
}
static MapperProviders create() {
return new MapperProviders();
}
void add(Function, Optional extends BiFunction>> function) {
this.providers.addFirst(function);
}
void add(ConfigMapperProvider provider) {
add(new ProviderWrapper(provider));
}
void addAll(MapperProviders other) {
LinkedList,
Optional extends BiFunction>>> 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 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