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.lang.annotation.Annotation;
020import java.lang.reflect.Array;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024import java.util.function.Supplier;
025
026import org.springframework.core.ResolvableType;
027import org.springframework.core.style.ToStringCreator;
028import org.springframework.util.Assert;
029import org.springframework.util.ObjectUtils;
030
031/**
032 * Source that can be bound by a {@link Binder}.
033 *
034 * @param <T> the source type
035 * @author Phillip Webb
036 * @author Madhura Bhave
037 * @since 2.0.0
038 * @see Bindable#of(Class)
039 * @see Bindable#of(ResolvableType)
040 */
041public final class Bindable<T> {
042
043        private static final Annotation[] NO_ANNOTATIONS = {};
044
045        private final ResolvableType type;
046
047        private final ResolvableType boxedType;
048
049        private final Supplier<T> value;
050
051        private final Annotation[] annotations;
052
053        private Bindable(ResolvableType type, ResolvableType boxedType, Supplier<T> value,
054                        Annotation[] annotations) {
055                this.type = type;
056                this.boxedType = boxedType;
057                this.value = value;
058                this.annotations = annotations;
059        }
060
061        /**
062         * Return the type of the item to bind.
063         * @return the type being bound
064         */
065        public ResolvableType getType() {
066                return this.type;
067        }
068
069        /**
070         * Return the boxed type of the item to bind.
071         * @return the boxed type for the item being bound
072         */
073        public ResolvableType getBoxedType() {
074                return this.boxedType;
075        }
076
077        /**
078         * Return a supplier that provides the object value or {@code null}.
079         * @return the value or {@code null}
080         */
081        public Supplier<T> getValue() {
082                return this.value;
083        }
084
085        /**
086         * Return any associated annotations that could affect binding.
087         * @return the associated annotations
088         */
089        public Annotation[] getAnnotations() {
090                return this.annotations;
091        }
092
093        /**
094         * Return a single associated annotations that could affect binding.
095         * @param <A> the annotation type
096         * @param type annotation type
097         * @return the associated annotation or {@code null}
098         */
099        @SuppressWarnings("unchecked")
100        public <A extends Annotation> A getAnnotation(Class<A> type) {
101                for (Annotation annotation : this.annotations) {
102                        if (type.isInstance(annotation)) {
103                                return (A) annotation;
104                        }
105                }
106                return null;
107        }
108
109        @Override
110        public boolean equals(Object obj) {
111                if (this == obj) {
112                        return true;
113                }
114                if (obj == null || getClass() != obj.getClass()) {
115                        return false;
116                }
117                Bindable<?> other = (Bindable<?>) obj;
118                boolean result = true;
119                result = result && nullSafeEquals(this.type.resolve(), other.type.resolve());
120                result = result && nullSafeEquals(this.annotations, other.annotations);
121                return result;
122        }
123
124        @Override
125        public int hashCode() {
126                final int prime = 31;
127                int result = 1;
128                result = prime * result + ObjectUtils.nullSafeHashCode(this.type);
129                result = prime * result + ObjectUtils.nullSafeHashCode(this.annotations);
130                return result;
131        }
132
133        @Override
134        public String toString() {
135                ToStringCreator creator = new ToStringCreator(this);
136                creator.append("type", this.type);
137                creator.append("value", (this.value != null) ? "provided" : "none");
138                creator.append("annotations", this.annotations);
139                return creator.toString();
140        }
141
142        private boolean nullSafeEquals(Object o1, Object o2) {
143                return ObjectUtils.nullSafeEquals(o1, o2);
144        }
145
146        /**
147         * Create an updated {@link Bindable} instance with the specified annotations.
148         * @param annotations the annotations
149         * @return an updated {@link Bindable}
150         */
151        public Bindable<T> withAnnotations(Annotation... annotations) {
152                return new Bindable<>(this.type, this.boxedType, this.value,
153                                (annotations != null) ? annotations : NO_ANNOTATIONS);
154        }
155
156        /**
157         * Create an updated {@link Bindable} instance with an existing value.
158         * @param existingValue the existing value
159         * @return an updated {@link Bindable}
160         */
161        public Bindable<T> withExistingValue(T existingValue) {
162                Assert.isTrue(
163                                existingValue == null || this.type.isArray()
164                                                || this.boxedType.resolve().isInstance(existingValue),
165                                () -> "ExistingValue must be an instance of " + this.type);
166                Supplier<T> value = (existingValue != null) ? () -> existingValue : null;
167                return new Bindable<>(this.type, this.boxedType, value, NO_ANNOTATIONS);
168        }
169
170        /**
171         * Create an updated {@link Bindable} instance with a value supplier.
172         * @param suppliedValue the supplier for the value
173         * @return an updated {@link Bindable}
174         */
175        public Bindable<T> withSuppliedValue(Supplier<T> suppliedValue) {
176                return new Bindable<>(this.type, this.boxedType, suppliedValue, NO_ANNOTATIONS);
177        }
178
179        /**
180         * Create a new {@link Bindable} of the type of the specified instance with an
181         * existing value equal to the instance.
182         * @param <T> the source type
183         * @param instance the instance (must not be {@code null})
184         * @return a {@link Bindable} instance
185         * @see #of(ResolvableType)
186         * @see #withExistingValue(Object)
187         */
188        @SuppressWarnings("unchecked")
189        public static <T> Bindable<T> ofInstance(T instance) {
190                Assert.notNull(instance, "Instance must not be null");
191                Class<T> type = (Class<T>) instance.getClass();
192                return of(type).withExistingValue(instance);
193        }
194
195        /**
196         * Create a new {@link Bindable} of the specified type.
197         * @param <T> the source type
198         * @param type the type (must not be {@code null})
199         * @return a {@link Bindable} instance
200         * @see #of(ResolvableType)
201         */
202        public static <T> Bindable<T> of(Class<T> type) {
203                Assert.notNull(type, "Type must not be null");
204                return of(ResolvableType.forClass(type));
205        }
206
207        /**
208         * Create a new {@link Bindable} {@link List} of the specified element type.
209         * @param <E> the element type
210         * @param elementType the list element type
211         * @return a {@link Bindable} instance
212         */
213        public static <E> Bindable<List<E>> listOf(Class<E> elementType) {
214                return of(ResolvableType.forClassWithGenerics(List.class, elementType));
215        }
216
217        /**
218         * Create a new {@link Bindable} {@link Set} of the specified element type.
219         * @param <E> the element type
220         * @param elementType the set element type
221         * @return a {@link Bindable} instance
222         */
223        public static <E> Bindable<Set<E>> setOf(Class<E> elementType) {
224                return of(ResolvableType.forClassWithGenerics(Set.class, elementType));
225        }
226
227        /**
228         * Create a new {@link Bindable} {@link Map} of the specified key and value type.
229         * @param <K> the key type
230         * @param <V> the value type
231         * @param keyType the map key type
232         * @param valueType the map value type
233         * @return a {@link Bindable} instance
234         */
235        public static <K, V> Bindable<Map<K, V>> mapOf(Class<K> keyType, Class<V> valueType) {
236                return of(ResolvableType.forClassWithGenerics(Map.class, keyType, valueType));
237        }
238
239        /**
240         * Create a new {@link Bindable} of the specified type.
241         * @param <T> the source type
242         * @param type the type (must not be {@code null})
243         * @return a {@link Bindable} instance
244         * @see #of(Class)
245         */
246        public static <T> Bindable<T> of(ResolvableType type) {
247                Assert.notNull(type, "Type must not be null");
248                ResolvableType boxedType = box(type);
249                return new Bindable<>(type, boxedType, null, NO_ANNOTATIONS);
250        }
251
252        private static ResolvableType box(ResolvableType type) {
253                Class<?> resolved = type.resolve();
254                if (resolved != null && resolved.isPrimitive()) {
255                        Object array = Array.newInstance(resolved, 1);
256                        Class<?> wrapperType = Array.get(array, 0).getClass();
257                        return ResolvableType.forClass(wrapperType);
258                }
259                if (resolved != null && resolved.isArray()) {
260                        return ResolvableType.forArrayComponent(box(type.getComponentType()));
261                }
262                return type;
263        }
264
265}