Java tutorial
/* * Copyright 2016-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.data.mapping.model; import lombok.Getter; import java.beans.FeatureDescriptor; import java.beans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Function; import org.springframework.data.util.Lazy; import org.springframework.data.util.Optionals; import org.springframework.data.util.TypeInformation; import org.springframework.lang.Nullable; import org.springframework.util.Assert; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; /** * Value object to abstract the concept of a property backed by a {@link Field} and / or a {@link PropertyDescriptor}. * * @author Oliver Gierke * @author Christoph Strobl * @author Mark Paluch */ public class Property { private final @Getter Optional<Field> field; private final Optional<PropertyDescriptor> descriptor; private final Class<?> rawType; private final Lazy<Integer> hashCode; private final Optional<Method> getter; private final Optional<Method> setter; private final Lazy<String> name; private final Lazy<String> toString; private final Lazy<Optional<Method>> wither; private Property(TypeInformation<?> type, Optional<Field> field, Optional<PropertyDescriptor> descriptor) { Assert.notNull(type, "Type must not be null!"); Assert.isTrue(Optionals.isAnyPresent(field, descriptor), "Either field or descriptor has to be given!"); this.field = field; this.descriptor = descriptor; this.rawType = withFieldOrDescriptor( // it -> type.getRequiredProperty(it.getName()).getType(), // it -> type.getRequiredProperty(it.getName()).getType() // ); this.hashCode = Lazy.of(() -> withFieldOrDescriptor(Object::hashCode)); this.name = Lazy.of(() -> withFieldOrDescriptor(Field::getName, FeatureDescriptor::getName)); this.toString = Lazy.of(() -> withFieldOrDescriptor(Object::toString, it -> String.format("%s.%s", type.getType().getName(), it.getDisplayName()))); this.getter = descriptor.map(PropertyDescriptor::getReadMethod)// .filter(it -> getType() != null)// .filter(it -> getType().isAssignableFrom(type.getReturnType(it).getType())); this.setter = descriptor.map(PropertyDescriptor::getWriteMethod)// .filter(it -> getType() != null)// .filter(it -> type.getParameterTypes(it).get(0).getType().isAssignableFrom(getType())); this.wither = Lazy.of(() -> findWither(type, getName(), getType())); } /** * Creates a new {@link Property} backed by the given field. * * @param type the owning type, must not be {@literal null}. * @param field must not be {@literal null}. * @return */ public static Property of(TypeInformation<?> type, Field field) { Assert.notNull(field, "Field must not be null!"); return new Property(type, Optional.of(field), Optional.empty()); } /** * Creates a new {@link Property} backed by the given {@link Field} and {@link PropertyDescriptor}. * * @param type the owning type, must not be {@literal null}. * @param field must not be {@literal null}. * @param descriptor must not be {@literal null}. * @return */ public static Property of(TypeInformation<?> type, Field field, PropertyDescriptor descriptor) { Assert.notNull(field, "Field must not be null!"); Assert.notNull(descriptor, "PropertyDescriptor must not be null!"); return new Property(type, Optional.of(field), Optional.of(descriptor)); } /** * Creates a new {@link Property} for the given {@link PropertyDescriptor}. The creation might fail if the given * property is not representing a proper property. * * @param type the owning type, must not be {@literal null}. * @param descriptor must not be {@literal null}. * @return * @see #supportsStandalone(PropertyDescriptor) */ public static Property of(TypeInformation<?> type, PropertyDescriptor descriptor) { Assert.notNull(descriptor, "PropertyDescriptor must not be null!"); return new Property(type, Optional.empty(), Optional.of(descriptor)); } /** * Returns whether the given {@link PropertyDescriptor} is supported in for standalone creation of a {@link Property} * instance. * * @param descriptor * @return */ public static boolean supportsStandalone(PropertyDescriptor descriptor) { Assert.notNull(descriptor, "PropertyDescriptor must not be null!"); return descriptor.getPropertyType() != null; } /** * Returns whether the property is backed by a field. * * @return */ public boolean isFieldBacked() { return field.isPresent(); } /** * Returns the getter of the property if available and if it matches the type of the property. * * @return will never be {@literal null}. */ public Optional<Method> getGetter() { return getter; } /** * Returns the setter of the property if available and if its first (only) parameter matches the type of the property. * * @return will never be {@literal null}. */ public Optional<Method> getSetter() { return setter; } /** * Returns the wither of the property if available and if its first (only) parameter matches the type of the property. * * @return will never be {@literal null}. */ public Optional<Method> getWither() { return wither.get(); } /** * Returns whether the property exposes a getter or a setter. * * @return */ public boolean hasAccessor() { return getGetter().isPresent() || getSetter().isPresent(); } /** * Returns the name of the property. * * @return will never be {@literal null}. */ public String getName() { return this.name.get(); } /** * Returns the type of the property. * * @return will never be {@literal null}. */ public Class<?> getType() { return rawType; } /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(@Nullable Object obj) { if (this == obj) { return true; } if (!(obj instanceof Property)) { return false; } Property that = (Property) obj; return this.field.isPresent() ? this.field.equals(that.field) : this.descriptor.equals(that.descriptor); } /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return hashCode.get(); } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { return toString.get(); } /** * Maps the backing {@link Field} or {@link PropertyDescriptor} using the given {@link Function}. * * @param function must not be {@literal null}. * @return */ private <T> T withFieldOrDescriptor(Function<Object, T> function) { return withFieldOrDescriptor(function, function); } /** * Maps the backing {@link Field} or {@link PropertyDescriptor} using the given functions. * * @param field must not be {@literal null}. * @param descriptor must not be {@literal null}. * @return */ private <T> T withFieldOrDescriptor(Function<? super Field, T> field, Function<? super PropertyDescriptor, T> descriptor) { return Optionals.firstNonEmpty(// () -> this.field.map(field), // () -> this.descriptor.map(descriptor))// .orElseThrow(() -> new IllegalStateException( "Should not occur! Either field or descriptor has to be given")); } private static Optional<Method> findWither(TypeInformation<?> owner, String propertyName, Class<?> rawType) { AtomicReference<Method> resultHolder = new AtomicReference<>(); String methodName = String.format("with%s", StringUtils.capitalize(propertyName)); ReflectionUtils.doWithMethods(owner.getType(), it -> { if (owner.isAssignableFrom(owner.getReturnType(it))) { resultHolder.set(it); } }, it -> isMethodWithSingleParameterOfType(it, methodName, rawType)); Method method = resultHolder.get(); return method != null ? Optional.of(method) : Optional.empty(); } private static boolean isMethodWithSingleParameterOfType(Method method, String name, Class<?> type) { return method.getParameterCount() == 1 // && method.getName().equals(name) // && method.getParameterTypes()[0].equals(type); } }