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

com.hp.autonomy.frontend.configuration.ConfigurationUtils Maven / Gradle / Ivy

package com.hp.autonomy.frontend.configuration;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Supplier;

/**
 * Utilities for common configuration component operations
 */
@SuppressWarnings({"UtilityClass", "WeakerAccess"})
public class ConfigurationUtils {
    private static final String STATIC_BUILDER_FACTORY_METHOD = "builder";
    private static final String BUILD_METHOD = "build";

    /**
     * Merges (nullable) local configuration with (nullable) default configuration for a given custom merge function
     *
     * @param local    local configuration object
     * @param defaults default configuration object
     * @param merge    the merge function
     * @param       the configuration object type
     * @return the merged configuration
     */
    public static > F mergeConfiguration(final F local, final F defaults, final Supplier merge) {
        return Optional.ofNullable(defaults).map(v -> merge.get()).orElse(local);
    }

    /**
     * Merges a (nullable) simple field on the local config object with a default (nullable) value in the default config object
     *
     * @param localValue   local field value
     * @param defaultValue default field value
     * @param           the configuration object type
     * @return the merged field value
     */
    public static  F mergeField(final F localValue, final F defaultValue) {
        return Optional.ofNullable(localValue).orElse(defaultValue);
    }

    /**
     * Merges a (nullable) object field on the local config object with a default (nullable) value in the default config object
     *
     * @param localValue   local field value
     * @param defaultValue default field value
     * @param           the configuration object type
     * @return the merged field value
     */
    public static > F mergeComponent(final F localValue, final F defaultValue) {
        return Optional.ofNullable(localValue).map(v -> v.merge(defaultValue)).orElse(defaultValue);
    }

    /**
     * Merges a (nullable) map on the local config object with a (nullable) map in the default config object
     * by adding all the fields from the default map to a new map, and then adding the local fields on top
     *
     * @param localValue   local field value
     * @param defaultValue default field value
     * @param           the map key type
     * @param           the map value type
     * @return the merged field value
     */
    public static  Map mergeMap(final Map localValue, final Map defaultValue) {
        return Optional.ofNullable(localValue).map(v -> Optional.ofNullable(defaultValue).map(w -> {
            final Map map = new LinkedHashMap<>(w);
            map.putAll(v);
            return map;
        }).orElse(localValue)).orElse(defaultValue);
    }

    /**
     * Calls {@link ConfigurationComponent#basicValidate(String)} on the supplied component if not null
     *
     * @param component the nullable component
     * @param section   the configuration section
     * @param        the component type
     * @throws ConfigException validation failure
     */
    public static > void basicValidate(final F component, final String section) throws ConfigException {
        final Optional maybeComponent = Optional.ofNullable(component);
        if (maybeComponent.isPresent()) {
            maybeComponent.get().basicValidate(section);
        }
    }

    /**
     * Performs skeleton validation, searching for any non-null {@link ConfigurationComponent} fields and calling {@link ConfigurationComponent#basicValidate(String)}
     *
     * @param local    local configuration object
     * @param defaults default configuration object
     * @param       the configuration object type
     * @return the merged configuration
     * @see SimpleComponent for basic usage
     */
    public static > F defaultMerge(final F local, final F defaults) {
        return mergeConfiguration(local, defaults, () -> defaultMergeInternal(local, defaults));
    }

    /**
     * Calls {@link ConfigurationComponent#basicValidate(String)} on the supplied component if not null
     *
     * @param component the nullable component
     * @param section   the configuration section
     * @param        the component type
     * @throws ConfigException validation failure
     * @see SimpleComponent for basic usage
     */
    public static > void defaultValidate(final F component, final String section) throws ConfigException {
        try {
            final Class type = component.getClass();
            final BeanInfo beanInfo = Introspector.getBeanInfo(type);
            for (final PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
                if (ConfigurationComponent.class.isAssignableFrom(propertyDescriptor.getPropertyType())) {
                    @SuppressWarnings("rawtypes")
                    final ConfigurationComponent subComponent = (ConfigurationComponent) propertyDescriptor.getReadMethod().invoke(component);
                    basicValidate(subComponent, section);
                }
            }
        } catch (final IntrospectionException | IllegalAccessException | InvocationTargetException e) {
            throw new ConfigException("Error performing config bean introspection", e);
        }
    }

    private static > F defaultMergeInternal(final F local, final F defaults) {
        try {
            final Class type = local.getClass();
            final Object builder = type.getMethod(STATIC_BUILDER_FACTORY_METHOD).invoke(null);
            final Class builderType = builder.getClass();
            final BeanInfo beanInfo = Introspector.getBeanInfo(type);
            for (final PropertyDescriptor propertyDescriptor : beanInfo.getPropertyDescriptors()) {
                final String propertyName = propertyDescriptor.getName();
                final Class propertyType = propertyDescriptor.getPropertyType();
                final Method getter = propertyDescriptor.getReadMethod();
                final Object localValue = getter.invoke(local);
                final Object defaultValue = getter.invoke(defaults);
                @SuppressWarnings({"unchecked", "rawtypes"})
                final Object mergedValue = mergeProperty((Class) propertyType, localValue, defaultValue);
                final Optional maybeSetter = getMethod(builderType, propertyName, propertyType);
                if (maybeSetter.isPresent()) {
                    maybeSetter.get().invoke(builder, mergedValue);
                }
            }

            @SuppressWarnings("unchecked")
            final F mergedComponent = (F) builderType.getMethod(BUILD_METHOD).invoke(builder);
            return mergedComponent;
        } catch (final IllegalAccessException | InvocationTargetException | IntrospectionException | NoSuchMethodException e) {
            throw new ConfigRuntimeException("Error performing config bean introspection", e);
        }
    }

    private static  F mergeProperty(final Class type, final F localValue, final F defaultValue) {
        final BiFunction mergeFunction = getMergeFunctionForField(type);
        return mergeFunction.apply(localValue, defaultValue);
    }

    @SuppressWarnings({"unchecked", "rawtypes", "RedundantCast"})
    private static  BiFunction getMergeFunctionForField(final Class type) {
        final BiFunction mergeFunction;
        if (ConfigurationComponent.class.isAssignableFrom(type)) {
            mergeFunction = (BiFunction) (local, defaults) -> mergeComponent((ConfigurationComponent) local, (ConfigurationComponent) defaults);
        } else if (Map.class.isAssignableFrom(type)) {
            mergeFunction = (BiFunction) (local, defaults) -> mergeMap((Map) local, (Map) defaults);
        } else {
            mergeFunction = ConfigurationUtils::mergeField;
        }

        return mergeFunction;
    }

    private static Optional getMethod(final Class builderType, final String methodName, final Class... parameterTypes) {
        Optional maybeMethod;
        try {
            maybeMethod = Optional.of(builderType.getMethod(methodName, parameterTypes));
        } catch (final NoSuchMethodException ignored) {
            maybeMethod = Optional.empty();
        }

        return maybeMethod;
    }

    static class ConfigRuntimeException extends RuntimeException {
        private static final long serialVersionUID = 3624972600385414973L;

        private ConfigRuntimeException(final String message, final Throwable cause) {
            super(message, cause);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy