org.lwes.db.EventTemplateDB.java Source code

Java tutorial

Introduction

Here is the source code for org.lwes.db.EventTemplateDB.java

Source

/*======================================================================*
 * Copyright (c) 2008, Yahoo! Inc. All rights reserved.                 *
 *                                                                      *
 * Licensed under the New BSD License (the "License"); you may not use  *
 * this file except in compliance with the License.  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. See accompanying LICENSE file.        *
 *======================================================================*/

package org.lwes.db;

import java.io.File;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.lwes.AttributeRequiredException;
import org.lwes.BaseType;
import org.lwes.Event;
import org.lwes.EventAttributeSizeException;
import org.lwes.EventSystemException;
import org.lwes.FieldAccessor;
import org.lwes.FieldType;
import org.lwes.NoSuchAttributeException;
import org.lwes.NoSuchAttributeTypeException;
import org.lwes.NoSuchEventException;
import org.lwes.ValidationExceptions;
import org.lwes.util.IPAddress;

/**
 * Provides type checking for the event system. Also provides a place for
 * globally accessible information.
 *
 * @author Anthony Molinaro
 * @author Michael P. Lum
 * @author Frank Maritato
 */
public class EventTemplateDB {

    private static transient Log log = LogFactory.getLog(EventTemplateDB.class);
    /**
     * the meta event info inherent to every event
     */
    private static final String META_EVENT_INFO = "MetaEventInfo";

    /**
     * esfFile for this event system.
     */
    private File esfFile = null;
    private InputStream esfInputStream = null;

    /**
     * System Attributes
     */
    private Map<String, Map<String, BaseType>> events = null;
    private Map<String, String> eventComments = null;
    private Map<FieldType, BaseType> knownTypes = null;
    private Map<String, BaseType> reservedWords = null;
    private String metaInfoComments = null;

    // To reduce memory footprint, set to false
    private boolean storeComments = true;

    /**
     * This is the EventTemplateDB constructor.
     */
    public EventTemplateDB() {
        events = new ConcurrentHashMap<String, Map<String, BaseType>>();
        eventComments = new ConcurrentHashMap<String, String>();
        knownTypes = new ConcurrentHashMap<FieldType, BaseType>();
        reservedWords = new ConcurrentHashMap<String, BaseType>();
        for (FieldType type : FieldType.values()) {
            knownTypes.put(type, new BaseType(type, type.getDefaultValue()));
        }
    }

    public boolean isStoreComments() {
        return storeComments;
    }

    public void setStoreComments(boolean storeComments) {
        this.storeComments = storeComments;
    }

    /**
     * Sets the Event Specification file for this system
     *
     * @param anEsfFile the ESF file for this system.
     */
    public void setESFFile(File anEsfFile) {
        esfFile = anEsfFile;
    }

    /**
     * Gets the ESF file, in case you want to look at it
     *
     * @return the ESF file used in this system.
     */
    public File getESFFile() {
        return esfFile;
    }

    /**
     * Sets the Event Specification file as an InputStream
     *
     * @param esfInputStream the InputStream representing an ESF file
     */
    public void setESFInputStream(InputStream esfInputStream) {
        this.esfInputStream = esfInputStream;
    }

    /**
     * Ges the ESF InputStream
     *
     * @return the ESF InputStream used in the system
     */
    public InputStream getESFInputStream() {
        return esfInputStream;
    }

    /**
     * Initializes the EventTemplateDB, assumes that setESFFile() has been
     * called.
     *
     * @return true if the EventTemplateDB initializes correctly; false if it
     *         does not. false means the event system is unable to perform validation
     */
    public synchronized boolean initialize() {
        /*
           * Call the parser to parse the file. Any errors cause the
           * initialization to fail.
           */
        ESFParser parser;
        try {
            if (getESFInputStream() != null) {
                parser = new ESFParser(getESFInputStream(), "UTF-8");
            } else if (getESFFile() != null) {
                parser = new ESFParser(new java.io.FileInputStream(getESFFile()), "UTF-8");
            } else {
                return false;
            }

            parser.setEventTemplateDB(this);
            parser.eventlist();
        } catch (java.io.FileNotFoundException e) {
            log.warn("File not found ", e);

            /*
            * treat this as just a warning and allow things to continue, this
            * allows an empty EventTemplateDB to work when type checking is
            * turned off
            */
        } catch (ParseException e) {
            log.error("Parser error in ESF file " + getESFFile(), e);
            return false;
        } catch (Exception e) {
            log.error("Error parsing ESF file " + getESFFile(), e);
            /* catch IO, NPEs and other exceptions but still continue */
            return false;
        }

        return true;
    }

