org.springframework.boot.context.properties.PropertyMapper.java Source code

Java tutorial

Introduction

Here is the source code for org.springframework.boot.context.properties.PropertyMapper.java

Source

/*
 * Copyright 2012-2019 the original author or 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 org.springframework.boot.context.properties;

import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * Utility that can be used to map values from a supplied source to a destination.
 * Primarily intended to be help when mapping from
 * {@link ConfigurationProperties @ConfigurationProperties} to third-party classes.
 * <p>
 * Can filter values based on predicates and adapt values if needed. For example:
 * <pre class="code">
 * PropertyMapper map = PropertyMapper.get();
 * map.from(source::getName)
 *   .to(destination::setName);
 * map.from(source::getTimeout)
 *   .whenNonNull()
 *   .asInt(Duration::getSeconds)
 *   .to(destination::setTimeoutSecs);
 * map.from(source::isEnabled)
 *   .whenFalse().
 *   .toCall(destination::disable);
 * </pre>
 * <p>
 * Mappings can ultimately be applied to a {@link Source#to(Consumer) setter}, trigger a
 * {@link Source#toCall(Runnable) method call} or create a
 * {@link Source#toInstance(Function) new instance}.
 *
 * @author Phillip Webb
 * @author Artsiom Yudovin
 * @since 2.0.0
 */
public final class PropertyMapper {

    private static final Predicate<?> ALWAYS = (t) -> true;

    private static final PropertyMapper INSTANCE = new PropertyMapper(null, null);

    private final PropertyMapper parent;

    private final SourceOperator sourceOperator;

    private PropertyMapper(PropertyMapper parent, SourceOperator sourceOperator) {
        this.parent = parent;
        this.sourceOperator = sourceOperator;
    }

    /**
     * Return a new {@link PropertyMapper} instance that applies
     * {@link Source#whenNonNull() whenNonNull} to every source.
     * @return a new property mapper instance
     */
    public PropertyMapper alwaysApplyingWhenNonNull() {
        return alwaysApplying(this::whenNonNull);
    }

    private <T> Source<T> whenNonNull(Source<T> source) {
        return source.whenNonNull();
    }

    /**
     * Return a new {@link PropertyMapper} instance that applies the given
     * {@link SourceOperator} to every source.
     * @param operator the source operator to apply
     * @return a new property mapper instance
     */
    public PropertyMapper alwaysApplying(SourceOperator operator) {
        Assert.notNull(operator, "Operator must not be null");
        return new PropertyMapper(this, operator);
    }

    /**
     * Return a new {@link Source} from the specified value supplier that can be used to
     * perform the mapping.
     * @param <T> the source type
     * @param supplier the value supplier
     * @return a {@link Source} that can be used to complete the mapping
     * @see #from(Object)
     */
    public <T> Source<T> from(Supplier<T> supplier) {
        Assert.notNull(supplier, "Supplier must not be null");
        Source<T> source = getSource(supplier);
        if (this.sourceOperator != null) {
            source = this.sourceOperator.apply(source);
        }
        return source;
    }

    /**
     * Return a new {@link Source} from the specified value that can be used to perform
     * the mapping.
     * @param <T> the source type
     * @param value the value
     * @return a {@link Source} that can be used to complete the mapping
     */
    public <T> Source<T> from(T value) {
        return from(() -> value);
    }

    @SuppressWarnings("unchecked")
    private <T> Source<T> getSource(Supplier<T> supplier) {
        if (this.parent != null) {
            return this.parent.from(supplier);
        }
        return new Source<>(new CachingSupplier<>(supplier), (Predicate<T>) ALWAYS);
    }

    /**
     * Return the property mapper.
     * @return the property mapper
     */
    public static PropertyMapper get() {
        return INSTANCE;
    }

    /**
     * Supplier that caches the value to prevent multiple calls.
     */
    private static class CachingSupplier<T> implements Supplier<T> {

        private final Supplier<T> supplier;

        private boolean hasResult;

        private T result;

        CachingSupplier(Supplier<T> supplier) {
            this.supplier = supplier;
        }

        @Override
        public T get() {
            if (!this.hasResult) {
                this.result = this.supplier.get();
                this.hasResult = true;
            }
            return this.result;
        }

    }

    /**
     * An operation that can be applied to a {@link Source}.
     */
    @FunctionalInterface
    public interface SourceOperator {

        /**
         * Apply the operation to the given source.
         * @param <T> the source type
         * @param source the source to operate on
         * @return the updated source
         */
        <T> Source<T> apply(Source<T> source);

    }

    /**
     * A source that is in the process of being mapped.
     *
     * @param <T> the source type
     */
    public static final class Source<T> {

        private final Supplier<T> supplier;

        private final Predicate<T> predicate;

        private Source(Supplier<T> supplier, Predicate<T> predicate) {
            Assert.notNull(predicate, "Predicate must not be null");
            this.supplier = supplier;
            this.predicate = predicate;
        }

