org.cp.elements.beans.model.Property Maven / Gradle / Ivy
/*
* Copyright 2011-Present 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.cp.elements.beans.model;
import static org.cp.elements.lang.ElementsExceptionsFactory.newPropertyReadException;
import static org.cp.elements.lang.ElementsExceptionsFactory.newPropertyWriteException;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.function.Predicate;
import org.cp.elements.beans.PropertyReadException;
import org.cp.elements.beans.PropertyWriteException;
import org.cp.elements.beans.annotation.Required;
import org.cp.elements.beans.model.support.AbstractIndexedProperty;
import org.cp.elements.lang.Assert;
import org.cp.elements.lang.ClassUtils;
import org.cp.elements.lang.Describable;
import org.cp.elements.lang.Nameable;
import org.cp.elements.lang.ObjectUtils;
import org.cp.elements.lang.PrimitiveTypeUtils;
import org.cp.elements.lang.annotation.Dsl;
import org.cp.elements.lang.annotation.FluentApi;
import org.cp.elements.lang.annotation.NotNull;
import org.cp.elements.lang.annotation.NullSafe;
import org.cp.elements.lang.annotation.Nullable;
import org.cp.elements.lang.annotation.Transient;
import org.cp.elements.lang.reflect.MethodNotFoundException;
import org.cp.elements.lang.reflect.ModifierUtils;
import org.cp.elements.util.CollectionUtils;
/**
* Abstract Data Type (ADT) modeling a {@link Object bean} property.
*
* @author John Blum
* @see java.beans.PropertyDescriptor
* @see java.lang.Comparable
* @see java.lang.annotation.Annotation
* @see java.lang.reflect.AnnotatedElement
* @see java.lang.reflect.Field
* @see java.lang.reflect.Method
* @see org.cp.elements.lang.Describable
* @see org.cp.elements.lang.Nameable
* @see org.cp.elements.lang.annotation.FluentApi
* @since 1.0.0
*/
@FluentApi
@SuppressWarnings("unused")
public class Property implements Comparable, Describable, Nameable {
public static final Function> ALL_ANNOTATIONS_RESOLVER =
annotatedElement -> annotatedElement != null
? CollectionUtils.asSet(annotatedElement.getAnnotations())
: Collections.emptySet();
public static final Function> DECLARED_ANNOTATIONS_RESOLVER =
annotatedElement -> annotatedElement != null
? CollectionUtils.asSet(annotatedElement.getDeclaredAnnotations())
: Collections.emptySet();
protected static final Function> DEFAULT_ANNOTATIONS_RESOLVER =
DECLARED_ANNOTATIONS_RESOLVER;
protected static final Predicate IS_REQUIRED = annotatedElement ->
annotatedElement != null && annotatedElement.isAnnotationPresent(Required.class);
protected static final Predicate IS_TRANSIENT_ANNOTATED = annotatedElement ->
annotatedElement != null
&& (annotatedElement.isAnnotationPresent(Transient.class)
|| annotatedElement.isAnnotationPresent(java.beans.Transient.class));
protected static final Predicate IS_TRANSIENT_FIELD = annotatedElement ->
annotatedElement instanceof Field && ModifierUtils.isTransient(annotatedElement);
protected static final Predicate IS_TRANSIENT = IS_TRANSIENT_FIELD.or(IS_TRANSIENT_ANNOTATED);
/**
* Factory method used to construct a new {@link Property} from the given, required {@link BeanModel}
* modeling the bean containing the property and a given, required {@link PropertyDescriptor}
* describing the bean property.
*
* @param beanModel {@link BeanModel} modeling the bean containing the property.
* @param propertyDescriptor {@link PropertyDescriptor} describing the bean property.
* @return a new {@link Property} modeling the bean property.
* @throws IllegalArgumentException if the {@link BeanModel} or {@link PropertyDescriptor} are {@literal null}.
* @see #Property(BeanModel, PropertyDescriptor)
* @see org.cp.elements.beans.model.BeanModel
* @see org.cp.elements.lang.annotation.Dsl
* @see java.beans.PropertyDescriptor
*/
@Dsl
public static @NotNull Property from(@NotNull BeanModel beanModel, @NotNull PropertyDescriptor propertyDescriptor) {
return new Property(beanModel, propertyDescriptor);
}
/**
* Resolves all {@link Annotation Annotations} declared on the given {@link AnnotatedElement} by using the given,
* required {@link Function} encapsulating the {@literal Strategy} for resolving {@link Annotation Annotations}
* declared on an {@link AnnotatedElement}.
*
* The default {@literal Strategies} provided by this {@link Property} class includes
* {@link AnnotatedElement#getAnnotations()} and {@link AnnotatedElement#getDeclaredAnnotations()}.
*
* @param {@link Class type} of {@link AnnotatedElement}, such as {@link Field} or {@link Method}.
* @param annotationsResolver {@link Function} encapsulating the {@literal Strategy} used to resolve
* the {@link Annotation Annotations} declared on the {@link AnnotatedElement}; should not be {@literal null},
* but defaults to {@link #DECLARED_ANNOTATIONS_RESOLVER}.
* @param annotatedElementAnnotationsCache {@link Map} acting as a {@literal cache} to lookup previously resolved
* {@link Annotation Annotations} declared on the {@link AnnotatedElement}; must not be {@literal null}.
* @param annotatedElement {@link AnnotatedElement}, such as a {@link Field} or {@link Method}, from which
* the declared {@link Annotation Annotations} are resolved.
* @return a {@link Set} of {@link Annotation Annotations} resolved from the {@link AnnotatedElement}.
* @see java.lang.reflect.AnnotatedElement
* @see java.lang.annotation.Annotation
* @see java.util.function.Function
* @see java.util.Map
*/
@NullSafe
protected static Set getAnnotatedElementAnnotations(
@NotNull Function> annotationsResolver,
@NotNull Map> annotatedElementAnnotationsCache,
@Nullable T annotatedElement) {
return annotatedElement != null
? nullSafeAnnotatedElementAnnotationsCache(annotatedElementAnnotationsCache)
.computeIfAbsent(annotatedElement, nullSafeAnnotationsResolver(annotationsResolver))
: Collections.emptySet();
}
@NullSafe
private static @NotNull Map> nullSafeAnnotatedElementAnnotationsCache(
@Nullable Map> annotatedElementAnnotations) {
return annotatedElementAnnotations != null ? annotatedElementAnnotations
: new AbstractMap<>() {
// Key is never cached; Value is always computed with the given Function.
@Override
public Set computeIfAbsent(T key,
@NotNull Function super T, ? extends Set> mappingFunction) {
return mappingFunction.apply(key);
}
@Override
public Set>> entrySet() {
return Collections.emptySet();
}
};
}
@NullSafe
private static @NotNull Function> nullSafeAnnotationsResolver(
@Nullable Function> annotationsResolver) {
return annotationsResolver != null ? annotationsResolver : DEFAULT_ANNOTATIONS_RESOLVER;
}
private final transient AtomicReference fieldReference = new AtomicReference<>(null);
private final transient FieldResolver fieldResolver = new PropertyNameFieldResolver();
private final BeanModel beanModel;
private final transient Map> fieldAnnotations = new WeakHashMap<>();
private final transient Map> methodAnnotations = new WeakHashMap<>();
private final PropertyDescriptor propertyDescriptor;
/**
* Constructs a new {@link Property} initialized with the given, required {@link BeanModel}
* modeling the bean containing this {@link Property} along with the given, required {@link PropertyDescriptor}
* describing the bean {@link Property}.
*
* @param beanModel {@link BeanModel} modeling the bean containing the property; must not be {@literal null}.
* @param propertyDescriptor {@link PropertyDescriptor} describing the bean property; must not be {@literal null}.
* @throws IllegalArgumentException if the {@link BeanModel} or {@link PropertyDescriptor} are {@literal null}.
* @see org.cp.elements.beans.model.BeanModel
* @see java.beans.PropertyDescriptor
*/
protected Property(@NotNull BeanModel beanModel, @NotNull PropertyDescriptor propertyDescriptor) {
this.beanModel = ObjectUtils.requireObject(beanModel, "BeanModel is required");
this.propertyDescriptor = ObjectUtils.requireObject(propertyDescriptor, "PropertyDescriptor is required");
}
/**
* Gets all resolvable {@link Annotation Annotations} declared on this {@link Property}.
*
* A {@link Property Property's} {@link Annotation Annotations} consist of
* all {@link Annotation Annotation's} declared on the {@link Property Property's} {@link Field}
* if the {@link Property} is not {@link #isDerived()}, as well as all {@link Annotation Annotations}
* declared on the {@link #getReadMethod() accessor method} and {@link #getWriteMethod() mutator method}
* of the {@link Property}, providing the {@link Property} is both {@link #isReadable()} and {@link #isWritable()}.
*
* @return a {@link Set} of all resolvable {@link Annotation Annotations} declared on this {@link Property}.
* @see java.lang.annotation.Annotation
* @see #getAnnotations(Function)
* @see java.util.Set
*/
public Set getAnnotations() {
return getAnnotations(DEFAULT_ANNOTATIONS_RESOLVER);
}
/**
* Gets all resolvable {@link Annotation Annotations} declared on this {@link Property} using the given, required
* {@link Function} encapsulating the {@literal Strategy} for resolving {@link Annotation Annotations}
* on all {@link AnnotatedElement AnnotatedElements} of this {@link Property}.
*
* A {@link Property Property's} {@link Annotation Annotations} consist of
* all {@link Annotation Annotation's} declared on the {@link Property Property's} {@link Field}
* if the {@link Property} is not {@link #isDerived()}, as well as all {@link Annotation Annotations}
* declared on the {@link #getReadMethod() accessor method} and {@link #getWriteMethod() mutator method}
* of the {@link Property}, providing the {@link Property} is both {@link #isReadable()} and {@link #isWritable()}.
*
* @param annotationsResolver {@link Function} encapsulating the {@literal Strategy} used to resolve
* the {@link Annotation Annotations} declared on an {@link AnnotatedElement}; should not be {@literal null},
* but defaults to {@link #DECLARED_ANNOTATIONS_RESOLVER}.
* @return a {@link Set} of all resolved {@link Annotation Annotations} declared on this {@link Property}.
* @see #getWriteMethodAnnotations(Function)
* @see #getReadMethodAnnotations(Function)
* @see #getFieldAnnotations(Function)
* @see java.lang.annotation.Annotation
* @see java.util.function.Function
* @see java.util.Set
*/
public Set getAnnotations(@NotNull Function> annotationsResolver) {
Set propertyAnnotations = new HashSet<>();
propertyAnnotations.addAll(getFieldAnnotations(annotationsResolver));
propertyAnnotations.addAll(getReadMethodAnnotations(annotationsResolver));
propertyAnnotations.addAll(getWriteMethodAnnotations(annotationsResolver));
return propertyAnnotations;
}
/**
* Gets an {@link Annotation} declared on this {@link Property} of the given {@link Class type}.
*
* @param {@link Class type} of the {@link Annotation}.
* @param annotationType {@link Class type} of {@link Annotation} to find.
* @return an {@link Annotation} declared on this {@link Property} of the given {@link Class type}
* or {@literal null} if an {@link Annotation} of the given {@link Class type} was not declared
* on this {@link Property}.
* @see #getAnnotation(Class, Function)
* @see java.lang.annotation.Annotation
*/
public @Nullable T getAnnotation(@NotNull Class annotationType) {
return getAnnotation(annotationType, DEFAULT_ANNOTATIONS_RESOLVER);
}
/**
* Gets an {@link Annotation} declared on this {@link Property} of the given {@link Class type}.
*
* @param {@link Class type} of the {@link Annotation}.
* @param annotationType {@link Class type} of {@link Annotation} to find.
* @param annotationsResolver {@link Function} encapsulating the {@literal Strategy} used to resolve
* the {@link Annotation Annotations} declared on an {@link AnnotatedElement}; should not be {@literal null},
* but defaults to {@link #DECLARED_ANNOTATIONS_RESOLVER}.
* @return an {@link Annotation} declared on this {@link Property} of the given {@link Class type}
* or {@literal null} if an {@link Annotation} of the given {@link Class type} was not declared
* on this {@link Property}.
* @see java.lang.annotation.Annotation
* @see java.util.function.Function
* @see #getAnnotations(Function)
*/
public @Nullable T getAnnotation(@NotNull Class annotationType,
@NotNull Function> annotationsResolver) {
return getAnnotations(annotationsResolver).stream()
.filter(annotation -> annotation.annotationType().equals(annotationType))
.findFirst()
.map(annotationType::cast)
.orElse(null);
}
/**
* Gets the {@link BeanAdapter bean} to which this {@link Property} belongs.
*
* @return the {@link BeanAdapter bean} to which this {@link Property} belongs.
* @see org.cp.elements.beans.model.BeanModel#getBean()
* @see org.cp.elements.beans.model.BeanAdapter
* @see org.cp.elements.lang.annotation.Dsl
* @see #getBeanModel()
*/
@Dsl
protected @NotNull BeanAdapter getBean() {
return getBeanModel().getBean();
}
/**
* Gets the {@link BeanModel} modeling the bean containing this {@link Property}.
*
* @return the {@link BeanModel} modeling the bean containing this {@link Property}.
* @see org.cp.elements.beans.model.BeanModel
* @see org.cp.elements.lang.annotation.Dsl
*/
@Dsl
public @NotNull BeanModel getBeanModel() {
return this.beanModel;
}
/**
* Gets the {@literal JavaBeans} {@link PropertyDescriptor} describing this bean {@link Property}.
*
* @return the {@literal JavaBeans} {@link PropertyDescriptor} describing this bean {@link Property}.
* @see java.beans.PropertyDescriptor
*/
public @NotNull PropertyDescriptor getDescriptor() {
return this.propertyDescriptor;
}
/**
* Gets the {@link Object} {@link Field} backing this bean {@link Property} if not derived.
*
* The {@link Object} {@link Field} may be {@literal null} if this bean {@link Property} is derived
* from some other {@link Object} {@link Field}. Think of an age property on a Person type derived
* from a person's date of birth.
*
* @return the {@link Object} {@link Field} backing this bean {@link Property} if not derived.
* @see org.cp.elements.beans.model.FieldResolver#resolve(Property)
* @see java.lang.reflect.Field
*/
protected @Nullable Field getField() {
return this.fieldReference.updateAndGet(field -> field != null ? field
: this.fieldResolver.resolve(this));
}
/**
* Gets all resolvable {@link Annotation Annotations} declared on the {@link Property Property's}
* {@link #getField() Field}.
*
* @param annotationsResolver {@link Function} encapsulating the {@literal Strategy} used to resolve
* the {@link Annotation Annotations} declared on the {@link Property Property's} {@link Field};
* should not be {@literal null}, but defaults to {@link #DECLARED_ANNOTATIONS_RESOLVER}.
* @return a {@link Set} of {@link Annotation Annotations} resolved from
* the {@link Property Property's} {@link Field}. Returns an {@link Collections#emptySet()}
* if this {@link Property} is {@link #isDerived() derived}.
* @see #getAnnotatedElementAnnotations(Function, Map, AnnotatedElement)
* @see java.lang.annotation.Annotation
* @see java.util.Set
* @see #getField()
*/
protected Set getFieldAnnotations(
@NotNull Function> annotationsResolver) {
return getAnnotatedElementAnnotations(annotationsResolver, this.fieldAnnotations, getField());
}
/**
* Alias for {@link #getReadMethod()}.
*
* @return the {@link Method} used to read from this {@link Property}.
* @see java.lang.reflect.Method
* @see #getReadMethod()
*/
protected @Nullable Method getAccessorMethod() {
return getReadMethod();
}
/**
* Gets the {@link Method} used to read from this {@link Property}.
*
* @return the {@link Method} used to read from this {@link Property};
* may return {@literal null} if the {@link Property} cannot be read.
* @see java.beans.PropertyDescriptor#getReadMethod()
* @see java.lang.reflect.Method
* @see #getWriteMethod()
* @see #getDescriptor()
*/
protected @Nullable Method getReadMethod() {
Method readMethod = getDescriptor().getReadMethod();
try {
return readMethod != null
? ClassUtils.getDeclaredMethod(getBeanModel().getTargetType(), readMethod)
: null;
}
catch (MethodNotFoundException ignore) {
return readMethod;
}
}
/**
* Gets all resolvable {@link Annotation Annotations} declared on the {@link Property Property's}
* {@link #getReadMethod() accessor method}.
*
* @param annotationsResolver {@link Function} encapsulating the {@literal Strategy} used to resolve
* the {@link Annotation Annotations} declared on this {@link Property Property's}
* {@link #getReadMethod() accessor method}; should not be {@literal null},
* but defaults to {@link #DECLARED_ANNOTATIONS_RESOLVER}.
* @return a {@link Set} of {@link Annotation Annotations} resolved from
* the {@link Property Property's} {@link #getReadMethod() accessor method}.
* Returns an {@link Collections#emptySet()} if this {@link Property} is not {@link #isReadable() readable}.
* @see #getAnnotatedElementAnnotations(Function, Map, AnnotatedElement)
* @see java.lang.annotation.Annotation
* @see #getReadMethod()
* @see java.util.Set
*/
protected Set getReadMethodAnnotations(
@NotNull Function> annotationsResolver) {
return getAnnotatedElementAnnotations(annotationsResolver, this.methodAnnotations, getReadMethod());
}
/**
* Gets the underlying {@link Object POJO} backing the bean for this {@link Property}.
*
* @return the underlying {@link Object POJO} backing the bean for this {@link Property}.
* @see org.cp.elements.beans.model.BeanAdapter#getTarget()
* @see java.lang.Object
* @see #getBean()
*/
protected @NotNull Object getTargetObject() {
return getBean().getTarget();
}
/**
* Alias for {@link #getWriteMethod()}.
*
* @return the {@link Method} used to write to this {@link Property}.
* @see java.lang.reflect.Method
* @see #getWriteMethod()
*/
protected @Nullable Method getMutatorMethod() {
return getWriteMethod();
}
/**
* Gets the {@link Method} used to write to this {@link Property}.
*
* @return the {@link Method} used to write to this {@link Property};
* may return {@literal null} if the {@link Property} cannot be written,
* such as for a read-only {@link Property}.
* @see java.beans.PropertyDescriptor#getWriteMethod()
* @see java.lang.reflect.Method
* @see #getDescriptor()
* @see #getReadMethod()
*/
protected @Nullable Method getWriteMethod() {
Method writeMethod = getDescriptor().getWriteMethod();
try {
return writeMethod != null
? ClassUtils.getDeclaredMethod(getBeanModel().getTargetType(), writeMethod)
: null;
}
catch (MethodNotFoundException ignore) {
return writeMethod;
}
}
/**
* Gets all resolvable {@link Annotation Annotations} declared on the {@link Property Property's}
* {@link #getReadMethod() mutator method}.
*
* @param annotationsResolver {@link Function} encapsulating the {@literal Strategy} used to resolve
* the {@link Annotation Annotations} declared on this {@link Property Property's}
* {@link #getWriteMethod() mutator method}; should not be {@literal null},
* but defaults to {@link #DECLARED_ANNOTATIONS_RESOLVER}.
* @return a {@link Set} of {@link Annotation Annotations} resolved from
* the {@link Property Property's} {@link #getWriteMethod() mutator method}.
* Returns an {@link Collections#emptySet()} if this {@link Property} is not {@link #isWritable() writable}.
* @see #getAnnotatedElementAnnotations(Function, Map, AnnotatedElement)
* @see java.lang.annotation.Annotation
* @see #getWriteMethod()
* @see java.util.Set
*/
protected Set getWriteMethodAnnotations(
@NotNull Function> annotationsResolver) {
return getAnnotatedElementAnnotations(annotationsResolver, this.methodAnnotations, getWriteMethod());
}
/**
* Determines whether this {@link Property} is annotated.
*
* @return a boolean value indicating whether this {@link Property} is annotated.
* @see #getAnnotations()
*/
public boolean isAnnotated() {
return !getAnnotations().isEmpty();
}
/**
* Determines whether this {@link Property} is annotated with an {@link Annotation} of the given, required
* {@link Annotation#annotationType()}.
*
* @param annotationType {@link Class type} of the {@link Annotation} to evaluate.
* @return a boolean value indicating whether this {@link Property} is annotated with an {@link Annotation}
* of the given, required {@link Annotation#annotationType()}.
* @see java.lang.annotation.Annotation#annotationType()
* @see #getAnnotations()
*/
public boolean isAnnotatedWith(@NotNull Class extends Annotation> annotationType) {
return getAnnotations().stream()
.map(Annotation::annotationType)
.anyMatch(actualAnnotationType -> actualAnnotationType.equals(annotationType));
}
/**
* Determines whether the {@link Class type} of this {@link Property} is an {@link Class#isArray() array}.
*
* @return a boolean value indicating whether the {@link Class type} of this {@link Property}
* is an {@link Class#isArray() array}.
* @see #isCollectionLike()
* @see #getType()
*/
public boolean isArrayTyped() {
return ClassUtils.isArray(getType());
}
/**
* Determines whether the {@link Class type} of this {@link Property} is {@link Collection Collection-like}.
*
* @return a boolean value indicating whether the {@link Class type} of this {@link Property}
* is {@link Collection Collection-like}.
* @see #isTypedAs(Class)
* @see #isArrayTyped()
*/
public boolean isCollectionLike() {
return isArrayTyped()
|| isTypedAs(List.class)
|| isTypedAs(Map.class)
|| isTypedAs(Set.class);
}
/**
* Determines whether this {@link Property} is derived from another bean {@link Property}.
*
* This means this {@link Property} is not backed by an {@link Object} {@link Field}.
* For example, this {@link Property} may represent the {@literal age} property of a {@literal Person} type
* where age is computed from the person's {@literal date of birth} using the {@literal birthdate} {@link Field}.
*
* @return a boolean value indicating whether this {@link Property} is derived from
* another bean {@link Property}.
* @see #getField()
*/
public boolean isDerived() {
return getField() == null;
}
/**
* Determines whether this {@link Property} is an {@link AbstractIndexedProperty Indexed-based Property}.
*
* A {@link Property} is an {@link AbstractIndexedProperty Indexed-based Property}
* if the {@link #getType() Property's Type} is an indexed-based data structure, such as an array,
* {@link List} or {@link Map}.
*
* @return a boolean value indicating whether this {@link Property}
* is an {@link AbstractIndexedProperty Indexed-based Property}.
* @see AbstractIndexedProperty#isIndexed(Property)
* @see #isArrayTyped()
*/
public boolean isIndexed() {
return isArrayTyped() || AbstractIndexedProperty.isIndexed(this);
}
/**
* Determines whether the {@link #getType()} of this {@link Property} is assignment compatible with
* the given {@link Class type}.
*
* If the {@link Class type} argument is {@literal null}, then this method returns {@literal false}.
*
* @param type {@link Class} used to evaluate this {@link Property#getType() property type}.
* @return a boolean value indicating whether the {@link #getType()} of this {@link Property}
* is assignment compatible with the given {@link Class type}.
* @see java.lang.Class
* @see #getType()
*/
@NullSafe
public boolean isTypedAs(@Nullable Class> type) {
return type != null && type.isAssignableFrom(getType());
}
/**
* Alias for {@link #isSerializable()}.
*
* @return the value of {@link #isSerializable()}.
* @see #isSerializable()
*/
public boolean isPersistent() {
return isSerializable();
}
/**
* Determine whether this {@link Property} can be read.
*
* @return a boolean value indicating whether this {@link Property} can be read.
* @see #getReadMethod()
* @see #isWritable()
*/
public boolean isReadable() {
return getReadMethod() != null;
}
/**
* Determines whether this {@link Property} is required.
*
* A {@link Property} is {@literal required} if the {@link Field} backing this {@link Property},
* the {@link Method getter method} or the {@link Method setter method} are annotated with
* the Element's {@link Required} annotation.
*
* @return a boolean value indicating whether this {@link Property} is required.
*/
public boolean isRequired() {
return IS_REQUIRED.test(getField())
|| IS_REQUIRED.test(getReadMethod())
|| IS_REQUIRED.test(getWriteMethod());
}
/**
* Determines whether this {@link Property} is serializable.
*
* A {@link Property} is {@literal serializable} if the {@link Property} is {@link #isReadable()}
* and is not {@link #isTransient()}.
*
* @return a boolean value indicating whether this {@link Property} is serializable.
* @see #isTransient()
* @see #isReadable()
*/
public boolean isSerializable() {
return isReadable() && !isTransient();
}
/**
* Determines whether this {@link Property} is transient.
*
* A {@link Property} is {@literal transient} if the {@link Field} backing this {@link Property} is declared with
* the {@literal transient} keyword, or the {@link Method getter method} or {@link Method setter method}
* are annotated with the Elements {@link Transient} annotation or the JavaBeans {@link java.beans.Transient}
* annotation.
*
* @return a boolean value indicating whether this {@link Property} is transient.
* @see #isSerializable()
*/
public boolean isTransient() {
return IS_TRANSIENT.test(getField())
|| IS_TRANSIENT.test(getReadMethod())
|| IS_TRANSIENT.test(getWriteMethod());
}
/**
* Determine whether this {@link Property} can be written.
*
* @return a boolean value indicating whether this {@link Property} can be written.
* @see #getWriteMethod()
* @see #isReadable()
*/
public boolean isWritable() {
return getWriteMethod() != null;
}
/**
* Gets the {@link String name} of this {@link Property}.
*
* @return the {@link String name} of this {@link Property}.
* @see java.lang.String
*/
public @NotNull String getName() {
return getDescriptor().getName();
}
/**
* Gets the {@link Class type} of this {@link Property}.
*
* @return the {@link Class type} of this {@link Property}.
* @see java.lang.Class
*/
@SuppressWarnings("unchecked")
public @NotNull Class> getType() {
return ObjectUtils.returnFirstNonNullValue(getDescriptor().getPropertyType(), Object.class);
}
/**
* Gets {@link Object value} of this {@link Property} as a value of the given {@link #getType() Property's type}.
*
* @param {@link Class type} of this {@link Property}.
* @return the {@link Object value} of this {@link Property} as a value of
* the given {@link #getType() Property's type}.
* @see #getValue()
* @see #getType()
*/
@SuppressWarnings("unchecked")
public @Nullable T getTypedValue() {
return (T) PrimitiveTypeUtils.primitiveToWrapperType().apply(getType()).cast(getValue());
}
/**
* Gets the {@link Object value} of this {@link Property}.
*
* @return the {@link Object value} of this {@link Property}.
* @throws PropertyReadException if this {@link Property} cannot be read.
* @see java.lang.Object
*/
public @Nullable Object getValue() {
Assert.state(isReadable(), newPropertyReadException("Property [%s] of bean [%s] is not readable",
getName(), getBean()));
return ObjectUtils.invoke(getTargetObject(), getReadMethod(), new Object[0], Object.class);
}
/**
* Sets the {@link Object value} of this {@link Property}.
*
* @param value {@link Object value} to set for this {@link Property}.
* @throws PropertyWriteException if this {@link Property} cannot be written.
* @see java.lang.Object
*/
public void setValue(Object value) {
Assert.state(isWritable(), newPropertyWriteException("Property [%s] of bean [%s] is not writable",
getName(), getBean()));
ObjectUtils.invoke(getTargetObject(), getWriteMethod(), new Object[] { value }, Void.class);
}
/**
* Returns this {@link Property} as a typed {@link Property} of the given {@link Property} {@link Class type}.
*
* @param {@link Class type} of {@link Property}.
* @param type {@link Class type} to cast this {@link Property} to; must not be {@literal null}.
* @return a typed {@link Property} of the given {@link Property} {@link Class type}.
* @throws org.cp.elements.lang.IllegalTypeException if this {@link Property} is not an instance of
* the given {@link Class type}.
*/
public @NotNull T asTypedProperty(@NotNull Class type) {
Assert.isInstanceOf(this, type);
return type.cast(this);
}
/**
* Compares this {@link Property} to the given, required {@link Property} to determine (sort) order.
*
* {@link Property Properties} are ordered by {@link #getName()}.
*
* @param property {@link Property} to compare with this {@link Property}.
* @return an {@link Integer} value indicating the order of this {@link Property} relative to the given,
* required {@link Property}. Returns less than zero if this {@link Property} is sorted before the given
* {@link Property}, greater than zero if this {@link Property} is sorted after the given {@link Property}
* and zero if this {@link Property} is equal to the given {@link Property}.
* @see #getName()
*/
@Override
public int compareTo(@NotNull Property property) {
return this.getName().compareTo(property.getName());
}
/**
* Determines whether this {@link Property} is equal to the given {@link Object}.
*
* @param obj {@link Object} to evaluate for equality with this {@link Property}.
* @return a boolean value indicating whether this {@link Property} is equal to the given {@link Object}.
* @see #getName()
*/
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Property that)) {
return false;
}
return ObjectUtils.equals(this.getName(), that.getName());
}
/**
* Computes the {@literal hash value} for this {{@link Property}.
*
* @return an {@link Integer} value with the hash code of this {@link Property}.
* @see #getName()
*/
@Override
public int hashCode() {
return ObjectUtils.hashCodeOf(getName());
}
/**
* Returns the {@link String name} of this {@link Property}.
*
* @return the {@link String name} of this {@link Property}.
* @see #getName()
*/
@Override
public String toString() {
return getName();
}
}