com.espertech.esper.event.EventTypeUtility.java Source code

Java tutorial

Introduction

Here is the source code for com.espertech.esper.event.EventTypeUtility.java

Source

/*
 * *************************************************************************************
 *  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;

import com.espertech.esper.client.*;
import com.espertech.esper.epl.core.EngineImportException;
import com.espertech.esper.epl.core.EngineImportService;
import com.espertech.esper.epl.expression.ExprValidationException;
import com.espertech.esper.epl.parse.ASTFilterSpecHelper;
import com.espertech.esper.epl.spec.ColumnDesc;
import com.espertech.esper.epl.spec.CreateSchemaDesc;
import com.espertech.esper.event.bean.BeanEventPropertyGetter;
import com.espertech.esper.event.bean.BeanEventType;
import com.espertech.esper.event.map.MapEventPropertyGetter;
import com.espertech.esper.event.property.*;
import com.espertech.esper.util.EventRepresentationUtil;
import com.espertech.esper.util.JavaClassHelper;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.util.*;

public class EventTypeUtility {

    private static final Log log = LogFactory.getLog(EventTypeUtility.class);

    public static EventPropertyDescriptor getNestablePropertyDescriptor(EventType target, String propertyName) {
        EventPropertyDescriptor descriptor = target.getPropertyDescriptor(propertyName);
        if (descriptor != null) {
            return descriptor;
        }
        int index = ASTFilterSpecHelper.unescapedIndexOfDot(propertyName);
        if (index == -1) {
            return null;
        }
        // parse, can be an nested property
        Property property = PropertyParser.parse(propertyName, false);
        if (property instanceof PropertyBase) {
            return target.getPropertyDescriptor(((PropertyBase) property).getPropertyNameAtomic());
        }
        if (!(property instanceof NestedProperty)) {
            return null;
        }
        NestedProperty nested = (NestedProperty) property;
        Deque<Property> properties = new ArrayDeque<Property>(nested.getProperties());
        return getNestablePropertyDescriptor(target, properties);
    }

    public static EventPropertyDescriptor getNestablePropertyDescriptor(EventType target, Deque<Property> stack) {

        Property topProperty = stack.removeFirst();
        if (stack.isEmpty()) {
            return target.getPropertyDescriptor(((PropertyBase) topProperty).getPropertyNameAtomic());
        }

        if (!(topProperty instanceof SimpleProperty)) {
            return null;
        }
        SimpleProperty simple = (SimpleProperty) topProperty;

        FragmentEventType fragmentEventType = target.getFragmentType(simple.getPropertyNameAtomic());
        if (fragmentEventType == null || fragmentEventType.getFragmentType() == null) {
            return null;
        }
        return getNestablePropertyDescriptor(fragmentEventType.getFragmentType(), stack);
    }

    public static LinkedHashMap<String, Object> buildType(List<ColumnDesc> columns,
            EventAdapterService eventAdapterService, Set<String> copyFrom, EngineImportService engineImportService)
            throws ExprValidationException {
        LinkedHashMap<String, Object> typing = new LinkedHashMap<String, Object>();
        Set<String> columnNames = new HashSet<String>();
        for (ColumnDesc column : columns) {
            boolean added = columnNames.add(column.getName());
            if (!added) {
                throw new ExprValidationException("Duplicate column name '" + column.getName() + "'");
            }
            Class plain = JavaClassHelper.getClassForSimpleName(column.getType());
            if (plain != null) {
                if (column.isArray()) {
                    plain = Array.newInstance(plain, 0).getClass();
                }
                typing.put(column.getName(), plain);
            } else {
                // try imports first
                Class resolved = null;
                try {
                    resolved = engineImportService.resolveClass(column.getType());
                } catch (EngineImportException e) {
                    // expected
                }

                // resolve from classpath when not found
                if (resolved == null) {
                    try {
                        resolved = JavaClassHelper.getClassForName(column.getType());
                    } catch (ClassNotFoundException e) {
                        // expected
                    }
                }

                // Handle resolved classes here
                if (resolved != null) {
                    if (column.isArray()) {
                        resolved = Array.newInstance(resolved, 0).getClass();
                    }
                    typing.put(column.getName(), resolved);
                }
                // Event types fall into here
                else {
                    if (column.isArray()) {
                        typing.put(column.getName(), column.getType() + "[]");
                    } else {
                        typing.put(column.getName(), column.getType());
                    }
                }
            }
        }

        if (copyFrom != null && !copyFrom.isEmpty()) {
            for (String copyFromName : copyFrom) {
                EventType type = eventAdapterService.getExistsTypeByName(copyFromName);
                if (type == null) {
                    throw new ExprValidationException("Type by name '" + copyFromName + "' could not be located");
                }
                mergeType(typing, type);
            }
        }
        return typing;
    }

    private static void mergeType(Map<String, Object> typing, EventType typeToMerge)
            throws ExprValidationException {
        for (EventPropertyDescriptor prop : typeToMerge.getPropertyDescriptors()) {

            Object existing = typing.get(prop.getPropertyName());

            if (!prop.isFragment()) {
                Class assigned = prop.getPropertyType();
                if (existing != null && existing instanceof Class) {
                    if (JavaClassHelper.getBoxedType((Class) existing) != JavaClassHelper.getBoxedType(assigned)) {
                        throw new ExprValidationException("Type by name '" + typeToMerge.getName()
                                + "' contributes property '" + prop.getPropertyName() + "' defined as '"
                                + JavaClassHelper.getClassNameFullyQualPretty(assigned)
                                + "' which overides the same property of type '"
                                + JavaClassHelper.getClassNameFullyQualPretty((Class) existing) + "'");
                    }
                }
                typing.put(prop.getPropertyName(), prop.getPropertyType());
            } else {
                if (existing != null) {
                    throw new ExprValidationException("Property by name '" + prop.getPropertyName()
                            + "' is defined twice by adding type '" + typeToMerge.getName() + "'");
                }

                FragmentEventType fragment = typeToMerge.getFragmentType(prop.getPropertyName());
                if (fragment == null) {
                    continue;
                }
                if (fragment.isIndexed()) {
                    typing.put(prop.getPropertyName(), new EventType[] { fragment.getFragmentType() });
                } else {
                    typing.put(prop.getPropertyName(), fragment.getFragmentType());
                }
            }
        }
    }

    public static void validateTimestampProperties(EventType eventType, String startTimestampProperty,
            String endTimestampProperty) throws ConfigurationException {

        if (startTimestampProperty != null) {
            if (eventType.getGetter(startTimestampProperty) == null) {
                throw new ConfigurationException(
                        "Declared start timestamp property name '" + startTimestampProperty + "' was not found");
            }
            Class type = eventType.getPropertyType(startTimestampProperty);
            if (!JavaClassHelper.isDatetimeClass(type)) {
                throw new ConfigurationException("Declared start timestamp property '" + startTimestampProperty
                        + "' is expected to return a Date, Calendar or long-typed value but returns '"
                        + type.getName() + "'");
            }
        }

        if (endTimestampProperty != null) {
            if (startTimestampProperty == null) {
                throw new ConfigurationException(
                        "Declared end timestamp property requires that a start timestamp property is also declared");
            }
            if (eventType.getGetter(endTimestampProperty) == null) {
                throw new ConfigurationException(
                        "Declared end timestamp property name '" + endTimestampProperty + "' was not found");
            }
            Class type = eventType.getPropertyType(endTimestampProperty);
            if (!JavaClassHelper.isDatetimeClass(type)) {
                throw new ConfigurationException("Declared end timestamp property '" + endTimestampProperty
                        + "' is expected to return a Date, Calendar or long-typed value but returns '"
                        + type.getName() + "'");
            }
            Class startType = eventType.getPropertyType(startTimestampProperty);
            if (JavaClassHelper.getBoxedType(startType) != JavaClassHelper.getBoxedType(type)) {
                throw new ConfigurationException("Declared end timestamp property '" + endTimestampProperty
                        + "' is expected to have the same property type as the start-timestamp property '"
                        + startTimestampProperty + "'");
            }
        }
    }

    public static boolean isTypeOrSubTypeOf(EventType candidate, EventType superType) {

        if (candidate == superType) {
            return true;
        }

        if (candidate.getSuperTypes() != null) {
            for (Iterator<EventType> it = candidate.getDeepSuperTypes(); it.hasNext();) {
                if (it.next() == superType) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * Determine among the Map-type properties which properties are Bean-type event type names,
     * rewrites these as Class-type instead so that they are configured as native property and do not require wrapping,
     * but may require unwrapping.
     * @param typing properties of map type
     * @param eventAdapterService event adapter service
     * @return compiled properties, same as original unless Bean-type event type names were specified.
     */
    public static Map<String, Object> compileMapTypeProperties(Map<String, Object> typing,
            EventAdapterService eventAdapterService) {
        Map<String, Object> compiled = new LinkedHashMap<String, Object>(typing);
        for (Map.Entry<String, Object> specifiedEntry : typing.entrySet()) {
            Object typeSpec = specifiedEntry.getValue();
            String nameSpec = specifiedEntry.getKey();
            if (!(typeSpec instanceof String)) {
                continue;
            }

            String typeNameSpec = (String) typeSpec;
            boolean isArray = EventTypeUtility.isPropertyArray(typeNameSpec);
            if (isArray) {
                typeNameSpec = EventTypeUtility.getPropertyRemoveArray(typeNameSpec);
            }

            EventType eventType = eventAdapterService.getExistsTypeByName(typeNameSpec);
            if (eventType == null || !(eventType instanceof BeanEventType)) {
                continue;
            }

            BeanEventType beanEventType = (BeanEventType) eventType;
            Class underlyingType = beanEventType.getUnderlyingType();
            if (isArray) {
                underlyingType = JavaClassHelper.getArrayType(underlyingType);
            }
            compiled.put(nameSpec, underlyingType);
        }
        return compiled;
    }

    /**
     * Returns true if the name indicates that the type is an array type.
     * @param name the property name
     * @return true if array type
     */
    public static boolean isPropertyArray(String name) {
        return name.trim().endsWith("[]");
    }

    /**
     * Returns the property name without the array type extension, if present.
     * @param name property name
     * @return property name with removed array extension name
     */
    public static String getPropertyRemoveArray(String name) {
        return name.replaceAll("\\[", "").replaceAll("\\]", "");
    }

    public static PropertySetDescriptor getNestableProperties(Map<String, Object> propertiesToAdd,
            EventAdapterService eventAdapterService, EventTypeNestableGetterFactory factory,
            EventType[] optionalSuperTypes) throws EPException {
        List<String> propertyNameList = new ArrayList<String>();
        List<EventPropertyDescriptor> propertyDescriptors = new ArrayList<EventPropertyDescriptor>();
        Map<String, Object> nestableTypes = new LinkedHashMap<String, Object>();
        Map<String, PropertySetDescriptorItem> propertyItems = new HashMap<String, PropertySetDescriptorItem>();

        // handle super-types first, such that the order of properties is well-defined from super-type to subtype
        if (optionalSuperTypes != null) {
            for (int i = 0; i < optionalSuperTypes.length; i++) {
                BaseNestableEventType superType = (BaseNestableEventType) optionalSuperTypes[i];
                for (String propertyName : superType.getPropertyNames()) {
                    if (nestableTypes.containsKey(propertyName)) {
                        continue;
                    }
                    propertyNameList.add(propertyName);
                }
                for (EventPropertyDescriptor descriptor : superType.getPropertyDescriptors()) {
                    if (nestableTypes.containsKey(descriptor.getPropertyName())) {
                        continue;
                    }
                    propertyDescriptors.add(descriptor);
                }

                propertyItems.putAll(superType.propertyItems);
                nestableTypes.putAll(superType.nestableTypes);
            }
        }

        nestableTypes.putAll(propertiesToAdd);

        // Initialize getters and names array: at this time we do not care about nested types,
        // these are handled at the time someone is asking for them
        for (Map.Entry<String, Object> entry : propertiesToAdd.entrySet()) {
            if (!(entry.getKey() instanceof String)) {
                throw new EPException("Invalid type configuration: property name is not a String-type value");
            }
            String name = entry.getKey();

            // handle types that are String values
            if (entry.getValue() instanceof String) {
                String value = entry.getValue().toString().trim();
                Class clazz = JavaClassHelper.getPrimitiveClassForName(value);
                if (clazz != null) {
                    entry.setValue(clazz);
                }
            }

            if (entry.getValue() instanceof Class) {
                Class classType = (Class) entry.getValue();

                boolean isArray = classType.isArray();
                Class componentType = null;
                if (isArray) {
                    componentType = classType.getComponentType();
                }
                boolean isMapped = JavaClassHelper.isImplementsInterface(classType, Map.class);
                if (isMapped) {
                    componentType = Object.class; // Cannot determine the type at runtime
                }
                boolean isFragment = JavaClassHelper.isFragmentableType(classType);
                BeanEventType nativeFragmentType = null;
                FragmentEventType fragmentType = null;
                if (isFragment) {
                    fragmentType = EventBeanUtility.createNativeFragmentType(classType, null, eventAdapterService);
                    if (fragmentType != null) {
                        nativeFragmentType = (BeanEventType) fragmentType.getFragmentType();
                    } else {
                        isFragment = false;
                    }
                }
                EventPropertyGetter getter = factory.getGetterProperty(name, nativeFragmentType,
                        eventAdapterService);
                EventPropertyDescriptor descriptor = new EventPropertyDescriptor(name, classType, componentType,
                        false, false, isArray, isMapped, isFragment);
                PropertySetDescriptorItem item = new PropertySetDescriptorItem(descriptor, classType, getter,
                        fragmentType);
                propertyNameList.add(name);
                propertyDescriptors.add(descriptor);
                propertyItems.put(name, item);
                continue;
            }

            // A null-type is also allowed
            if (entry.getValue() == null) {
                EventPropertyGetter getter = factory.getGetterProperty(name, null, null);
                EventPropertyDescriptor descriptor = new EventPropertyDescriptor(name, null, null, false, false,
                        false, false, false);
                PropertySetDescriptorItem item = new PropertySetDescriptorItem(descriptor, null, getter, null);
                propertyNameList.add(name);
                propertyDescriptors.add(descriptor);
                propertyItems.put(name, item);
                continue;
            }

            // Add Map itself as a property
            if (entry.getValue() instanceof Map) {
                EventPropertyGetter getter = factory.getGetterProperty(name, null, null);
                EventPropertyDescriptor descriptor = new EventPropertyDescriptor(name, Map.class, null, false,
                        false, false, true, false);
                PropertySetDescriptorItem item = new PropertySetDescriptorItem(descriptor, Map.class, getter, null);
                propertyNameList.add(name);
                propertyDescriptors.add(descriptor);
                propertyItems.put(name, item);
                continue;
            }

            if (entry.getValue() instanceof EventType) {
                // Add EventType itself as a property
                EventPropertyGetter getter = factory.getGetterEventBean(name);
                EventType eventType = (EventType) entry.getValue();
                EventPropertyDescriptor descriptor = new EventPropertyDescriptor(name,
                        eventType.getUnderlyingType(), null, false, false, false, false, true);
                FragmentEventType fragmentEventType = new FragmentEventType(eventType, false, false);
                PropertySetDescriptorItem item = new PropertySetDescriptorItem(descriptor,
                        eventType.getUnderlyingType(), getter, fragmentEventType);
                propertyNameList.add(name);
                propertyDescriptors.add(descriptor);
                propertyItems.put(name, item);
                continue;
            }

            if (entry.getValue() instanceof EventType[]) {
                // Add EventType array itself as a property, type is expected to be first array element
                EventType eventType = ((EventType[]) entry.getValue())[0];
                Object prototypeArray = Array.newInstance(eventType.getUnderlyingType(), 0);
                EventPropertyGetter getter = factory.getGetterEventBeanArray(name, eventType);
                EventPropertyDescriptor descriptor = new EventPropertyDescriptor(name, prototypeArray.getClass(),
                        eventType.getUnderlyingType(), false, false, true, false, true);
                FragmentEventType fragmentEventType = new FragmentEventType(eventType, true, false);
                PropertySetDescriptorItem item = new PropertySetDescriptorItem(descriptor,
                        prototypeArray.getClass(), getter, fragmentEventType);
                propertyNameList.add(name);
                propertyDescriptors.add(descriptor);
                propertyItems.put(name, item);
                continue;
            }

            if (entry.getValue() instanceof String) {
                String propertyName = entry.getValue().toString();
                boolean isArray = EventTypeUtility.isPropertyArray(propertyName);
                if (isArray) {
                    propertyName = EventTypeUtility.getPropertyRemoveArray(propertyName);
                }

                // Add EventType itself as a property
                EventType eventType = eventAdapterService.getExistsTypeByName(propertyName);
                if (!(eventType instanceof BaseNestableEventType)) {
                    throw new EPException(
                            "Nestable type configuration encountered an unexpected property type name '"
                                    + entry.getValue() + "' for property '" + name
                                    + "', expected java.lang.Class or java.util.Map or the name of a previously-declared Map or ObjectArray type");
                }

                Class underlyingType = eventType.getUnderlyingType();
                if (isArray) {
                    underlyingType = Array.newInstance(underlyingType, 0).getClass();
                }
                EventPropertyGetter getter;
                if (!isArray) {
                    getter = factory.getGetterBeanNested(name, eventType, eventAdapterService);
                } else {
                    getter = factory.getGetterBeanNestedArray(name, eventType, eventAdapterService);
                }
                EventPropertyDescriptor descriptor = new EventPropertyDescriptor(name, underlyingType, null, false,
                        false, isArray, false, true);
                FragmentEventType fragmentEventType = new FragmentEventType(eventType, isArray, false);
                PropertySetDescriptorItem item = new PropertySetDescriptorItem(descriptor, underlyingType, getter,
                        fragmentEventType);
                propertyNameList.add(name);
                propertyDescriptors.add(descriptor);
                propertyItems.put(name, item);
                continue;
            }

            generateExceptionNestedProp(name, entry.getValue());
        }

        return new PropertySetDescriptor(propertyNameList, propertyDescriptors, propertyItems, nestableTypes);
    }

    private static void generateExceptionNestedProp(String name, Object value) throws EPException {
        String clazzName = (value == null) ? "null" : value.getClass().getSimpleName();
        throw new EPException("Nestable type configuration encountered an unexpected property type of '" + clazzName
                + "' for property '" + name
                + "', expected java.lang.Class or java.util.Map or the name of a previously-declared Map or ObjectArray type");
    }

    public static Class getNestablePropertyType(String propertyName,
            Map<String, PropertySetDescriptorItem> simplePropertyTypes, Map<String, Object> nestableTypes,
            EventAdapterService eventAdapterService) {
        PropertySetDescriptorItem item = simplePropertyTypes.get(ASTFilterSpecHelper.unescapeDot(propertyName));
        if (item != null) {
            return item.getSimplePropertyType();
        }

        // see if this is a nested property
        int index = ASTFilterSpecHelper.unescapedIndexOfDot(propertyName);
        if (index == -1) {
            // dynamic simple property
            if (propertyName.endsWith("?")) {
                return Object.class;
            }

            // parse, can be an indexed property
            Property property;
            try {
                property = PropertyParser.parse(propertyName, false);
            } catch (Exception ex) {
                // cannot parse property, return type
                PropertySetDescriptorItem propitem = simplePropertyTypes.get(propertyName);
                if (propitem != null) {
                    return propitem.getSimplePropertyType();
                }
                return null;
            }

            if (property instanceof IndexedProperty) {
                IndexedProperty indexedProp = (IndexedProperty) property;
                Object type = nestableTypes.get(indexedProp.getPropertyNameAtomic());
                if (type == null) {
                    return null;
                } else if (type instanceof EventType[]) {
                    return ((EventType[]) type)[0].getUnderlyingType();
                } else if (type instanceof String) {
                    String propTypeName = type.toString();
                    boolean isArray = EventTypeUtility.isPropertyArray(propTypeName);
                    if (isArray) {
                        propTypeName = EventTypeUtility.getPropertyRemoveArray(propTypeName);
                    }
                    EventType innerType = eventAdapterService.getExistsTypeByName(propTypeName);
                    return innerType.getUnderlyingType();
                }
                if (!(type instanceof Class)) {
                    return null;
                }
                if (!((Class) type).isArray()) {
                    return null;
                }
                // its an array
                return ((Class) type).getComponentType();
            } else if (property instanceof MappedProperty) {
                MappedProperty mappedProp = (MappedProperty) property;
                Object type = nestableTypes.get(mappedProp.getPropertyNameAtomic());
                if (type == null) {
                    return null;
                }
                if (type instanceof Class) {
                    if (JavaClassHelper.isImplementsInterface((Class) type, Map.class)) {
                        return Object.class;
                    }
                }
                return null;
            } else {
                return null;
            }
        }

        // Map event types allow 2 types of properties inside:
        //   - a property that is a Java object is interrogated via bean property getters and BeanEventType
        //   - a property that is a Map itself is interrogated via map property getters
        // The property getters therefore act on

        // Take apart the nested property into a map key and a nested value class property name
        String propertyMap = ASTFilterSpecHelper.unescapeDot(propertyName.substring(0, index));
        String propertyNested = propertyName.substring(index + 1, propertyName.length());
        boolean isRootedDynamic = false;

        // If the property is dynamic, remove the ? since the property type is defined without
        if (propertyMap.endsWith("?")) {
            propertyMap = propertyMap.substring(0, propertyMap.length() - 1);
            isRootedDynamic = true;
        }

        Object nestedType = nestableTypes.get(propertyMap);
        if (nestedType == null) {
            // parse, can be an indexed property
            Property property = PropertyParser.parse(propertyMap, false);
            if (property instanceof IndexedProperty) {
                IndexedProperty indexedProp = (IndexedProperty) property;
                Object type = nestableTypes.get(indexedProp.getPropertyNameAtomic());
                if (type == null) {
                    return null;
                }
                // handle map-in-map case
                if (type instanceof String) {
                    String propTypeName = type.toString();
                    boolean isArray = EventTypeUtility.isPropertyArray(propTypeName);
                    if (isArray) {
                        propTypeName = EventTypeUtility.getPropertyRemoveArray(propTypeName);
                    }
                    EventType innerType = eventAdapterService.getExistsTypeByName(propTypeName);
                    if (!(innerType instanceof BaseNestableEventType)) {
                        return null;
                    }
                    return innerType.getPropertyType(propertyNested);
                }
                // handle eventtype[] in map
                else if (type instanceof EventType[]) {
                    EventType innerType = ((EventType[]) type)[0];
                    return innerType.getPropertyType(propertyNested);
                }
                // handle array class in map case
                else {
                    if (!(type instanceof Class)) {
                        return null;
                    }
                    if (!((Class) type).isArray()) {
                        return null;
                    }
                    Class componentType = ((Class) type).getComponentType();
                    EventType nestedEventType = eventAdapterService.addBeanType(componentType.getName(),
                            componentType, false, false, false);
                    return nestedEventType.getPropertyType(propertyNested);
                }
            } else if (property instanceof MappedProperty) {
                return null; // Since no type information is available for the property
            } else {
                return null;
            }
        }

        // If there is a map value in the map, return the Object value if this is a dynamic property
        if (nestedType == Map.class) {
            Property prop = PropertyParser.parse(propertyNested, isRootedDynamic);
            return prop.getPropertyTypeMap(null, eventAdapterService); // we don't have a definition of the nested props
        } else if (nestedType instanceof Map) {
            Property prop = PropertyParser.parse(propertyNested, isRootedDynamic);
            Map nestedTypes = (Map) nestedType;
            return prop.getPropertyTypeMap(nestedTypes, eventAdapterService);
        } else if (nestedType instanceof Class) {
            Class simpleClass = (Class) nestedType;
            if (JavaClassHelper.isJavaBuiltinDataType(simpleClass)) {
                return null;
            }
            EventType nestedEventType = eventAdapterService.addBeanType(simpleClass.getName(), simpleClass, false,
                    false, false);
            return nestedEventType.getPropertyType(propertyNested);
        } else if (nestedType instanceof EventType) {
            EventType innerType = (EventType) nestedType;
            return innerType.getPropertyType(propertyNested);
        } else if (nestedType instanceof EventType[]) {
            return null; // requires indexed property
        } else if (nestedType instanceof String) {
            String nestedName = nestedType.toString();
            boolean isArray = EventTypeUtility.isPropertyArray(nestedName);
            if (isArray) {
                nestedName = EventTypeUtility.getPropertyRemoveArray(nestedName);
            }
            EventType innerType = eventAdapterService.getExistsTypeByName(nestedName);
            if (!(innerType instanceof BaseNestableEventType)) {
                return null;
            }
            return innerType.getPropertyType(propertyNested);
        } else {
            String message = "Nestable map type configuration encountered an unexpected value type of '"
                    + nestedType.getClass() + " for property '" + propertyName
                    + "', expected Class, Map.class or Map<String, Object> as value type";
            throw new PropertyAccessException(message);
        }
    }

    public static EventPropertyGetter getNestableGetter(String propertyName,
            Map<String, PropertySetDescriptorItem> propertyGetters,
            Map<String, EventPropertyGetter> propertyGetterCache, Map<String, Object> nestableTypes,
            EventAdapterService eventAdapterService, EventTypeNestableGetterFactory factory) {
        EventPropertyGetter cachedGetter = propertyGetterCache.get(propertyName);
        if (cachedGetter != null) {
            return cachedGetter;
        }

        String unescapePropName = ASTFilterSpecHelper.unescapeDot(propertyName);
        PropertySetDescriptorItem item = propertyGetters.get(unescapePropName);
        if (item != null) {
            EventPropertyGetter getter = item.getPropertyGetter();
            propertyGetterCache.put(propertyName, getter);
            return getter;
        }

        // see if this is a nested property
        int index = ASTFilterSpecHelper.unescapedIndexOfDot(propertyName);
        if (index == -1) {
            Property prop = PropertyParser.parse(propertyName, false);
            if (prop instanceof DynamicProperty) {
                EventPropertyGetter getterDyn = factory.getPropertyProvidedGetter(nestableTypes, propertyName, prop,
                        eventAdapterService);
                propertyGetterCache.put(propertyName, getterDyn);
                return getterDyn;
            } else if (prop instanceof IndexedProperty) {
                IndexedProperty indexedProp = (IndexedProperty) prop;
                Object type = nestableTypes.get(indexedProp.getPropertyNameAtomic());
                if (type == null) {
                    return null;
                } else if (type instanceof EventType[]) {
                    EventPropertyGetter getterArr = factory
                            .getGetterIndexedEventBean(indexedProp.getPropertyNameAtomic(), indexedProp.getIndex());
                    propertyGetterCache.put(propertyName, getterArr);
                    return getterArr;
                } else if (type instanceof String) {
                    String nestedTypeName = type.toString();
                    boolean isArray = EventTypeUtility.isPropertyArray(nestedTypeName);
                    if (isArray) {
                        nestedTypeName = EventTypeUtility.getPropertyRemoveArray(nestedTypeName);
                    }
                    EventType innerType = eventAdapterService.getExistsTypeByName(nestedTypeName);
                    if (!(innerType instanceof BaseNestableEventType)) {
                        return null;
                    }
                    EventPropertyGetter typeGetter;
                    if (!isArray) {
                        typeGetter = factory.getGetterBeanNested(indexedProp.getPropertyNameAtomic(), innerType,
                                eventAdapterService);
                    } else {
                        typeGetter = factory.getGetterIndexedUnderlyingArray(indexedProp.getPropertyNameAtomic(),
                                indexedProp.getIndex(), eventAdapterService, innerType);
                    }
                    propertyGetterCache.put(propertyName, typeGetter);
                    return typeGetter;
                }
                // handle map type name in map
                if (!(type instanceof Class)) {
                    return null;
                }
                if (!((Class) type).isArray()) {
                    return null;
                }

                // its an array
                Class componentType = ((Class) type).getComponentType();
                EventPropertyGetter indexedGetter = factory.getGetterIndexedPOJO(
                        indexedProp.getPropertyNameAtomic(), indexedProp.getIndex(), eventAdapterService,
                        componentType);
                propertyGetterCache.put(propertyName, indexedGetter);
                return indexedGetter;
            } else if (prop instanceof MappedProperty) {
                MappedProperty mappedProp = (MappedProperty) prop;
                Object type = nestableTypes.get(mappedProp.getPropertyNameAtomic());
                if (type == null) {
                    return null;
                }
                if (type instanceof Class) {
                    if (JavaClassHelper.isImplementsInterface((Class) type, Map.class)) {
                        return factory.getGetterMappedProperty(mappedProp.getPropertyNameAtomic(),
                                mappedProp.getKey());
                    }
                }
                return null;
            } else {
                return null;
            }
        }

        // Take apart the nested property into a map key and a nested value class property name
        String propertyMap = ASTFilterSpecHelper.unescapeDot(propertyName.substring(0, index));
        String propertyNested = propertyName.substring(index + 1, propertyName.length());
        boolean isRootedDynamic = false;

        // If the property is dynamic, remove the ? since the property type is defined without
        if (propertyMap.endsWith("?")) {
            propertyMap = propertyMap.substring(0, propertyMap.length() - 1);
            isRootedDynamic = true;
        }

        Object nestedType = nestableTypes.get(propertyMap);
        if (nestedType == null) {
            // parse, can be an indexed property
            Property property = PropertyParser.parse(propertyMap, false);
            if (property instanceof IndexedProperty) {
                IndexedProperty indexedProp = (IndexedProperty) property;
                Object type = nestableTypes.get(indexedProp.getPropertyNameAtomic());
                if (type == null) {
                    return null;
                }
                if (type instanceof String) {
                    String nestedTypeName = type.toString();
                    boolean isArray = EventTypeUtility.isPropertyArray(nestedTypeName);
                    if (isArray) {
                        nestedTypeName = EventTypeUtility.getPropertyRemoveArray(nestedTypeName);
                    }
                    EventType innerType = eventAdapterService.getExistsTypeByName(nestedTypeName);
                    if (!(innerType instanceof BaseNestableEventType)) {
                        return null;
                    }
                    EventPropertyGetter typeGetter;
                    if (!isArray) {
                        typeGetter = factory.getGetterNestedEntryBean(propertyMap,
                                innerType.getGetter(propertyNested), innerType, eventAdapterService);
                    } else {
                        typeGetter = factory.getGetterNestedEntryBeanArray(indexedProp.getPropertyNameAtomic(),
                                indexedProp.getIndex(), innerType.getGetter(propertyNested), innerType,
                                eventAdapterService);
                    }
                    propertyGetterCache.put(propertyName, typeGetter);
                    return typeGetter;
                } else if (type instanceof EventType[]) {
                    EventType componentType = ((EventType[]) type)[0];
                    final EventPropertyGetter nestedGetter = componentType.getGetter(propertyNested);
                    if (nestedGetter == null) {
                        return null;
                    }
                    EventPropertyGetter typeGetter = factory.getGetterIndexedEntryEventBeanArrayElement(
                            indexedProp.getPropertyNameAtomic(), indexedProp.getIndex(), nestedGetter);
                    propertyGetterCache.put(propertyName, typeGetter);
                    return typeGetter;
                } else {
                    if (!(type instanceof Class)) {
                        return null;
                    }
                    if (!((Class) type).isArray()) {
                        return null;
                    }
                    Class componentType = ((Class) type).getComponentType();
                    EventType nestedEventType = eventAdapterService.addBeanType(componentType.getName(),
                            componentType, false, false, false);

                    final BeanEventPropertyGetter nestedGetter = (BeanEventPropertyGetter) nestedEventType
                            .getGetter(propertyNested);
                    if (nestedGetter == null) {
                        return null;
                    }
                    Class propertyTypeGetter = nestedEventType.getPropertyType(propertyNested);
                    // construct getter for nested property
                    EventPropertyGetter indexGetter = factory.getGetterIndexedEntryPOJO(
                            indexedProp.getPropertyNameAtomic(), indexedProp.getIndex(), nestedGetter,
                            eventAdapterService, propertyTypeGetter);
                    propertyGetterCache.put(propertyName, indexGetter);
                    return indexGetter;
                }
            } else if (property instanceof MappedProperty) {
                return null; // Since no type information is available for the property
            } else {
                return null;
            }
        }

        // The map contains another map, we resolve the property dynamically
        if (nestedType == Map.class) {
            Property prop = PropertyParser.parse(propertyNested, isRootedDynamic);
            MapEventPropertyGetter getterNestedMap = prop.getGetterMap(null, eventAdapterService);
            if (getterNestedMap == null) {
                return null;
            }
            EventPropertyGetter mapGetter = factory.getGetterNestedMapProp(propertyMap, getterNestedMap);
            propertyGetterCache.put(propertyName, mapGetter);
            return mapGetter;
        } else if (nestedType instanceof Map) {
            Property prop = PropertyParser.parse(propertyNested, isRootedDynamic);
            Map nestedTypes = (Map) nestedType;
            MapEventPropertyGetter getterNestedMap = prop.getGetterMap(nestedTypes, eventAdapterService);
            if (getterNestedMap == null) {
                return null;
            }
            EventPropertyGetter mapGetter = factory.getGetterNestedMapProp(propertyMap, getterNestedMap);
            propertyGetterCache.put(propertyName, mapGetter);
            return mapGetter;
        } else if (nestedType instanceof Class) {
            // ask the nested class to resolve the property
            Class simpleClass = (Class) nestedType;
            EventType nestedEventType = eventAdapterService.addBeanType(simpleClass.getName(), simpleClass, false,
                    false, false);
            final BeanEventPropertyGetter nestedGetter = (BeanEventPropertyGetter) nestedEventType
                    .getGetter(propertyNested);
            if (nestedGetter == null) {
                return null;
            }
            Class nestedReturnType = nestedEventType.getPropertyType(propertyNested);

            // construct getter for nested property
            EventPropertyGetter getter = factory.getGetterNestedPOJOProp(propertyMap, nestedGetter,
                    eventAdapterService, nestedReturnType);
            propertyGetterCache.put(propertyName, getter);
            return getter;
        } else if (nestedType instanceof EventType) {
            // ask the nested class to resolve the property
            EventType innerType = (EventType) nestedType;
            final EventPropertyGetter nestedGetter = innerType.getGetter(propertyNested);
            if (nestedGetter == null) {
                return null;
            }

            // construct getter for nested property
            EventPropertyGetter getter = factory.getGetterNestedEventBean(propertyMap, nestedGetter);
            propertyGetterCache.put(propertyName, getter);
            return getter;
        } else if (nestedType instanceof EventType[]) {
            EventType[] typeArray = (EventType[]) nestedType;
            EventPropertyGetter beanArrGetter = factory.getGetterEventBeanArray(propertyMap, typeArray[0]);
            propertyGetterCache.put(propertyName, beanArrGetter);
            return beanArrGetter;
        } else if (nestedType instanceof String) {
            String nestedName = nestedType.toString();
            boolean isArray = EventTypeUtility.isPropertyArray(nestedName);
            if (isArray) {
                nestedName = EventTypeUtility.getPropertyRemoveArray(nestedName);
            }
            EventType innerType = eventAdapterService.getExistsTypeByName(nestedName);
            if (!(innerType instanceof BaseNestableEventType)) {
                return null;
            }
            EventPropertyGetter innerGetter = innerType.getGetter(propertyNested);
            if (innerGetter == null) {
                return null;
            }
            EventPropertyGetter outerGetter;
            if (!isArray) {
                outerGetter = factory.getGetterNestedEntryBean(propertyMap, innerGetter, innerType,
                        eventAdapterService);
            } else {
                outerGetter = factory.getGetterNestedEntryBeanArray(propertyMap, 0, innerGetter, innerType,
                        eventAdapterService);
            }
            propertyGetterCache.put(propertyName, outerGetter);
            return outerGetter;
        } else {
            String message = "Nestable type configuration encountered an unexpected value type of '"
                    + nestedType.getClass() + " for property '" + propertyName
                    + "', expected Class, Map.class or Map<String, Object> as value type";
            throw new PropertyAccessException(message);
        }
    }

    public static LinkedHashMap<String, Object> validateObjectArrayDef(String[] propertyNames,
            Object[] propertyTypes) {
        if (propertyNames.length != propertyTypes.length) {
            throw new ConfigurationException("Number of property names and property types do not match, found "
                    + propertyNames.length + " property names and " + propertyTypes.length + " property types");
        }

        // validate property names for no-duplicates
        Set<String> propertyNamesSet = new HashSet<String>();
        LinkedHashMap<String, Object> propertyTypesMap = new LinkedHashMap<String, Object>();
        for (int i = 0; i < propertyNames.length; i++) {
            String propertyName = propertyNames[i];
            if (propertyNamesSet.contains(propertyName)) { // duplicate prop check
                throw new ConfigurationException(
                        "Property '" + propertyName + "' is listed twice in the type definition");
            }
            propertyNamesSet.add(propertyName);
            propertyTypesMap.put(propertyName, propertyTypes[i]);
        }
        return propertyTypesMap;
    }

    public static EventType createNonVariantType(boolean isAnonymous, CreateSchemaDesc spec,
            Annotation[] annotations, ConfigurationInformation configSnapshot,
            EventAdapterService eventAdapterService, EngineImportService engineImportService)
            throws ExprValidationException {
        if (spec.getAssignedType() == CreateSchemaDesc.AssignedType.VARIANT) {
            throw new IllegalStateException("Variant type is not allowed in this context");
        }

        EventType eventType;
        if (spec.getTypes().isEmpty()) {
            boolean useMap = EventRepresentationUtil.isMap(annotations, configSnapshot, spec.getAssignedType());
            Map<String, Object> typing = EventTypeUtility.buildType(spec.getColumns(), eventAdapterService,
                    spec.getCopyFrom(), engineImportService);
            Map<String, Object> compiledTyping = EventTypeUtility.compileMapTypeProperties(typing,
                    eventAdapterService);

            ConfigurationEventTypeWithSupertype config;
            if (useMap) {
                config = new ConfigurationEventTypeMap();
            } else {
                config = new ConfigurationEventTypeObjectArray();
            }
            if (spec.getInherits() != null) {
                config.getSuperTypes().addAll(spec.getInherits());
            }
            config.setStartTimestampPropertyName(spec.getStartTimestampProperty());
            config.setEndTimestampPropertyName(spec.getEndTimestampProperty());

            if (useMap) {
                if (isAnonymous) {
                    eventType = eventAdapterService.createAnonymousMapType(spec.getSchemaName(), compiledTyping);
                } else {
                    eventType = eventAdapterService.addNestableMapType(spec.getSchemaName(), compiledTyping,
                            (ConfigurationEventTypeMap) config, false, false, true, false, false);
                }
            } else {
                if (isAnonymous) {
                    eventType = eventAdapterService.createAnonymousObjectArrayType(spec.getSchemaName(),
                            compiledTyping);
                } else {
                    eventType = eventAdapterService.addNestableObjectArrayType(spec.getSchemaName(), compiledTyping,
                            (ConfigurationEventTypeObjectArray) config, false, false, true, false, false);
                }
            }
        } else {
            // Java Object/Bean/POJO type definition
            if (spec.getCopyFrom() != null && !spec.getCopyFrom().isEmpty()) {
                throw new ExprValidationException("Copy-from types are not allowed with class-provided types");
            }
            if (spec.getTypes().size() != 1) {
                throw new IllegalStateException("Multiple types provided");
            }
            String typeName = spec.getTypes().iterator().next();
            try {
                // use the existing configuration, if any, possibly adding the start and end timestamps
                ConfigurationEventTypeLegacy config = eventAdapterService.getClassLegacyConfigs(typeName);
                if (spec.getStartTimestampProperty() != null || spec.getEndTimestampProperty() != null) {
                    if (config == null) {
                        config = new ConfigurationEventTypeLegacy();
                    }
                    config.setStartTimestampPropertyName(spec.getStartTimestampProperty());
                    config.setEndTimestampPropertyName(spec.getEndTimestampProperty());
                    eventAdapterService.setClassLegacyConfigs(Collections.singletonMap(typeName, config));
                }
                if (isAnonymous) {
                    String className = spec.getTypes().iterator().next();
                    Class clazz;
                    try {
                        clazz = engineImportService.resolveClass(className);
                    } catch (EngineImportException e) {
                        throw new ExprValidationException(
                                "Failed to resolve class '" + className + "': " + e.getMessage(), e);
                    }
                    eventType = eventAdapterService.createAnonymousBeanType(spec.getSchemaName(), clazz);
                } else {
                    eventType = eventAdapterService.addBeanType(spec.getSchemaName(),
                            spec.getTypes().iterator().next(), false, false, false, true);
                }
            } catch (EventAdapterException ex) {
                Class clazz;
                try {
                    clazz = engineImportService.resolveClass(typeName);
                    if (isAnonymous) {
                        eventType = eventAdapterService.createAnonymousBeanType(spec.getSchemaName(), clazz);
                    } else {
                        eventType = eventAdapterService.addBeanType(spec.getSchemaName(), clazz, false, false,
                                true);
                    }
                } catch (EngineImportException e) {
                    log.debug("Engine import failed to resolve event type '" + typeName + "'");
                    throw ex;
                }
            }
        }
        return eventType;
    }

    public static WriteablePropertyDescriptor findWritable(String propertyName,
            Set<WriteablePropertyDescriptor> writables) {
        for (WriteablePropertyDescriptor writable : writables) {
            if (writable.getPropertyName().equals(propertyName)) {
                return writable;
            }
        }
        return null;
    }

    public static TimestampPropertyDesc validatedDetermineTimestampProps(EventType type, String startProposed,
            String endProposed, EventType[] superTypes) throws EPException {
        // determine start&end timestamp as inherited
        String startTimestampPropertyName = startProposed;
        String endTimestampPropertyName = endProposed;

        if (superTypes != null && superTypes.length > 0) {
            for (EventType superType : superTypes) {
                if (superType.getStartTimestampPropertyName() != null) {
                    if (startTimestampPropertyName != null
                            && !startTimestampPropertyName.equals(superType.getStartTimestampPropertyName())) {
                        throw getExceptionTimestampInherited("start", startTimestampPropertyName,
                                superType.getStartTimestampPropertyName(), superType);
                    }
                    startTimestampPropertyName = superType.getStartTimestampPropertyName();
                }
                if (superType.getEndTimestampPropertyName() != null) {
                    if (endTimestampPropertyName != null
                            && !endTimestampPropertyName.equals(superType.getEndTimestampPropertyName())) {
                        throw getExceptionTimestampInherited("end", endTimestampPropertyName,
                                superType.getEndTimestampPropertyName(), superType);
                    }
                    endTimestampPropertyName = superType.getEndTimestampPropertyName();
                }
            }
        }

        validateTimestampProperties(type, startTimestampPropertyName, endTimestampPropertyName);
        return new TimestampPropertyDesc(startTimestampPropertyName, endTimestampPropertyName);
    }

    private static EPException getExceptionTimestampInherited(String tstype, String firstName, String secondName,
            EventType superType) {
        String message = "Event type declares " + tstype + " timestamp as property '" + firstName
                + "' however inherited event type '" + superType.getName() + "' declares " + tstype
                + " timestamp as property '" + secondName + "'";
        return new EPException(message);
    }

    public static class TimestampPropertyDesc {
        private final String start;
        private final String end;

        public TimestampPropertyDesc(String start, String end) {
            this.start = start;
            this.end = end;
        }

        public String getStart() {
            return start;
        }

        public String getEnd() {
            return end;
        }
    }
}