/**************************************************************************************
* 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.vaevent;
import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.EventPropertyGetter;
import com.espertech.esper.client.EventType;
import com.espertech.esper.client.PropertyAccessException;
import com.espertech.esper.collection.MultiKey;
import com.espertech.esper.collection.MultiKeyUntyped;
import com.espertech.esper.util.JavaClassHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
/**
* Utility for handling properties for the purpose of merging and versioning by revision event types.
*/
public class PropertyUtility
{
private static final Log log = LogFactory.getLog(PropertyUtility.class);
/**
* Returns a multi-key for an event and key property getters
* @param theEvent to get keys for
* @param keyPropertyGetters getters to use
* @return key
*/
public static Object getKeys(EventBean theEvent, EventPropertyGetter[] keyPropertyGetters)
{
if (keyPropertyGetters.length == 1) {
return keyPropertyGetters[0].get(theEvent);
}
Object[] keys = new Object[keyPropertyGetters.length];
for (int i = 0; i < keys.length; i++)
{
keys[i] = keyPropertyGetters[i].get(theEvent);
}
return new MultiKeyUntyped(keys);
}
/**
* From a list of property groups that include contributing event types, build a map
* of contributing event types and their type descriptor.
* @param groups property groups
* @param changesetProperties properties that change between groups
* @param keyProperties key properties
* @return map of event type and type information
*/
public static Map<EventType, RevisionTypeDesc> getPerType(PropertyGroupDesc[] groups, String[] changesetProperties, String[] keyProperties)
{
Map<EventType, RevisionTypeDesc> perType = new HashMap<EventType, RevisionTypeDesc>();
for (PropertyGroupDesc group : groups)
{
for (EventType type : group.getTypes().keySet())
{
EventPropertyGetter[] changesetGetters = getGetters(type, changesetProperties);
EventPropertyGetter[] keyGetters = getGetters(type, keyProperties);
RevisionTypeDesc pair = new RevisionTypeDesc(keyGetters, changesetGetters, group);
perType.put(type, pair);
}
}
return perType;
}
/**
* From a list of property groups that include multiple group numbers for each property,
* make a map of group numbers per property.
* @param groups property groups
* @return map of property name and group number
*/
public static Map<String, int[]> getGroupsPerProperty(PropertyGroupDesc[] groups)
{
Map<String, int[]> groupsNumsPerProp = new HashMap<String, int[]>();
for (PropertyGroupDesc group : groups)
{
for (String property : group.getProperties())
{
int[] value = groupsNumsPerProp.get(property);
if (value == null)
{
value = new int[1];
groupsNumsPerProp.put(property, value);
value[0] = group.getGroupNum();
}
else
{
int[] copy = new int[value.length + 1];
System.arraycopy(value, 0, copy, 0, value.length);
copy[value.length] = group.getGroupNum();
Arrays.sort(copy);
groupsNumsPerProp.put(property, copy);
}
}
}
return groupsNumsPerProp;
}
/**
* Analyze multiple event types and determine common property sets that form property groups.
* @param allProperties property names to look at
* @param deltaEventTypes all types contributing
* @param names names of properies
* @return groups
*/
public static PropertyGroupDesc[] analyzeGroups(String[] allProperties, EventType[] deltaEventTypes, String[] names)
{
if (deltaEventTypes.length != names.length)
{
throw new IllegalArgumentException("Delta event type number and name number of elements don't match");
}
allProperties = copyAndSort(allProperties);
Map<MultiKey<String>, PropertyGroupDesc> result = new LinkedHashMap<MultiKey<String>, PropertyGroupDesc>();
int currentGroupNum = 0;
for (int i = 0; i < deltaEventTypes.length; i++)
{
MultiKey<String> props = getPropertiesContributed(deltaEventTypes[i], allProperties);
if (props.getArray().length == 0)
{
log.warn("Event type named '" + names[i] + "' does not contribute (or override) any properties of the revision event type");
continue;
}
PropertyGroupDesc propertyGroup = result.get(props);
Map<EventType, String> typesForGroup;
if (propertyGroup == null)
{
typesForGroup = new HashMap<EventType, String>();
propertyGroup = new PropertyGroupDesc(currentGroupNum++, typesForGroup, props.getArray());
result.put(props, propertyGroup);
}
else
{
typesForGroup = propertyGroup.getTypes();
}
typesForGroup.put(deltaEventTypes[i], names[i]);
}
Collection<PropertyGroupDesc> outColl = result.values();
PropertyGroupDesc[] array = outColl.toArray(new PropertyGroupDesc[outColl.size()]);
if (log.isDebugEnabled())
{
log.debug(".analyzeGroups " + Arrays.toString(array));
}
return array;
}
private static MultiKey<String> getPropertiesContributed(EventType deltaEventType, String[] allPropertiesSorted) {
TreeSet<String> props = new TreeSet<String>();
for (String property : deltaEventType.getPropertyNames())
{
for (String propInAll : allPropertiesSorted)
{
if (propInAll.equals(property))
{
props.add(property);
break;
}
}
}
return new MultiKey<String>(props.toArray(new String[props.size()]));
}
/**
* Copy an sort the input array.
* @param input to sort
* @return sorted copied array
*/
protected static String[] copyAndSort(String[] input)
{
String[] result = new String[input.length];
System.arraycopy(input, 0, result, 0, input.length);
Arrays.sort(result);
return result;
}
/**
* Return getters for property names.
* @param eventType type to get getters from
* @param propertyNames names to get
* @return getters
*/
public static EventPropertyGetter[] getGetters(EventType eventType, String[] propertyNames)
{
EventPropertyGetter[] getters = new EventPropertyGetter[propertyNames.length];
for (int i = 0; i < getters.length; i++)
{
getters[i] = eventType.getGetter(propertyNames[i]);
}
return getters;
}
/**
* Remove from values all removeValues and build a unique sorted result array.
* @param values to consider
* @param removeValues values to remove from values
* @return sorted unique
*/
protected static String[] uniqueExclusiveSort(String[] values, String[] removeValues)
{
Set<String> unique = new HashSet<String>();
unique.addAll(Arrays.asList(values));
for (String removeValue : removeValues)
{
unique.remove(removeValue);
}
String[] uniqueArr = unique.toArray(new String[unique.size()]);
Arrays.sort(uniqueArr);
return uniqueArr;
}
public static PropertyAccessException getMismatchException(Method method, Object object, ClassCastException e) {
return getMismatchException(method.getDeclaringClass(), object, e);
}
public static PropertyAccessException getMismatchException(Field field, Object object, ClassCastException e) {
return getMismatchException(field.getDeclaringClass(), object, e);
}
public static PropertyAccessException getInvocationTargetException(Method method, InvocationTargetException e) {
Class declaring = method.getDeclaringClass();
String message = "Failed to invoke method " + method.getName() + " on class " + JavaClassHelper.getClassNameFullyQualPretty(declaring) + ": " + e.getTargetException().getMessage();
throw new PropertyAccessException(message, e);
}
public static PropertyAccessException getIllegalAccessException(Field field, IllegalAccessException e) {
return getAccessExceptionField(field, e);
}
public static PropertyAccessException getIllegalArgumentException(Field field, IllegalArgumentException e) {
return getAccessExceptionField(field, e);
}
private static PropertyAccessException getAccessExceptionField(Field field, Exception e) {
Class declaring = field.getDeclaringClass();
String message = "Failed to obtain field value for field " + field.getName() + " on class " + JavaClassHelper.getClassNameFullyQualPretty(declaring) + ": " + e.getMessage();
throw new PropertyAccessException(message, e);
}
private static PropertyAccessException getMismatchException(Class declared, Object object, ClassCastException e) {
String classNameExpected = JavaClassHelper.getClassNameFullyQualPretty(declared);
String classNameReceived;
if (object != null) {
classNameReceived = JavaClassHelper.getClassNameFullyQualPretty(object.getClass());
}
else {
classNameReceived = "null";
}
if (classNameExpected.equals(classNameReceived)) {
classNameExpected = JavaClassHelper.getClassNameFullyQualPrettyWithClassloader(declared);
classNameReceived = object != null ? JavaClassHelper.getClassNameFullyQualPrettyWithClassloader(object.getClass()) : "null";
}
String message = "Mismatched getter instance to event bean type, expected " + classNameExpected + " but received " + classNameReceived;
throw new PropertyAccessException(message, e);
}
public static PropertyAccessException getIllegalAccessException(Method method, IllegalAccessException e) {
return getAccessExceptionMethod(method, e);
}
public static PropertyAccessException getIllegalArgumentException(Method method, IllegalArgumentException e) {
return getAccessExceptionMethod(method, e);
}
private static PropertyAccessException getAccessExceptionMethod(Method method, Exception e) {
Class declaring = method.getDeclaringClass();
String message = "Failed to invoke method " + method.getName() + " on class " + JavaClassHelper.getClassNameFullyQualPretty(declaring) + ": " + e.getMessage();
throw new PropertyAccessException(message, e);
}
}