All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.perfectable.introspection.bean.Properties Maven / Gradle / Ivy

There is a newer version: 5.1.0
Show newest version
package org.perfectable.introspection.bean;

import org.perfectable.introspection.PrivilegedActions;

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 javax.annotation.Nullable;

import com.google.common.base.Throwables;

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) {
		PrivilegedActions.markAccessible(field);
		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();
			PrivilegedActions.markAccessible(getter);
			return new ReadOnlyMethodPropertySchema<>(getter);
		}
		if (setterOption.isPresent()) {
			Method setter = setterOption.get();
			PrivilegedActions.markAccessible(setter);
			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"})
		void set(CT bean, @Nullable PT value) {
			try {
				this.field.set(bean, value);
			}
			catch (IllegalAccessException e) {
				throw new RuntimeException(e);
			}
		}

		// checked at construction
		@Override
		@Nullable
		@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(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
		@Nullable
		public PT get(CT bean) {
			try {
				return (PT) this.getter.invoke(bean);
			}
			catch (IllegalAccessException e) {
				throw new RuntimeException(e);
			}
			catch (InvocationTargetException e) {
				Throwables.throwIfUnchecked(e.getTargetException());
				throw new RuntimeException(e.getTargetException());
			}
		}

		@Override
		public void set(CT bean, @Nullable 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(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
		@Nullable
		public PT get(CT bean) {
			throw new IllegalStateException("Property is not readable");
		}

		@SuppressWarnings({"unchecked", "PMD.AvoidThrowingRawExceptionTypes", "ThrowSpecificExceptions"})
		@Override
		public void set(CT bean, @Nullable PT value) {
			try {
				this.setter.invoke(bean, value);
			}
			catch (IllegalAccessException e) {
				throw new RuntimeException(e);
			}
			catch (InvocationTargetException e) {
				Throwables.throwIfUnchecked(e.getTargetException());
				throw new RuntimeException(e.getTargetException());
			}
		}

		@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(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
		@Nullable
		public PT get(CT bean) {
			try {
				return (PT) this.getter.invoke(bean);
			}
			catch (IllegalAccessException e) {
				throw new RuntimeException(e);
			}
			catch (InvocationTargetException e) {
				Throwables.throwIfUnchecked(e.getTargetException());
				throw new RuntimeException(e.getTargetException());
			}
		}

		@Override
		@SuppressWarnings({"PMD.AvoidThrowingRawExceptionTypes", "ThrowSpecificExceptions"})
		public void set(CT bean, @Nullable PT value) {
			try {
				this.setter.invoke(bean, value);
			}
			catch (IllegalAccessException e) {
				throw new RuntimeException(e);
			}
			catch (InvocationTargetException e) {
				Throwables.throwIfUnchecked(e.getTargetException());
				throw new RuntimeException(e.getTargetException());
			}
		}

		@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(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);
		}
	}

	private Properties() {
		// utility
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy