org.keycloak.models.utils.reflection.MethodPropertyImpl Maven / Gradle / Ivy
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* 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.keycloak.models.utils.reflection;
import org.keycloak.common.util.reflections.Reflections;
import java.beans.Introspector;
import java.lang.annotation.Annotation;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
/**
* A bean property based on the value represented by a getter/setter method pair
*/
class MethodPropertyImpl implements MethodProperty {
private static final String GETTER_METHOD_PREFIX = "get";
private static final String SETTER_METHOD_PREFIX = "set";
private static final String BOOLEAN_GETTER_METHOD_PREFIX = "is";
private static final int GETTER_METHOD_PREFIX_LENGTH = GETTER_METHOD_PREFIX.length();
private static final int SETTER_METHOD_PREFIX_LENGTH = SETTER_METHOD_PREFIX.length();
private static final int BOOLEAN_GETTER_METHOD_PREFIX_LENGTH = BOOLEAN_GETTER_METHOD_PREFIX.length();
private final Method getterMethod;
private final String propertyName;
private final Method setterMethod;
public MethodPropertyImpl(Method method) {
final String accessorMethodPrefix;
final String propertyNameInAccessorMethod;
if (method.getName().startsWith(GETTER_METHOD_PREFIX)) {
if (method.getReturnType() == Void.TYPE) {
throw new IllegalArgumentException(
"Invalid accessor method, must have return value if starts with 'get'. Method: " + method);
} else if (method.getParameterTypes().length > 0) {
throw new IllegalArgumentException(
"Invalid accessor method, must have zero arguments if starts with 'get'. Method: " + method);
}
propertyNameInAccessorMethod = method.getName().substring(GETTER_METHOD_PREFIX_LENGTH);
accessorMethodPrefix = GETTER_METHOD_PREFIX;
} else if (method.getName().startsWith(SETTER_METHOD_PREFIX)) {
if (method.getReturnType() != Void.TYPE) {
throw new IllegalArgumentException(
"Invalid accessor method, must not have return value if starts with 'set'. Method: " + method);
} else if (method.getParameterTypes().length != 1) {
throw new IllegalArgumentException(
"Invalid accessor method, must have one argument if starts with 'set'. Method: " + method);
}
propertyNameInAccessorMethod = method.getName().substring(SETTER_METHOD_PREFIX_LENGTH);
accessorMethodPrefix = SETTER_METHOD_PREFIX;
} else if (method.getName().startsWith(BOOLEAN_GETTER_METHOD_PREFIX)) {
if (method.getReturnType() != Boolean.TYPE || !method.getReturnType().isPrimitive()) {
throw new IllegalArgumentException(
"Invalid accessor method, must return boolean primitive if starts with 'is'. Method: " +
method);
}
propertyNameInAccessorMethod = method.getName().substring(BOOLEAN_GETTER_METHOD_PREFIX_LENGTH);
accessorMethodPrefix = BOOLEAN_GETTER_METHOD_PREFIX;
} else {
throw new IllegalArgumentException(
"Invalid accessor method, must start with 'get', 'set' or 'is'. " + "Method: " + method);
}
if (propertyNameInAccessorMethod.length() == 0 ||
!Character.isUpperCase(propertyNameInAccessorMethod.charAt(0))) {
throw new IllegalArgumentException("Invalid accessor method, prefix '" + accessorMethodPrefix +
"' must be followed a non-empty property name, capitalized. Method: " + method);
}
this.propertyName = Introspector.decapitalize(propertyNameInAccessorMethod);
this.getterMethod = getGetterMethod(method.getDeclaringClass(), propertyName);
this.setterMethod = getSetterMethod(method.getDeclaringClass(), propertyName);
}
@Override
public String getName() {
return propertyName;
}
@SuppressWarnings("unchecked")
@Override
public Class getJavaClass() {
return (Class) getterMethod.getReturnType();
}
@Override
public Type getBaseType() {
return getterMethod.getGenericReturnType();
}
@Override
public Method getAnnotatedElement() {
return getterMethod;
}
@Override
public Member getMember() {
return getterMethod;
}
@Override
public V getValue(Object instance) {
if (getterMethod == null) {
throw new UnsupportedOperationException("Property " +
this.setterMethod.getDeclaringClass() + "." + propertyName +
" cannot be read, as there is no getter method.");
}
return Reflections.cast(Reflections.invokeMethod(getterMethod, instance));
}
@Override
public void setValue(Object instance, V value) {
if (setterMethod == null) {
// if the setter method is null may be because the declaring type is an interface which does not declare
// a setter method. We just check if the instance is assignable from the property declaring class and
// try to find a overridden method.
if (getDeclaringClass().isAssignableFrom(instance.getClass())) {
Method instanceSetterMethod = getSetterMethod(instance.getClass(), getName());
if (instanceSetterMethod != null) {
Reflections.invokeMethod(instanceSetterMethod, instance, value);
return;
}
}
throw new UnsupportedOperationException("Property " +
this.getterMethod.getDeclaringClass() + "." + propertyName +
" is read only, as there is no setter method.");
}
Reflections.invokeMethod(setterMethod, instance, value);
}
private static Method getSetterMethod(Class> clazz, String name) {
Method[] methods = clazz.getMethods();
for (Method method : methods) {
String methodName = method.getName();
if (methodName.startsWith(SETTER_METHOD_PREFIX) && method.getParameterTypes().length == 1) {
if (Introspector.decapitalize(methodName.substring(SETTER_METHOD_PREFIX_LENGTH)).equals(name)) {
return method;
}
}
}
return null;
}
private static Method getGetterMethod(Class> clazz, String name) {
for (Method method : clazz.getDeclaredMethods()) {
String methodName = method.getName();
if (method.getParameterTypes().length == 0) {
if (methodName.startsWith(GETTER_METHOD_PREFIX)) {
if (Introspector.decapitalize(methodName.substring(GETTER_METHOD_PREFIX_LENGTH)).equals(name)) {
return method;
}
} else if (methodName.startsWith(BOOLEAN_GETTER_METHOD_PREFIX)) {
if (Introspector.decapitalize(
methodName.substring(BOOLEAN_GETTER_METHOD_PREFIX_LENGTH)).equals(name)) {
return method;
}
}
}
}
throw new IllegalArgumentException("no such getter method: " + clazz.getName() + '.' + name);
}
@Override
public Class> getDeclaringClass() {
return getterMethod.getDeclaringClass();
}
@Override
public boolean isReadOnly() {
return setterMethod == null;
}
@Override
public void setAccessible() {
if (setterMethod != null) {
Reflections.setAccessible(setterMethod);
}
if (getterMethod != null) {
Reflections.setAccessible(getterMethod);
}
}
@Override
public boolean isAnnotationPresent(final Class extends Annotation> annotation) {
return getAnnotatedElement() != null && getAnnotatedElement().isAnnotationPresent(annotation);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
if (isReadOnly()) {
builder.append("read-only ").append(setterMethod.toString()).append("; ");
}
builder.append(getterMethod.toString());
return builder.toString();
}
@Override
public int hashCode() {
int hash = 1;
hash = hash * 31 + (setterMethod == null ? 0 : setterMethod.hashCode());
hash = hash * 31 + getterMethod.hashCode();
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof MethodPropertyImpl>) {
MethodPropertyImpl> that = (MethodPropertyImpl>) obj;
if (this.setterMethod == null) {
return that.setterMethod == null && this.getterMethod.equals(that.getterMethod);
} else {
return this.setterMethod.equals(that.setterMethod) && this.getterMethod.equals(that.getterMethod);
}
} else {
return false;
}
}
}