    /**
     * Add an Event to the EventTemplateDB
     *
     * @param anEventName the name of the Event to add
     * @return true if the event was added, false if it was not
     */
    public boolean addEvent(String anEventName) {
        return addEvent(anEventName, null);
    }

    /**
     * Add an Event to the EventTemplateDB
     *
     * @param anEventName the name of the Event to add
     * @param comment     a comment associated with the event
     * @return true if the event was added, false if it was not
     */
    public synchronized boolean addEvent(String anEventName, String comment) {
        if (anEventName == null) {
            return false;
        }

        try {
            if (anEventName.equals(META_EVENT_INFO)) {
                addMetaComment(comment);
                return true;
            }

            if (events.containsKey(anEventName)) {
                if (log.isInfoEnabled()) {
                    log.info("Event " + anEventName + " already exists in event DB");
                }
                return false;
            }

            Map<String, BaseType> evtHash = new ConcurrentHashMap<String, BaseType>();
            if (!(reservedWords.isEmpty())) {
                /* insert reserved words into new event */
                for (String key : reservedWords.keySet()) {
                    if (key != null) {
                        evtHash.put(key, reservedWords.get(key));
                    }
                }
            }

            events.put(anEventName, evtHash);
            if (comment != null && storeComments) {
                eventComments.put(anEventName, comment);
            }
        } catch (Exception e) {
            log.warn("Error adding event to EventTemplateDB", e);
        }

        return true;
    }

    @Deprecated
    public synchronized boolean addEventAttribute(String anEventName, String anAttributeName,
            String anAttributeType) {
        return addEventAttribute(anEventName, anAttributeName, anAttributeType, -1, false);
    }

    @Deprecated
    public synchronized boolean addEventAttribute(String anEventName, String anAttributeName,
            String anAttributeType, Integer size, boolean required) {
        return addEventAttribute(anEventName, anAttributeName, anAttributeType, size, required, null);
    }

    public synchronized boolean addEventAttribute(String anEventName, String anAttributeName,
            FieldType anAttributeType, Integer size, boolean required, Object defaultValue) {
        return addEventAttribute(anEventName, anAttributeName, anAttributeType, size, required, defaultValue, null);

    }

    /**
     * Add an attribute to an Event in the EventTemplateDB
     *
     * @param anEventName     the name of the event to add this attribute to
     * @param anAttributeName the name of the attribute to add
     * @param anAttributeType the type of the attribute, should be the name of the type
     *                        given in the ESF Specification.
     * @param size            The size restriction for this attribute
     * @param required        Is this attribute required
     * @return true if the attribute can be added, false if it can not.
     */
    public synchronized boolean addEventAttribute(String anEventName, String anAttributeName,
            FieldType anAttributeType, Integer size, boolean required, Object defaultValue, String comment) {

        if (anEventName == null || anAttributeName == null || anAttributeType == null) {
            return false;
        }

        try {
            if (anEventName.equals(META_EVENT_INFO)) {
                if (checkForType(anAttributeType)) {
                    BaseType bt = knownTypes.get(anAttributeType).cloneBaseType();
                    bt.setRequired(required);
                    bt.setSizeRestriction(size);
                    bt.setComment(comment);
                    if (defaultValue != null) {
                        bt.setDefaultValue(canonicalizeDefaultValue(anEventName, anAttributeName, anAttributeType,
                                defaultValue));
                    }
                    reservedWords.put(anAttributeName, bt);
                    return true;
                } else {
                    if (log.isInfoEnabled()) {
                        log.info("Meta keyword " + anEventName + "." + anAttributeName + " has unknown type "
                                + anAttributeType + ", skipping");
                    }
                    return false;
                }
            }

            if (reservedWords.containsKey(anEventName)) {
                if (log.isWarnEnabled()) {
                    log.warn("Unable to add attribute named " + anAttributeName
                            + "as it is a reserved word, skipping");
                }
                return false;
            }

            if (events.containsKey(anEventName)) {
                Map<String, BaseType> evtHash = events.get(anEventName);
                if (checkForType(anAttributeType)) {
                    BaseType bt = knownTypes.get(anAttributeType).cloneBaseType();
                    bt.setRequired(required);
                    bt.setComment(comment);
                    bt.setSizeRestriction(size);
                    if (defaultValue != null) {
                        bt.setDefaultValue(
                                canonicalizeDefaultValue(anEventName, anAttributeName, bt.getType(), defaultValue));
                    }
                    evtHash.put(anAttributeName, bt);
                    return true;
                } else {
                    if (log.isWarnEnabled()) {
                        log.warn("Type " + anAttributeType + " does not exist for " + anAttributeName
                                + ", skipping");
                    }
                    return false;
                }
            } else {
                if (log.isWarnEnabled()) {
                    log.warn("No such event " + anEventName + ", skipping");
                }
                return false;
            }
        } catch (Exception e) {
            log.error("Error adding attribute " + anAttributeName + " to " + anEventName, e);
            return false;
        }
    }

