/* * 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<V> implements MethodProperty<V> { 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<V> getJavaClass() { return (Class<V>) 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; } } }