/*
 * Copyright 2016-2020 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 Field and / or a PropertyDescriptor.
Author:Oliver Gierke, Christoph Strobl, Mark Paluch
/** * 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 Property backed by the given field.
Params:
  • type – the owning type, must not be null.
  • field – must not be null.
Returns:
/** * 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 Property backed by the given Field and PropertyDescriptor.
Params:
  • type – the owning type, must not be null.
  • field – must not be null.
  • descriptor – must not be null.
Returns:
/** * 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 Property for the given PropertyDescriptor. The creation might fail if the given property is not representing a proper property.
Params:
  • type – the owning type, must not be null.
  • descriptor – must not be null.
See Also:
Returns:
/** * 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 PropertyDescriptor is supported in for standalone creation of a Property instance.
Params:
  • descriptor –
Returns:
/** * 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.
Returns:
/** * 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.
Returns:will never be null.
/** * 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.
Returns:will never be null.
/** * 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.
Returns:will never be null.
/** * 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.
Returns:
/** * 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.
Returns:will never be null.
/** * 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.
Returns:will never be null.
/** * 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 Field or PropertyDescriptor using the given Function.
Params:
  • function – must not be null.
Returns:
/** * 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 Field or PropertyDescriptor using the given functions.
Params:
  • field – must not be null.
  • descriptor – must not be null.
Returns:
/** * 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); } }