    /**
     * This method checks the type and range of a default value (from the ESF).
     * It returns the desired form, if allowed.
     *
     * @param type     which controls the desired object type of the value
     * @param esfValue which should be converted to fit 'type'
     * @return a value suitable for storing in a BaseType of this 'type'
     * @throws EventSystemException if the value is not acceptable for the type.
     */
    @SuppressWarnings("cast")
    private Object canonicalizeDefaultValue(String eventName, String attributeName, FieldType type, Object esfValue)
            throws EventSystemException {
        try {
            switch (type) {
            case BOOLEAN:
                return (Boolean) esfValue;
            case BYTE:
                checkRange(eventName, attributeName, esfValue, Byte.MIN_VALUE, Byte.MAX_VALUE);
                return ((Number) esfValue).byteValue();
            case INT16:
                checkRange(eventName, attributeName, esfValue, Short.MIN_VALUE, Short.MAX_VALUE);
                return ((Number) esfValue).shortValue();
            case INT32:
                checkRange(eventName, attributeName, esfValue, Integer.MIN_VALUE, Integer.MAX_VALUE);
                return ((Number) esfValue).intValue();
            case UINT16:
                checkRange(eventName, attributeName, esfValue, 0, 0x10000);
                return ((Number) esfValue).intValue() & 0xffff;
            case UINT32:
                checkRange(eventName, attributeName, esfValue, 0, 0x100000000L);
                return ((Number) esfValue).longValue() & 0xffffffff;
            case FLOAT:
                return ((Number) esfValue).floatValue();
            case DOUBLE:
                return ((Number) esfValue).doubleValue();
            case STRING:
                return ((String) esfValue);
            case INT64: {
                if (esfValue instanceof Long) {
                    return esfValue;
                }
                final BigInteger bi = (BigInteger) esfValue;
                if (bi.compareTo(BigInteger.valueOf(Long.MIN_VALUE)) < 0
                        || bi.compareTo(BigInteger.valueOf(Long.MAX_VALUE)) > 0) {
                    throw new EventSystemException(
                            String.format("Field %s.%s value %s outside allowed range [%d,%d]", eventName,
                                    attributeName, esfValue, Long.MIN_VALUE, Long.MAX_VALUE));
                }
                return bi.longValue();
            }
            case UINT64: {
                if (esfValue instanceof BigInteger) {
                    return esfValue;
                }
                return BigInteger.valueOf(((Number) esfValue).longValue());
            }
            case IPADDR:
                return ((IPAddress) esfValue);
            case BOOLEAN_ARRAY:
            case BYTE_ARRAY:
            case DOUBLE_ARRAY:
            case FLOAT_ARRAY:
            case INT16_ARRAY:
            case INT32_ARRAY:
            case INT64_ARRAY:
            case IP_ADDR_ARRAY:
            case STRING_ARRAY:
            case UINT16_ARRAY:
            case UINT32_ARRAY:
            case UINT64_ARRAY:
            case NBOOLEAN_ARRAY:
            case NBYTE_ARRAY:
            case NDOUBLE_ARRAY:
            case NFLOAT_ARRAY:
            case NINT16_ARRAY:
            case NINT32_ARRAY:
            case NINT64_ARRAY:
            case NSTRING_ARRAY:
            case NUINT16_ARRAY:
            case NUINT32_ARRAY:
            case NUINT64_ARRAY:
                throw new EventSystemException("Unsupported default value type " + type);
            }
            throw new EventSystemException("Unrecognized type " + type + " for value " + esfValue);
        } catch (ClassCastException e) {
            throw new EventSystemException("Type " + type + " had an inappropriate default value " + esfValue);
        }
    }