        /**
         * Return an adapted version of the source with {@link Integer} type.
         * @param <R> the resulting type
         * @param adapter an adapter to convert the current value to a number.
         * @return a new adapted source instance
         */
        public <R extends Number> Source<Integer> asInt(Function<T, R> adapter) {
            return as(adapter).as(Number::intValue);
        }

        /**
         * Return an adapted version of the source changed via the given adapter function.
         * @param <R> the resulting type
         * @param adapter the adapter to apply
         * @return a new adapted source instance
         */
        public <R> Source<R> as(Function<T, R> adapter) {
            Assert.notNull(adapter, "Adapter must not be null");
            Supplier<Boolean> test = () -> this.predicate.test(this.supplier.get());
            Predicate<R> predicate = (t) -> test.get();
            Supplier<R> supplier = () -> {
                if (test.get()) {
                    return adapter.apply(this.supplier.get());
                }
                return null;
            };
            return new Source<>(supplier, predicate);
        }

        /**
         * Return a filtered version of the source that won't map non-null values or
         * suppliers that throw a {@link NullPointerException}.
         * @return a new filtered source instance
         */
        public Source<T> whenNonNull() {
            return new Source<>(new NullPointerExceptionSafeSupplier<>(this.supplier), Objects::nonNull);
        }

        /**
         * Return a filtered version of the source that will only map values that are
         * {@code true}.
         * @return a new filtered source instance
         */
        public Source<T> whenTrue() {
            return when(Boolean.TRUE::equals);
        }

        /**
         * Return a filtered version of the source that will only map values that are
         * {@code false}.
         * @return a new filtered source instance
         */
        public Source<T> whenFalse() {
            return when(Boolean.FALSE::equals);
        }

        /**
         * Return a filtered version of the source that will only map values that have a
         * {@code toString()} containing actual text.
         * @return a new filtered source instance
         */
        public Source<T> whenHasText() {
            return when((value) -> StringUtils.hasText(Objects.toString(value, null)));
        }

        /**
         * Return a filtered version of the source that will only map values equal to the
         * specified {@code object}.
         * @param object the object to match
         * @return a new filtered source instance
         */
        public Source<T> whenEqualTo(Object object) {
            return when(object::equals);
        }

        /**
         * Return a filtered version of the source that will only map values that are an
         * instance of the given type.
         * @param <R> the target type
         * @param target the target type to match
         * @return a new filtered source instance
         */
        public <R extends T> Source<R> whenInstanceOf(Class<R> target) {
            return when(target::isInstance).as(target::cast);
        }

        /**
         * Return a filtered version of the source that won't map values that match the
         * given predicate.
         * @param predicate the predicate used to filter values
         * @return a new filtered source instance
         */
        public Source<T> whenNot(Predicate<T> predicate) {
            Assert.notNull(predicate, "Predicate must not be null");
            return when(predicate.negate());
        }

        /**
         * Return a filtered version of the source that won't map values that don't match
         * the given predicate.
         * @param predicate the predicate used to filter values
         * @return a new filtered source instance
         */
        public Source<T> when(Predicate<T> predicate) {
            Assert.notNull(predicate, "Predicate must not be null");
            return new Source<>(this.supplier,
                    (this.predicate != null) ? this.predicate.and(predicate) : predicate);
        }

        /**
         * Complete the mapping by passing any non-filtered value to the specified
         * consumer.
         * @param consumer the consumer that should accept the value if it's not been
         * filtered
         */
        public void to(Consumer<T> consumer) {
            Assert.notNull(consumer, "Consumer must not be null");
            T value = this.supplier.get();
            if (this.predicate.test(value)) {
                consumer.accept(value);
            }
        }

        /**
         * Complete the mapping by creating a new instance from the non-filtered value.
         * @param <R> the resulting type
         * @param factory the factory used to create the instance
         * @return the instance
         * @throws NoSuchElementException if the value has been filtered
         */
        public <R> R toInstance(Function<T, R> factory) {
            Assert.notNull(factory, "Factory must not be null");
            T value = this.supplier.get();
            if (!this.predicate.test(value)) {
                throw new NoSuchElementException("No value present");
            }
            return factory.apply(value);
        }

        /**
         * Complete the mapping by calling the specified method when the value has not
         * been filtered.
         * @param runnable the method to call if the value has not been filtered
         */
        public void toCall(Runnable runnable) {
            Assert.notNull(runnable, "Runnable must not be null");
            T value = this.supplier.get();
            if (this.predicate.test(value)) {
                runnable.run();
            }
        }

    }

    /**
     * Supplier that will catch and ignore any {@link NullPointerException}.
     */
    private static class NullPointerExceptionSafeSupplier<T> implements Supplier<T> {

        private final Supplier<T> supplier;

        NullPointerExceptionSafeSupplier(Supplier<T> supplier) {
            this.supplier = supplier;
        }

        @Override
        public T get() {
            try {
                return this.supplier.get();
            } catch (NullPointerException ex) {
                return null;
            }
        }

    }

}