// Copyright (c) 2003-present, Jodd Team (http://jodd.org) // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. package jodd.introspector; import jodd.util.ClassUtil; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import static jodd.util.ClassUtil.METHOD_GET_PREFIX; import static jodd.util.ClassUtil.METHOD_IS_PREFIX; /** * Bean properties collection. Property in Java is defined as a pair of * read and write method. In Jodd, property can be extended with field * definition. Moreover, properties will include just single fields. * This behavior can be controlled via {@link ClassDescriptor}. */ public class Properties { protected final ClassDescriptor classDescriptor; protected final HashMap<String, PropertyDescriptor> propertyDescriptors; // cache private PropertyDescriptor[] allProperties; public Properties(ClassDescriptor classDescriptor) { this.classDescriptor = classDescriptor; this.propertyDescriptors = inspectProperties(); } /** * Inspects all properties of target type. */ protected HashMap<String, PropertyDescriptor> inspectProperties() { boolean scanAccessible = classDescriptor.isScanAccessible(); Class type = classDescriptor.getType(); HashMap<String, PropertyDescriptor> map = new HashMap<>(); Method[] methods = scanAccessible ? ClassUtil.getAccessibleMethods(type) : ClassUtil.getSupportedMethods(type); for (int iteration = 0; iteration < 2; iteration++) { // first find the getters, and then the setters! for (Method method : methods) { if (Modifier.isStatic(method.getModifiers())) { continue; // ignore static methods } boolean add = false; boolean issetter = false; String propertyName; if (iteration == 0) { propertyName = ClassUtil.getBeanPropertyGetterName(method); if (propertyName != null) { add = true; issetter = false; } } else { propertyName = ClassUtil.getBeanPropertySetterName(method); if (propertyName != null) { add = true; issetter = true; } } if (add) { MethodDescriptor methodDescriptor = classDescriptor.getMethodDescriptor(method.getName(), method.getParameterTypes(), true); addProperty(map, propertyName, methodDescriptor, issetter); } } } if (classDescriptor.isIncludeFieldsAsProperties()) { FieldDescriptor[] fieldDescriptors = classDescriptor.getAllFieldDescriptors(); String[] prefix = classDescriptor.getPropertyFieldPrefix(); for (FieldDescriptor fieldDescriptor : fieldDescriptors) { Field field = fieldDescriptor.getField(); if (Modifier.isStatic(field.getModifiers())) { continue; // ignore static fields } String name = field.getName(); if (prefix != null) { for (String p : prefix) { if (!name.startsWith(p)) { continue; } name = name.substring(p.length()); break; } } if (!map.containsKey(name)) { // add missing field as a potential property map.put(name, createPropertyDescriptor(name, fieldDescriptor)); } } } return map; } /** * Adds a setter and/or getter method to the property. * If property is already defined, the new, updated, definition will be created. */ protected void addProperty(HashMap<String, PropertyDescriptor> map, String name, MethodDescriptor methodDescriptor, boolean isSetter) { MethodDescriptor setterMethod = isSetter ? methodDescriptor : null; MethodDescriptor getterMethod = isSetter ? null : methodDescriptor; PropertyDescriptor existing = map.get(name); if (existing == null) { // new property, just add it PropertyDescriptor propertyDescriptor = createPropertyDescriptor(name, getterMethod, setterMethod); map.put(name, propertyDescriptor); return; } // property exist if (!isSetter) { // use existing setter setterMethod = existing.getWriteMethodDescriptor(); // check existing MethodDescriptor existingMethodDescriptor = existing.getReadMethodDescriptor(); if (existingMethodDescriptor != null) { // check for special case of double get/is // getter with the same name already exist String methodName = methodDescriptor.getMethod().getName(); String existingMethodName = existingMethodDescriptor.getMethod().getName(); if ( existingMethodName.startsWith(METHOD_IS_PREFIX) && methodName.startsWith(METHOD_GET_PREFIX)) { // ignore getter when ister exist return; } } } else { // setter // use existing getter getterMethod = existing.getReadMethodDescriptor(); if (getterMethod != null) { Class returnType = getterMethod.getMethod().getReturnType(); if (setterMethod != null) { Class parameterType = setterMethod.getMethod().getParameterTypes()[0]; if (returnType != parameterType) { // getter's type is different then setter's return; } } } } PropertyDescriptor propertyDescriptor = createPropertyDescriptor(name, getterMethod, setterMethod); map.put(name, propertyDescriptor); } /** * Creates new {@link PropertyDescriptor}. Note that this method may be called * up to three times (depends on use case) for the same property. Each time when * a property is updated, a new definition is created with updated information. */ protected PropertyDescriptor createPropertyDescriptor(String name, MethodDescriptor getterMethod, MethodDescriptor setterMethod) { return new PropertyDescriptor(classDescriptor, name, getterMethod, setterMethod); } /** * Creates new field-only {@link PropertyDescriptor}. It will be invoked only once. */ protected PropertyDescriptor createPropertyDescriptor(String name, FieldDescriptor fieldDescriptor) { return new PropertyDescriptor(classDescriptor, name, fieldDescriptor); } // ---------------------------------------------------------------- get /** * Returns {@link PropertyDescriptor property descriptor}. */ public PropertyDescriptor getPropertyDescriptor(String name) { return propertyDescriptors.get(name); } /** * Returns all property descriptors. * Properties are sorted by name. */ public PropertyDescriptor[] getAllPropertyDescriptors() { if (allProperties == null) { PropertyDescriptor[] allProperties = new PropertyDescriptor[propertyDescriptors.size()]; int index = 0; for (PropertyDescriptor propertyDescriptor : propertyDescriptors.values()) { allProperties[index] = propertyDescriptor; index++; } Arrays.sort(allProperties, new Comparator<PropertyDescriptor>() { public int compare(PropertyDescriptor pd1, PropertyDescriptor pd2) { return pd1.getName().compareTo(pd2.getName()); } }); this.allProperties = allProperties; } return allProperties; } }