    private void checkRange(String eventName, String attributeName, Object value, long min, long max)
            throws EventSystemException {
        final Number number = (Number) value;
        if (min <= number.longValue() && number.longValue() <= max) {
            return;
        }
        throw new EventSystemException(String.format("Field %s.%s value %d outside allowed range [%d,%d]",
                eventName, attributeName, value, min, max));
    }

    /**
     * use {@link #addEventAttribute(String, String, FieldType, Integer, boolean, Object)}
     */
    @Deprecated
    public boolean addEventAttribute(String anEventName, String anAttributeName, String anAttributeType,
            Integer size, boolean required, Object defaultValue) {
        return addEventAttribute(anEventName, anAttributeName, FieldType.byName(anAttributeType), size, required,
                defaultValue);
    }

    /**
     * Returns an enumeration of all defined events
     *
     * @return an enumeration of all defined events
     */
    public Enumeration<String> getEventNames() {
        return Collections.enumeration(this.events.keySet());
    }

    public String getEventComment(String event) {
        if (eventComments.containsKey(event)) {
            return eventComments.get(event);
        } else {
            return null;
        }
    }

    public void setEventComment(String event, String comment) {
        if (storeComments) {
            eventComments.put(event, comment);
        }
    }

    /**
     * More useful than getting an Enumeration<String>
     *
     * @return Set<String>
     */
    public Set<String> getEventNameSet() {
        return this.events.keySet();
    }

    /**
     * Returns true if the type given by aTypeName is a valid type in the DB.
     *
     * @param type a type name according to the ESF Specification
     * @return true if the type exists in the DB, false otherwise
     */
    public boolean checkForType(FieldType type) {
        return type == null ? false : knownTypes.containsKey(type);
    }

    public boolean checkForType(String aTypeName) {
        return checkForType(FieldType.byName(aTypeName));
    }

    /**
     * This checks an attribute against a limit on the number of elements in an
     * array, and does not check the number of serialized bytes required to
     * store the value.
     */
    public void checkForSize(String eventName, String attributeName, BaseType attributeValue)
            throws EventAttributeSizeException {

        if (!attributeValue.getType().isArray()) {
            return;
        }

        Map<String, BaseType> evtMap = events.get(eventName);
        if (evtMap == null) {
            log.error("event definition did not exist for event " + eventName);
            return;
        }
        BaseType attrBaseType = evtMap.get(attributeName);
        if (attrBaseType == null) {
            log.error("attribute definition did not exist for attribute " + eventName + "." + attributeName);
            return;
        }

        final int maximumAllowedSize = attrBaseType.getSizeRestriction();
        final int observedSize = Array.getLength(attributeValue.getTypeObject());
        if (log.isTraceEnabled()) {
            log.trace("sizeToCheck: " + observedSize + " size: " + maximumAllowedSize);
        }
        if (maximumAllowedSize > 0 && observedSize > maximumAllowedSize) {
            throw new EventAttributeSizeException(attributeName, observedSize, maximumAllowedSize);
        }
    }

    /**
     * Checks to see if an Event exists in the EventTemplateDB
     *
     * @param anEventName the name of the event to check the existence of
     * @return true if the event with the name <tt>anEventName</tt> exists in
     *         the EventTemplateDB, false otherwise.
     */
    public boolean checkForEvent(String anEventName) {
        if (anEventName == null) {
            return false;
        }
        return events.containsKey(anEventName);
    }

