org.perfectable.introspection.bean.Properties Maven / Gradle / Ivy
package org.perfectable.introspection.bean;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Pattern;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import static com.google.common.base.Preconditions.checkArgument;
import static java.util.Objects.requireNonNull;
import static org.perfectable.introspection.Introspections.introspect;
final class Properties {
private static final String BOOLEAN_GETTER_PREFIX = "is";
private static final String STANDARD_GETTER_PREFIX = "get";
private static final String SETTER_PREFIX = "set";
static PropertySchema fromField(Field field) {
field.setAccessible(true);
return new FieldPropertySchema<>(field);
}
static PropertySchema create(Class beanClass, String name) {
requireNonNull(beanClass);
Optional field = introspect(beanClass).fields().named(name).option();
if (field.isPresent()) {
return fromField(field.get());
}
Optional getterOption = findGetter(beanClass, name);
Optional setterOption = findSetter(beanClass, name);
if (setterOption.isPresent() && getterOption.isPresent()) {
Method getter = getterOption.get();
Method setter = setterOption.get();
ReadWriteMethodPropertySchema.checkCompatibility(getter, setter);
return new ReadWriteMethodPropertySchema<>(getter, setter);
}
if (getterOption.isPresent()) {
Method getter = getterOption.get();
getter.setAccessible(true);
return new ReadOnlyMethodPropertySchema<>(getter);
}
if (setterOption.isPresent()) {
Method setter = setterOption.get();
setter.setAccessible(true);
return new WriteOnlyMethodPropertySchema<>(setter);
}
throw new IllegalArgumentException("No property " + name + " for " + beanClass);
}
private static Optional findGetter(Class> beanClass, String name) {
Pattern getterNamePattern = Pattern.compile("(?:is|get)" + Pattern.quote(capitalize(name)));
return introspect(beanClass).methods()
.nameMatching(getterNamePattern)
.parameters()
.option();
}
private static Optional findSetter(Class> beanClass, String name) {
String setterName = setterName(name);
return introspect(beanClass).methods()
.named(setterName)
.parameterCount(1)
.returningVoid()
.option();
}
private static String propertyNameFromGetter(Method getter) {
String unformatted = getter.getName();
int prefixLength = getterPrefix(getter.getReturnType()).length();
return String.valueOf(unformatted.charAt(prefixLength)).toLowerCase(Locale.ROOT)
+ unformatted.substring(prefixLength + 1);
}
private static String propertyNameFromSetter(Method setter) {
String unformatted = setter.getName();
int prefixLength = SETTER_PREFIX.length();
return String.valueOf(unformatted.charAt(prefixLength)).toLowerCase(Locale.ROOT)
+ unformatted.substring(prefixLength + 1);
}
private static String setterName(String name) {
return SETTER_PREFIX + capitalize(name);
}
private static String getterPrefix(Class> returnType) {
boolean returnsBoolean = Boolean.class.equals(returnType)
|| boolean.class.equals(returnType);
return returnsBoolean ? BOOLEAN_GETTER_PREFIX : STANDARD_GETTER_PREFIX;
}
private static String capitalize(String name) {
return name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1);
}
static final class FieldPropertySchema extends PropertySchema {
private final Field field;
FieldPropertySchema(Field field) {
this.field = field;
}
@Override
@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes", "ThrowSpecificExceptions", "argument"})
void set(CT bean, PT value) {
try {
this.field.set(bean, value);
}
catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
// checked at construction
@Override
@SuppressWarnings({"unchecked", "PMD.AvoidThrowingRawExceptionTypes", "ThrowSpecificExceptions"})
PT get(CT bean) {
try {
return (PT) this.field.get(bean);
}
catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
@Override
public String name() {
return this.field.getName();
}
// checked at construction
@Override
@SuppressWarnings("unchecked")
public Class type() {
return (Class) this.field.getType();
}
@Override
public boolean isReadable() {
return true;
}
@Override
public boolean isWritable() {
return !Modifier.isFinal(this.field.getModifiers());
}
@Override
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof FieldPropertySchema, ?>)) {
return false;
}
FieldPropertySchema, ?> other = (FieldPropertySchema, ?>) obj;
return Objects.equals(field, other.field);
}
@Override
public int hashCode() {
return Objects.hash(field);
}
}
static final class ReadOnlyMethodPropertySchema extends PropertySchema {
private final Method getter;
ReadOnlyMethodPropertySchema(Method getter) {
this.getter = requireNonNull(getter);
}
@SuppressWarnings({"unchecked", "PMD.AvoidThrowingRawExceptionTypes", "ThrowSpecificExceptions"})
@Override
public PT get(CT bean) {
try {
return (PT) this.getter.invoke(bean);
}
catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
catch (InvocationTargetException e) {
throw unwrapInvocationTargetException(e);
}
}
@Override
public void set(CT bean, PT value) {
throw new IllegalStateException("Property is not writable");
}
@Override
public String name() {
return propertyNameFromGetter(this.getter);
}
@Override
public Class type() {
@SuppressWarnings("unchecked")
Class resultType = (Class) this.getter.getReturnType();
return requireNonNull(resultType);
}
@Override
public boolean isReadable() {
return introspect(getter).isCallable();
}
@Override
public boolean isWritable() {
return false;
}
@Override
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof ReadOnlyMethodPropertySchema, ?>)) {
return false;
}
ReadOnlyMethodPropertySchema, ?> other = (ReadOnlyMethodPropertySchema, ?>) obj;
return Objects.equals(getter, other.getter);
}
@Override
public int hashCode() {
return Objects.hash(getter);
}
}
static final class WriteOnlyMethodPropertySchema extends PropertySchema {
private final Method setter;
WriteOnlyMethodPropertySchema(Method setter) {
this.setter = requireNonNull(setter);
}
@Override
public PT get(CT bean) {
throw new IllegalStateException("Property is not readable");
}
@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes", "ThrowSpecificExceptions",
"argument"})
@Override
public void set(CT bean, PT value) {
try {
this.setter.invoke(bean, value);
}
catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
catch (InvocationTargetException e) {
throw unwrapInvocationTargetException(e);
}
}
@Override
public String name() {
return propertyNameFromSetter(this.setter);
}
@Override
public Class type() {
Class>[] parameterTypes = this.setter.getParameterTypes();
@SuppressWarnings("unchecked") // checked at construction
Class firstParameterType = (Class) parameterTypes[0];
return firstParameterType;
}
@Override
public boolean isReadable() {
return false;
}
@Override
public boolean isWritable() {
return introspect(this.setter).isCallable();
}
@Override
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof WriteOnlyMethodPropertySchema, ?>)) {
return false;
}
WriteOnlyMethodPropertySchema, ?> other = (WriteOnlyMethodPropertySchema, ?>) obj;
return Objects.equals(setter, other.setter);
}
@Override
public int hashCode() {
return Objects.hash(setter);
}
}
static final class ReadWriteMethodPropertySchema extends PropertySchema {
private final Method getter;
private final Method setter;
ReadWriteMethodPropertySchema(Method getter, Method setter) {
this.getter = getter;
this.setter = setter;
}
@SuppressWarnings({"unchecked", "PMD.AvoidThrowingRawExceptionTypes", "ThrowSpecificExceptions"})
@Override
public PT get(CT bean) {
try {
return (PT) this.getter.invoke(bean);
}
catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
catch (InvocationTargetException e) {
throw unwrapInvocationTargetException(e);
}
}
@Override
@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes", "ThrowSpecificExceptions", "argument"})
public void set(CT bean, PT value) {
try {
this.setter.invoke(bean, value);
}
catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
catch (InvocationTargetException e) {
throw unwrapInvocationTargetException(e);
}
}
@Override
public String name() {
return propertyNameFromGetter(this.getter);
}
@SuppressWarnings("unchecked")
@Override
public Class type() {
return (Class) this.getter.getReturnType();
}
@Override
public boolean isReadable() {
return introspect(getter).isCallable();
}
@Override
public boolean isWritable() {
return introspect(setter).isCallable();
}
private static void checkCompatibility(Method getter, Method setter) {
Class> getterReturnType = getter.getReturnType();
Class> setterAcceptType = setter.getParameterTypes()[0];
checkArgument(getterReturnType.equals(setterAcceptType));
}
@Override
public boolean equals(@Nullable Object obj) {
if (!(obj instanceof ReadWriteMethodPropertySchema, ?>)) {
return false;
}
ReadWriteMethodPropertySchema, ?> other = (ReadWriteMethodPropertySchema, ?>) obj;
return Objects.equals(getter, other.getter)
&& Objects.equals(setter, other.setter);
}
@Override
public int hashCode() {
return Objects.hash(getter, setter);
}
}
@SuppressWarnings("ThrowSpecificExceptions")
private static RuntimeException unwrapInvocationTargetException(InvocationTargetException source) {
@Nullable Throwable target = source.getTargetException();
if (target == null) {
throw new RuntimeException(source);
}
if (target instanceof RuntimeException) {
throw (RuntimeException) target;
}
if (target instanceof Error) {
throw (Error) target;
}
throw new RuntimeException(target);
}
private Properties() {
// utility
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy