/*
* Copyright 2016-2017 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
*
* http://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 org.springframework.data.util.Lazy;
import org.springframework.util.Assert;
/**
* 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
*/
public class Property {
private final @Getter Optional<Field> field;
private final Optional<PropertyDescriptor> descriptor;
private final Lazy<Class<?>> rawType;
private final Lazy<Integer> hashCode;
private Property(Optional<Field> field, Optional<PropertyDescriptor> descriptor) {
this.field = field;
this.descriptor = descriptor;
this.hashCode = Lazy.of(this::computeHashCode);
this.rawType = Lazy.of(
() -> field.<Class<?>> map(Field::getType).orElseGet(() -> descriptor.map(PropertyDescriptor::getPropertyType)//
.orElseThrow(IllegalStateException::new)));
}
/**
* Creates a new {@link Property} backed by the given field.
*
* @param field must not be {@literal null}.
* @return
*/
public static Property of(Field field) {
Assert.notNull(field, "Field must not be null!");
return new Property(Optional.of(field), Optional.empty());
}
/**
* Creates a new {@link Property} backed by the given {@link Field} and {@link PropertyDescriptor}.
*
* @param field must not be {@literal null}.
* @param descriptor must not be {@literal null}.
* @return
*/
public static Property of(Field field, PropertyDescriptor descriptor) {
Assert.notNull(field, "Field must not be null!");
Assert.notNull(descriptor, "PropertyDescriptor must not be null!");
return new Property(Optional.of(field), Optional.of(descriptor));
}
/**
* Creates a new {@link Property} for the given {@link PropertyDescriptor}.
*
* @param descriptor must not be {@literal null}.
* @return
*/
public static Property of(PropertyDescriptor descriptor) {
Assert.notNull(descriptor, "PropertyDescriptor must not be null!");
return new Property(Optional.empty(), Optional.of(descriptor));
}
/**
* 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 descriptor.map(PropertyDescriptor::getReadMethod)//
.filter(it -> getType().isAssignableFrom(it.getReturnType()));
}
/**
* 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 descriptor.map(PropertyDescriptor::getWriteMethod)//
.filter(it -> it.getParameterTypes()[0].isAssignableFrom(getType()));
}
/**
* 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 field.map(Field::getName)//
.orElseGet(() -> descriptor.map(FeatureDescriptor::getName)//
.orElseThrow(IllegalStateException::new));
}
/**
* Returns the type of the property.
*
* @return will never be {@literal null}.
*/
public Class<?> getType() {
return rawType.get();
}
private int computeHashCode() {
return this.field.map(Field::hashCode)
.orElseGet(() -> this.descriptor.map(PropertyDescriptor::hashCode).orElseThrow(IllegalStateException::new));
}
/*
* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode() {
return hashCode.get();
}
/*
* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(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#toString()
*/
@Override
public String toString() {
return field.map(Object::toString)
.orElseGet(() -> descriptor.map(Object::toString).orElseThrow(IllegalStateException::new));
}
}