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.bind;
018
019import java.util.NoSuchElementException;
020import java.util.function.Consumer;
021import java.util.function.Function;
022import java.util.function.Supplier;
023
024import org.springframework.beans.BeanUtils;
025import org.springframework.util.Assert;
026import org.springframework.util.ObjectUtils;
027
028/**
029 * A container object to return the result of a {@link Binder} bind operation. May contain
030 * either a successfully bound object or an empty result.
031 *
032 * @param <T> the result type
033 * @author Phillip Webb
034 * @author Madhura Bhave
035 * @since 2.0.0
036 */
037public final class BindResult<T> {
038
039        private static final BindResult<?> UNBOUND = new BindResult<>(null);
040
041        private final T value;
042
043        private BindResult(T value) {
044                this.value = value;
045        }
046
047        /**
048         * Return the object that was bound or throw a {@link NoSuchElementException} if no
049         * value was bound.
050         * @return the bound value (never {@code null})
051         * @throws NoSuchElementException if no value was bound
052         * @see #isBound()
053         */
054        public T get() throws NoSuchElementException {
055                if (this.value == null) {
056                        throw new NoSuchElementException("No value bound");
057                }
058                return this.value;
059        }
060
061        /**
062         * Returns {@code true} if a result was bound.
063         * @return if a result was bound
064         */
065        public boolean isBound() {
066                return (this.value != null);
067        }
068
069        /**
070         * Invoke the specified consumer with the bound value, or do nothing if no value has
071         * been bound.
072         * @param consumer block to execute if a value has been bound
073         */
074        public void ifBound(Consumer<? super T> consumer) {
075                Assert.notNull(consumer, "Consumer must not be null");
076                if (this.value != null) {
077                        consumer.accept(this.value);
078                }
079        }
080
081        /**
082         * Apply the provided mapping function to the bound value, or return an updated
083         * unbound result if no value has been bound.
084         * @param <U> the type of the result of the mapping function
085         * @param mapper a mapping function to apply to the bound value. The mapper will not
086         * be invoked if no value has been bound.
087         * @return an {@code BindResult} describing the result of applying a mapping function
088         * to the value of this {@code BindResult}.
089         */
090        public <U> BindResult<U> map(Function<? super T, ? extends U> mapper) {
091                Assert.notNull(mapper, "Mapper must not be null");
092                return of((this.value != null) ? mapper.apply(this.value) : null);
093        }
094
095        /**
096         * Return the object that was bound, or {@code other} if no value has been bound.
097         * @param other the value to be returned if there is no bound value (may be
098         * {@code null})
099         * @return the value, if bound, otherwise {@code other}
100         */
101        public T orElse(T other) {
102                return (this.value != null) ? this.value : other;
103        }
104
105        /**
106         * Return the object that was bound, or the result of invoking {@code other} if no
107         * value has been bound.
108         * @param other a {@link Supplier} of the value to be returned if there is no bound
109         * value
110         * @return the value, if bound, otherwise the supplied {@code other}
111         */
112        public T orElseGet(Supplier<? extends T> other) {
113                return (this.value != null) ? this.value : other.get();
114        }
115
116        /**
117         * Return the object that was bound, or a new instance of the specified class if no
118         * value has been bound.
119         * @param type the type to create if no value was bound
120         * @return the value, if bound, otherwise a new instance of {@code type}
121         */
122        public T orElseCreate(Class<? extends T> type) {
123                Assert.notNull(type, "Type must not be null");
124                return (this.value != null) ? this.value : BeanUtils.instantiateClass(type);
125        }
126
127        /**
128         * Return the object that was bound, or throw an exception to be created by the
129         * provided supplier if no value has been bound.
130         * @param <X> the type of the exception to be thrown
131         * @param exceptionSupplier the supplier which will return the exception to be thrown
132         * @return the present value
133         * @throws X if there is no value present
134         */
135        public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier)
136                        throws X {
137                if (this.value == null) {
138                        throw exceptionSupplier.get();
139                }
140                return this.value;
141        }
142
143        @Override
144        public boolean equals(Object obj) {
145                if (this == obj) {
146                        return true;
147                }
148                if (obj == null || getClass() != obj.getClass()) {
149                        return false;
150                }
151                return ObjectUtils.nullSafeEquals(this.value, ((BindResult<?>) obj).value);
152        }
153
154        @Override
155        public int hashCode() {
156                return ObjectUtils.nullSafeHashCode(this.value);
157        }
158
159        @SuppressWarnings("unchecked")
160        static <T> BindResult<T> of(T value) {
161                if (value == null) {
162                        return (BindResult<T>) UNBOUND;
163                }
164                return new BindResult<>(value);
165        }
166
167}