    /**
     * Checks to see if an attribute <tt>anAttributeName</tt> exists for the
     * event <tt>anEventName</tt>
     *
     * @param anEventName     the name of an Event
     * @param anAttributeName the name of an attribute of Event to check
     * @return true if the attribute exists as a member of event, false
     *         otherwise
     */
    public boolean checkForAttribute(String anEventName, String anAttributeName) {
        if (anEventName == null || anAttributeName == null) {
            return false;
        }

        if (checkForEvent(anEventName)) {
            Map<String, BaseType> evtHash = events.get(anEventName);
            return evtHash.containsKey(anAttributeName);
        }
        return false;
    }

    /**
     * Checks to see if the type of an attribute is proper. (i.e. if the given
     * attribute of the given event has the same type assigned to it as the type
     * of the given objects value)
     *
     * @param anEventName      the name of an Event.
     * @param anAttributeName  the name of the attribute whose type is being checked
     * @param anAttributeValue the Object containing the possible value of the attribute.
     * @return true if the event and attribute exist and if the type of the
     *         attribute matches the type assigned to this attribute in the
     *         EventTemplateDB, false otherwise.
     */
    public boolean checkTypeForAttribute(String anEventName, String anAttributeName, Object anAttributeValue) {
        if (anEventName == null || anAttributeName == null || anAttributeValue == null) {
            return false;
        }

        if (checkForAttribute(anEventName, anAttributeName)) {
            Map<String, BaseType> evtHash = events.get(anEventName);
            Object storedTypeObject = evtHash.get(anAttributeName);
            byte type1 = ((BaseType) anAttributeValue).getTypeToken();
            byte type2 = ((BaseType) storedTypeObject).getTypeToken();
            if (type1 == type2) {
                return true;
            }
        }

        return false;
    }

    /**
     * Checks to see if the type of an attribute is proper. (i.e. if the given
     * attribute of the given event has the same type assigned to it as the type
     * given)
     *
     * @param anEventName     the name of an Event.
     * @param anAttributeName the name of the attribute whose type is being checked
     * @param anAttributeType the String containing the possible type value of the
     *                        attribute.
     * @return true if the event and attribute exist and if the type of the
     *         attribute matches the type assigned to this attribute in the
     *         EventTemplateDB, false otherwise.
     */
    public boolean checkTypeForAttribute(String anEventName, String anAttributeName, FieldType anAttributeType) {
        if (anEventName == null || anAttributeName == null || anAttributeType == null) {
            return false;
        }

        if (checkForAttribute(anEventName, anAttributeName)) {
            Map<String, BaseType> evtHash = events.get(anEventName);
            FieldType storedType = evtHash.get(anAttributeName).getType();
            if (log.isDebugEnabled()) {
                log.debug("attr: " + anAttributeName + " stored: " + storedType + " passed in: " + anAttributeType);
            }
            if (anAttributeType == storedType) {
                return true;
            }
        }

        return false;
    }

    /**
     * use {@link #checkTypeForAttribute(String, String, FieldType)}
     */
    @Deprecated
    public boolean checkTypeForAttribute(String anEventName, String anAttributeName, String anAttributeType) {
        return checkTypeForAttribute(anEventName, anAttributeName, FieldType.byName(anAttributeType));
    }

    /**
     * Given an Object which is the attribute value of the attribute
     * <tt>attributeName</tt> of event <tt>eventName</tt>, return the internal
     * representation (i.e. <tt>BaseType</tt>) of this Object
     *
     * @param eventName      the name of an Event.
     * @param attributeName  the name of an attribute of <tt>eventName</tt>
     * @param attributeValue the value of the attribute
     * @return the <tt>BaseType</tt> representation of <tt>attributeValue</tt>
     * @throws NoSuchAttributeTypeException if the type and value are not compatible
     */
    public BaseType getBaseTypeForObjectAttribute(String eventName, String attributeName, Object attributeValue)
            throws NoSuchAttributeTypeException {
        if (eventName == null || attributeName == null || attributeValue == null) {
            return null;
        }

        Map<String, BaseType> evtHash = events.get(eventName);
        BaseType tmpBaseType = evtHash.get(attributeName);
        BaseType retBaseType = tmpBaseType.cloneBaseType();
        retBaseType.setTypeObject(attributeValue);
        return retBaseType;
    }

