001/*
002 * Copyright 2012-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.boot.context.properties;
018
019import java.util.NoSuchElementException;
020import java.util.Objects;
021import java.util.function.Consumer;
022import java.util.function.Function;
023import java.util.function.Predicate;
024import java.util.function.Supplier;
025
026import org.springframework.util.Assert;
027import org.springframework.util.StringUtils;
028
029/**
030 * Utility that can be used to map values from a supplied source to a destination.
031 * Primarily intended to be help when mapping from
032 * {@link ConfigurationProperties @ConfigurationProperties} to third-party classes.
033 * <p>
034 * Can filter values based on predicates and adapt values if needed. For example:
035 * <pre class="code">
036 * PropertyMapper map = PropertyMapper.get();
037 * map.from(source::getName)
038 *   .to(destination::setName);
039 * map.from(source::getTimeout)
040 *   .whenNonNull()
041 *   .asInt(Duration::getSeconds)
042 *   .to(destination::setTimeoutSecs);
043 * map.from(source::isEnabled)
044 *   .whenFalse().
045 *   .toCall(destination::disable);
046 * </pre>
047 * <p>
048 * Mappings can ultimately be applied to a {@link Source#to(Consumer) setter}, trigger a
049 * {@link Source#toCall(Runnable) method call} or create a
050 * {@link Source#toInstance(Function) new instance}.
051 *
052 * @author Phillip Webb
053 * @since 2.0.0
054 */
055public final class PropertyMapper {
056
057        private static final Predicate<?> ALWAYS = (t) -> true;
058
059        private static final PropertyMapper INSTANCE = new PropertyMapper(null, null);
060
061        private final PropertyMapper parent;
062
063        private final SourceOperator sourceOperator;
064
065        private PropertyMapper(PropertyMapper parent, SourceOperator sourceOperator) {
066                this.parent = parent;
067                this.sourceOperator = sourceOperator;
068        }
069
070        /**
071         * Return a new {@link PropertyMapper} instance that applies
072         * {@link Source#whenNonNull() whenNonNull} to every source.
073         * @return a new property mapper instance
074         */
075        public PropertyMapper alwaysApplyingWhenNonNull() {
076                return alwaysApplying(this::whenNonNull);
077        }
078
079        private <T> Source<T> whenNonNull(Source<T> source) {
080                return source.whenNonNull();
081        }
082
083        /**
084         * Return a new {@link PropertyMapper} instance that applies the given
085         * {@link SourceOperator} to every source.
086         * @param operator the source operator to apply
087         * @return a new property mapper instance
088         */
089        public PropertyMapper alwaysApplying(SourceOperator operator) {
090                Assert.notNull(operator, "Operator must not be null");
091                return new PropertyMapper(this, operator);
092        }
093
094        /**
095         * Return a new {@link Source} from the specified value supplier that can be used to
096         * perform the mapping.
097         * @param <T> the source type
098         * @param supplier the value supplier
099         * @return a {@link Source} that can be used to complete the mapping
100         * @see #from(Object)
101         */
102        public <T> Source<T> from(Supplier<T> supplier) {
103                Assert.notNull(supplier, "Supplier must not be null");
104                Source<T> source = getSource(supplier);
105                if (this.sourceOperator != null) {
106                        source = this.sourceOperator.apply(source);
107                }
108                return source;
109        }
110
111        /**
112         * Return a new {@link Source} from the specified value that can be used to perform
113         * the mapping.
114         * @param <T> the source type
115         * @param value the value
116         * @return a {@link Source} that can be used to complete the mapping
117         */
118        public <T> Source<T> from(T value) {
119                return from(() -> value);
120        }
121
122        @SuppressWarnings("unchecked")
123        private <T> Source<T> getSource(Supplier<T> supplier) {
124                if (this.parent != null) {
125                        return this.parent.from(supplier);
126                }
127                return new Source<>(new CachingSupplier<>(supplier), (Predicate<T>) ALWAYS);
128        }
129
130        /**
131         * Return the property mapper.
132         * @return the property mapper
133         */
134        public static PropertyMapper get() {
135                return INSTANCE;
136        }
137
138        /**
139         * Supplier that caches the value to prevent multiple calls.
140         */
141        private static class CachingSupplier<T> implements Supplier<T> {
142
143                private final Supplier<T> supplier;
144
145                private boolean hasResult;
146
147                private T result;
148
149                CachingSupplier(Supplier<T> supplier) {
150                        this.supplier = supplier;
151                }
152
153                @Override
154                public T get() {
155                        if (!this.hasResult) {
156                                this.result = this.supplier.get();
157                                this.hasResult = true;
158                        }
159                        return this.result;
160                }
161
162        }
163
164        /**
165         * An operation that can be applied to a {@link Source}.
166         */
167        @FunctionalInterface
168        public interface SourceOperator {
169
170                /**
171                 * Apply the operation to the given source.
172                 * @param <T> the source type
173                 * @param source the source to operate on
174                 * @return the updated source
175                 */
176                <T> Source<T> apply(Source<T> source);
177
178        }
179
180        /**
181         * A source that is in the process of being mapped.
182         *
183         * @param <T> the source type
184         */
185        public static final class Source<T> {
186
187                private final Supplier<T> supplier;
188
189                private final Predicate<T> predicate;
190
191                private Source(Supplier<T> supplier, Predicate<T> predicate) {
192                        Assert.notNull(predicate, "Predicate must not be null");
193                        this.supplier = supplier;
194                        this.predicate = predicate;
195                }
196
197                /**
198                 * Return an adapted version of the source with {@link Integer} type.
199                 * @param <R> the resulting type
200                 * @param adapter an adapter to convert the current value to a number.
201                 * @return a new adapted source instance
202                 */
203                public <R extends Number> Source<Integer> asInt(Function<T, R> adapter) {
204                        return as(adapter).as(Number::intValue);
205                }
206
207                /**
208                 * Return an adapted version of the source changed via the given adapter function.
209                 * @param <R> the resulting type
210                 * @param adapter the adapter to apply
211                 * @return a new adapted source instance
212                 */
213                public <R> Source<R> as(Function<T, R> adapter) {
214                        Assert.notNull(adapter, "Adapter must not be null");
215                        Supplier<Boolean> test = () -> this.predicate.test(this.supplier.get());
216                        Predicate<R> predicate = (t) -> test.get();
217                        Supplier<R> supplier = () -> {
218                                if (test.get()) {
219                                        return adapter.apply(this.supplier.get());
220                                }
221                                return null;
222                        };
223                        return new Source<>(supplier, predicate);
224                }
225
226                /**
227                 * Return a filtered version of the source that won't map non-null values or
228                 * suppliers that throw a {@link NullPointerException}.
229                 * @return a new filtered source instance
230                 */
231                public Source<T> whenNonNull() {
232                        return new Source<>(new NullPointerExceptionSafeSupplier<>(this.supplier),
233                                        Objects::nonNull);
234                }
235
236                /**
237                 * Return a filtered version of the source that will only map values that are
238                 * {@code true}.
239                 * @return a new filtered source instance
240                 */
241                public Source<T> whenTrue() {
242                        return when(Boolean.TRUE::equals);
243                }
244
245                /**
246                 * Return a filtered version of the source that will only map values that are
247                 * {@code false}.
248                 * @return a new filtered source instance
249                 */
250                public Source<T> whenFalse() {
251                        return when(Boolean.FALSE::equals);
252                }
253
254                /**
255                 * Return a filtered version of the source that will only map values that have a
256                 * {@code toString()} containing actual text.
257                 * @return a new filtered source instance
258                 */
259                public Source<T> whenHasText() {
260                        return when((value) -> StringUtils.hasText(Objects.toString(value, null)));
261                }
262
263                /**
264                 * Return a filtered version of the source that will only map values equal to the
265                 * specified {@code object}.
266                 * @param object the object to match
267                 * @return a new filtered source instance
268                 */
269                public Source<T> whenEqualTo(Object object) {
270                        return when(object::equals);
271                }
272
273                /**
274                 * Return a filtered version of the source that will only map values that are an
275                 * instance of the given type.
276                 * @param <R> the target type
277                 * @param target the target type to match
278                 * @return a new filtered source instance
279                 */
280                public <R extends T> Source<R> whenInstanceOf(Class<R> target) {
281                        return when(target::isInstance).as(target::cast);
282                }
283
284                /**
285                 * Return a filtered version of the source that won't map values that match the
286                 * given predicate.
287                 * @param predicate the predicate used to filter values
288                 * @return a new filtered source instance
289                 */
290                public Source<T> whenNot(Predicate<T> predicate) {
291                        Assert.notNull(predicate, "Predicate must not be null");
292                        return new Source<>(this.supplier, predicate.negate());
293                }
294
295                /**
296                 * Return a filtered version of the source that won't map values that don't match
297                 * the given predicate.
298                 * @param predicate the predicate used to filter values
299                 * @return a new filtered source instance
300                 */
301                public Source<T> when(Predicate<T> predicate) {
302                        Assert.notNull(predicate, "Predicate must not be null");
303                        return new Source<>(this.supplier, predicate);
304                }
305
306                /**
307                 * Complete the mapping by passing any non-filtered value to the specified
308                 * consumer.
309                 * @param consumer the consumer that should accept the value if it's not been
310                 * filtered
311                 */
312                public void to(Consumer<T> consumer) {
313                        Assert.notNull(consumer, "Consumer must not be null");
314                        T value = this.supplier.get();
315                        if (this.predicate.test(value)) {
316                                consumer.accept(value);
317                        }
318                }
319
320                /**
321                 * Complete the mapping by creating a new instance from the non-filtered value.
322                 * @param <R> the resulting type
323                 * @param factory the factory used to create the instance
324                 * @return the instance
325                 * @throws NoSuchElementException if the value has been filtered
326                 */
327                public <R> R toInstance(Function<T, R> factory) {
328                        Assert.notNull(factory, "Factory must not be null");
329                        T value = this.supplier.get();
330                        if (!this.predicate.test(value)) {
331                                throw new NoSuchElementException("No value present");
332                        }
333                        return factory.apply(value);
334                }
335
336                /**
337                 * Complete the mapping by calling the specified method when the value has not
338                 * been filtered.
339                 * @param runnable the method to call if the value has not been filtered
340                 */
341                public void toCall(Runnable runnable) {
342                        Assert.notNull(runnable, "Runnable must not be null");
343                        T value = this.supplier.get();
344                        if (this.predicate.test(value)) {
345                                runnable.run();
346                        }
347                }
348
349        }
350
351        /**
352         * Supplier that will catch and ignore any {@link NullPointerException}.
353         */
354        private static class NullPointerExceptionSafeSupplier<T> implements Supplier<T> {
355
356                private final Supplier<T> supplier;
357
358                NullPointerExceptionSafeSupplier(Supplier<T> supplier) {
359                        this.supplier = supplier;
360                }
361
362                @Override
363                public T get() {
364                        try {
365                                return this.supplier.get();
366                        }
367                        catch (NullPointerException ex) {
368                                return null;
369                        }
370                }
371
372        }
373
374}