/************************************************************************************** * Copyright (C) 2008 EsperTech, Inc. All rights reserved. * * http://esper.codehaus.org * * http://www.espertech.com * * ---------------------------------------------------------------------------------- * * The software in this package is published under the terms of the GPL license * * a copy of which has been included with this distribution in the license.txt file. * **************************************************************************************/ package com.espertech.esper.event.bean; import com.asper.sources.net.sf.cglib.reflect.FastClass; import com.asper.sources.net.sf.cglib.reflect.FastMethod; import com.asper.sources.openbeans.*; import com.espertech.esper.client.EventPropertyGetter; import com.espertech.esper.event.EventAdapterService; import com.espertech.esper.event.EventPropertyType; import com.espertech.esper.event.WriteablePropertyDescriptor; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import java.io.StringWriter; import java.lang.reflect.Method; import java.util.*; /** * This class offers utililty methods around introspection and CGLIB interaction. */ public class PropertyHelper { /** * Return getter for the given method and CGLIB FastClass. * @param method to return getter for * @param fastClass is the CGLIB fast classs to make FastMethod for * @param eventAdapterService factory for event beans and event types * @return property getter */ public static EventPropertyGetter getGetter(Method method, FastClass fastClass, EventAdapterService eventAdapterService) { // Get CGLib fast method handle FastMethod fastMethod = null; try { if (fastClass != null) { fastMethod = fastClass.getMethod(method); } } catch (Throwable ex) { log.warn(".getAccessors Unable to obtain CGLib fast method implementation, msg=" + ex.getMessage()); } // Construct the appropriate property getter CGLib or reflect EventPropertyGetter getter; if (fastMethod != null) { getter = new CGLibPropertyGetter(method, fastMethod, eventAdapterService); } else { getter = new ReflectionPropMethodGetter(method, eventAdapterService); } return getter; } /** * Introspects the given class and returns event property descriptors for each property found * in the class itself, it's superclasses and all interfaces this class and the superclasses implements. * @param clazz is the Class to introspect * @return list of properties */ public static List<InternalEventPropDescriptor> getProperties(Class clazz) { // Determine all interfaces implemented and the interface's parent interfaces if any Set<Class> propertyOrigClasses = new HashSet<Class>(); getImplementedInterfaceParents(clazz, propertyOrigClasses); // Add class itself propertyOrigClasses.add(clazz); // Get the set of property names for all classes return getPropertiesForClasses(propertyOrigClasses); } /** * Introspects the given class and returns event property descriptors for each writable property found * in the class itself, it's superclasses and all interfaces this class and the superclasses implements. * @param clazz is the Class to introspect * @return list of properties */ public static Set<WriteablePropertyDescriptor> getWritableProperties(Class clazz) { // Determine all interfaces implemented and the interface's parent interfaces if any Set<Class> propertyOrigClasses = new HashSet<Class>(); getImplementedInterfaceParents(clazz, propertyOrigClasses); // Add class itself propertyOrigClasses.add(clazz); // Get the set of property names for all classes return getWritablePropertiesForClasses(propertyOrigClasses); } private static void getImplementedInterfaceParents(Class clazz, Set<Class> classesResult) { Class[] interfaces = clazz.getInterfaces(); if (interfaces == null) { return; } for (int i = 0; i < interfaces.length; i++) { classesResult.add(interfaces[i]); getImplementedInterfaceParents(interfaces[i], classesResult); } } private static Set<WriteablePropertyDescriptor> getWritablePropertiesForClasses(Set<Class> propertyClasses) { Set<WriteablePropertyDescriptor> result = new HashSet<WriteablePropertyDescriptor>(); for (Class clazz : propertyClasses) { addIntrospectPropertiesWritable(clazz, result); } return result; } private static List<InternalEventPropDescriptor> getPropertiesForClasses(Set<Class> propertyClasses) { List<InternalEventPropDescriptor> result = new LinkedList<InternalEventPropDescriptor>(); for (Class clazz : propertyClasses) { addIntrospectProperties(clazz, result); addMappedProperties(clazz, result); } removeDuplicateProperties(result); removeJavaProperties(result); return result; } /** * Remove Java language specific properties from the given list of property descriptors. * @param properties is the list of property descriptors */ protected static void removeJavaProperties(List<InternalEventPropDescriptor> properties) { List<InternalEventPropDescriptor> toRemove = new LinkedList<InternalEventPropDescriptor>(); // add removed entries to separate list for (InternalEventPropDescriptor desc : properties) { if ((desc.getPropertyName().equals("class")) || (desc.getPropertyName().equals("getClass")) || (desc.getPropertyName().equals("toString")) || (desc.getPropertyName().equals("hashCode"))) { toRemove.add(desc); } } // remove for (InternalEventPropDescriptor desc : toRemove) { properties.remove(desc); } } /** * Removed duplicate properties using the property name to find unique properties. * @param properties is a list of property descriptors */ protected static void removeDuplicateProperties(List<InternalEventPropDescriptor> properties) { LinkedHashMap<String, InternalEventPropDescriptor> set = new LinkedHashMap<String, InternalEventPropDescriptor>(); List<InternalEventPropDescriptor> toRemove = new LinkedList<InternalEventPropDescriptor>(); // add duplicates to separate list for (InternalEventPropDescriptor desc : properties) { if (set.containsKey(desc.getPropertyName())) { toRemove.add(desc); continue; } set.put(desc.getPropertyName(), desc); } // remove duplicates for (InternalEventPropDescriptor desc : toRemove) { properties.remove(desc); } } /** * Adds to the given list of property descriptors the properties of the given class * using the Introspector to introspect properties. This also finds array and indexed properties. * @param clazz to introspect * @param result is the list to add to */ protected static void addIntrospectProperties(Class clazz, List<InternalEventPropDescriptor> result) { PropertyDescriptor properties[] = introspect(clazz); for (int i = 0; i < properties.length; i++) { PropertyDescriptor property = properties[i]; String propertyName = property.getName(); Method readMethod = property.getReadMethod(); EventPropertyType type = EventPropertyType.SIMPLE; if (property instanceof IndexedPropertyDescriptor) { readMethod = ((IndexedPropertyDescriptor) property).getIndexedReadMethod(); type = EventPropertyType.INDEXED; } if (readMethod == null) { continue; } result.add(new InternalEventPropDescriptor(propertyName, readMethod, type)); } } private static void addIntrospectPropertiesWritable(Class clazz, Set<WriteablePropertyDescriptor> result) { PropertyDescriptor properties[] = introspect(clazz); for (int i = 0; i < properties.length; i++) { PropertyDescriptor property = properties[i]; String propertyName = property.getName(); Method writeMethod = property.getWriteMethod(); if (writeMethod == null) { continue; } result.add(new WriteablePropertyDescriptor(propertyName, writeMethod.getParameterTypes()[0], writeMethod)); } } /** * Adds to the given list of property descriptors the mapped properties, ie. * properties that have a getter method taking a single String value as a parameter. * @param clazz to introspect * @param result is the list to add to */ protected static void addMappedProperties(Class clazz, List<InternalEventPropDescriptor> result) { Set<String> uniquePropertyNames = new HashSet<String>(); Method[] methods = clazz.getMethods(); for (int i = 0; i < methods.length; i++) { String methodName = methods[i].getName(); if (!methodName.startsWith("get")) { continue; } String inferredName = methodName.substring(3, methodName.length()); if (inferredName.length() == 0) { continue; } Class<?> parameterTypes[] = methods[i].getParameterTypes(); if (parameterTypes.length != 1) { continue; } if (parameterTypes[0] != String.class) { continue; } String newInferredName = null; // Leave uppercase inferred names such as URL if (inferredName.length() >= 2) { if ((Character.isUpperCase(inferredName.charAt(0))) && (Character.isUpperCase(inferredName.charAt(1)))) { newInferredName = inferredName; } } // camelCase the inferred name if (newInferredName == null) { newInferredName = Character.toString(Character.toLowerCase(inferredName.charAt(0))); if (inferredName.length() > 1) { newInferredName += inferredName.substring(1, inferredName.length()); } } inferredName = newInferredName; // if the property inferred name already exists, don't supply it if (uniquePropertyNames.contains(inferredName)) { continue; } result.add(new InternalEventPropDescriptor(inferredName, methods[i], EventPropertyType.MAPPED)); uniquePropertyNames.add(inferredName); } } /** * Using the Java Introspector class the method returns the property descriptors obtained through introspection. * @param clazz to introspect * @return array of property descriptors */ protected static PropertyDescriptor[] introspect(Class clazz) { BeanInfo beanInfo; try { beanInfo = Introspector.getBeanInfo(clazz); } catch (IntrospectionException e) { return (new PropertyDescriptor[0]); } return beanInfo.getPropertyDescriptors(); } public static String getGetterMethodName(String propertyName) { return getGetterSetterMethodName(propertyName, "get"); } public static String getSetterMethodName(String propertyName) { return getGetterSetterMethodName(propertyName, "set"); } public static String getIsMethodName(String propertyName) { return getGetterSetterMethodName(propertyName, "is"); } private static String getGetterSetterMethodName(String propertyName, String operation) { StringWriter writer = new StringWriter(); writer.write(operation); writer.write(Character.toUpperCase(propertyName.charAt(0))); writer.write(propertyName.substring(1)); return writer.toString(); } private static final Log log = LogFactory.getLog(PropertyHelper.class); }