    /**
     * Returns the base types for this event
     *
     * @param eventName
     * @return a map of event fields to base types
     */
    public Map<String, BaseType> getBaseTypesForEvent(String eventName) {
        if (eventName == null) {
            return null;
        }

        Map<String, BaseType> map = events.get(eventName);

        return map == null ? new ConcurrentHashMap<String, BaseType>() : map;
    }

    /**
     * Parses the string representation of an event attribute into the
     * appropriate objectt.
     *
     * @param anEventName          the name of an Event.
     * @param anAttributeName      the name of the attribute we are parsing
     * @param stringAttributeValue a string representation of the value of the attribute given by
     *                             anAttributeName.
     * @return the object represented by the string
     *         <tt>stringAttributeValue</tt>
     */
    public Object parseAttribute(String anEventName, String anAttributeName, String stringAttributeValue) {
        Object retObject = null;

        if (anEventName == null || anAttributeName == null || stringAttributeValue == null) {
            return null;
        }

        if (log.isTraceEnabled()) {
            log.trace("parseAttribute: " + anEventName + "." + anAttributeName + "=" + stringAttributeValue);
        }

        if (checkForAttribute(anEventName, anAttributeName)) {
            if (log.isTraceEnabled()) {
                log.trace("parseAttribute: passed first if attribute exists");
            }

            Map<String, BaseType> evtHash = events.get(anEventName);
            try {
                BaseType bt = evtHash.get(anAttributeName);
                if (bt == null) {
                    throw new EventSystemException("Null BaseType for " + anAttributeName);
                }

                retObject = bt.parseFromString(stringAttributeValue);
                if (log.isTraceEnabled()) {
                    log.trace("parseAttribute: parsed " + retObject);
                }
            } catch (EventSystemException btpe) {
                log.error("Unable to parseAttribute", btpe);
            }
        }

        if (log.isTraceEnabled()) {
            log.trace("parseAttribute: returning " + retObject);
        }
        return retObject;
    }

    /**
     * Returns a HTML rendering of the EventTemplateDB
     *
     * @return HTML string of the EventTemplateDB
     */
    public String toHtmlString() {
        StringBuffer sb = new StringBuffer();
        sb.append("<table>\n");
        sb.append("<tr><th>" + META_EVENT_INFO + "</th><th>Type</th><th>Name</th></tr>\n");
        for (String key : reservedWords.keySet()) {
            BaseType tv = reservedWords.get(key);
            FieldType type = tv.getType();
            sb.append("<tr><td></td><td>").append(type).append("</td><td>").append(key).append("</td></tr>\n");
        }
        for (String EventKey : events.keySet()) {
            sb.append("<tr><th>").append(EventKey).append("</th><th>Type</th><th>Name</th></tr>\n");
            if (EventKey != null) {
                Map<String, BaseType> event = events.get(EventKey);
                for (Enumeration<String> att = Collections.enumeration(event.keySet()); att.hasMoreElements();) {
                    String key = att.nextElement();
                    BaseType tv = event.get(key);
                    FieldType type = tv.getType();
                    sb.append("<tr><td></td><td>").append(type).append("</td><td>").append(key)
                            .append("</td></tr>\n");
                }
            }
        }

        sb.append("</table>\n");
        return sb.toString();

    }

    /**
     * Returns a rather long string Representation of the EventTemplateDB
     *
     * @return a string Representation of the EventTemplateDB
     */
    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("\n").append(META_EVENT_INFO).append("\n{\n");
        String[] reservedKeys = new String[reservedWords.size()];
        int i = 0, j = 0;

        for (String s1 : reservedWords.keySet()) {
            reservedKeys[i] = s1;
            ++i;
        }
        Arrays.sort(reservedKeys);

        for (i = 0; i < reservedKeys.length; ++i) {
            BaseType tv = reservedWords.get(reservedKeys[i]);
            FieldType type = tv.getType();
            sb.append("\t").append(type).append(" ").append(reservedKeys[i]).append(";\n");
        }
        sb.append("}\n");

        String[] eventKeys = new String[events.size()];
        i = 0;
        for (String s : events.keySet()) {
            eventKeys[i] = s;
            ++i;
        }
        Arrays.sort(eventKeys);

        for (i = 0; i < eventKeys.length; ++i) {
            sb.append(eventKeys[i]).append("\n{\n");
            if (eventKeys[i] != null) {
                Map<String, BaseType> event = events.get(eventKeys[i]);
                j = 0;
                String[] attributeKeys = new String[event.size()];
                for (Enumeration<String> att = Collections.enumeration(event.keySet()); att.hasMoreElements();) {
                    attributeKeys[j] = att.nextElement();
                    ++j;
                }
                Arrays.sort(attributeKeys);

                for (j = 0; j < attributeKeys.length; ++j) {
                    BaseType tv = event.get(attributeKeys[j]);
                    FieldType type = tv.getType();
                    sb.append("\t").append(type).append(" ").append(attributeKeys[j]).append(";\n");
                }
            }
            sb.append("}\n");
        }
        return sb.toString();
    }

    public String toStringOneLine() {
        return toString().replace("\n", " ");
    }

    public Map<String, BaseType> getMetaFields() {
        Map<String, BaseType> m = new TreeMap<String, BaseType>();
        m.putAll(reservedWords);
        return m;
    }

    public Map<String, Map<String, BaseType>> getEvents() {
        Map<String, Map<String, BaseType>> cp = new TreeMap<String, Map<String, BaseType>>();
        cp.putAll(events);
        return cp;
    }

    /**
     * This method can be used to validate an event after it has been created.
     *
     * @throws ValidationExceptions A list of validation errors
     */
    public void validate(Event event) throws ValidationExceptions {
        validate(event, null);
    }

    /**
     * This method can be used to validate an event after it has been created.
     *
     * @param event          the event to validate
     * @param excludedFields a pattern to determine which fields to ignore when validating
     * @throws ValidationExceptions A list of validation errors
     */
    public void validate(Event event, Pattern excludedFields) throws ValidationExceptions {
        final String eventName = event.getEventName();
        ValidationExceptions ve = null;

        final Map<String, BaseType> eventTypes = events.get(eventName);
        if (eventTypes == null) {
            throw new ValidationExceptions(
                    new NoSuchEventException("Event " + eventName + " does not exist in event definition"));
        }

        for (FieldAccessor field : event) {
            final String fieldName = field.getName();
            if (excludedFields != null && excludedFields.matcher(fieldName).matches()) {
                continue;
            }

            final BaseType baseType = eventTypes.get(fieldName);
            if (baseType == null) {
                if (ve == null) {
                    ve = new ValidationExceptions(eventName);
                }
                ve.addException(new NoSuchAttributeException(
                        "Attribute " + fieldName + " does not exist for event " + eventName));
                continue;
            }

            if (baseType.getType() != field.getType()) {
                if (ve == null) {
                    ve = new ValidationExceptions(eventName);
                }
                ve.addException(new NoSuchAttributeTypeException("Wrong type " + field.getType() + " for field "
                        + eventName + "." + fieldName + "; should be " + baseType.getType()));
                continue;
            }
        }

        for (Entry<String, BaseType> entry : eventTypes.entrySet()) {
            final String key = entry.getKey();
            if (excludedFields != null && excludedFields.matcher(key).matches()) {
                continue;
            }
            if (entry.getValue().isRequired()) {
                if (!event.isSet(key)) {
                    if (ve == null) {
                        ve = new ValidationExceptions(eventName);
                    }
                    ve.addException(new AttributeRequiredException(key));
                }
            }
        }

        if (ve != null) {
            throw ve;
        }
    }

    private void addMetaComment(String comment) {
        if (metaInfoComments == null) {
            metaInfoComments = comment;
        } else {
            metaInfoComments = metaInfoComments.concat(comment);
        }
    }

    public String getMetaComment() {
        return metaInfoComments;
    }
}