001    /* 
002     * Copyright 2008-2009 the original author or authors.
003     * The contents of this file are subject to the Mozilla Public License
004     * Version 1.1 (the "License"); you may not use this file except in
005     * compliance with the License. You may obtain a copy of the License at
006     * http://www.mozilla.org/MPL/
007     *
008     * Software distributed under the License is distributed on an "AS IS"
009     * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
010     * License for the specific language governing rights and limitations
011     * under the License.
012     */
013     
014    package com.mtgi.analytics;
015    
016    import java.io.ObjectStreamException;
017    import java.io.Serializable;
018    import java.util.Collections;
019    import java.util.HashMap;
020    import java.util.Iterator;
021    import java.util.Map;
022    import java.util.NoSuchElementException;
023    
024    /**
025     * Semi-structured (XML-like) data about a {@link BehaviorEvent}.
026     * 
027     * @see BehaviorEvent#getData()
028     */
029    public class EventDataElement extends DataLink<EventDataElement> implements Serializable {
030    
031            private static final long serialVersionUID = -7479072851562747744L;
032            
033            private String name;
034            private String text;
035    
036            //simple linked-list structure for child data elements, which has proven
037            //faster than java collections in hitting our performance test targets
038            private EventDataElement firstChild = ChildListHead.INSTANCE;
039            private EventDataElement lastChild = ChildListHead.INSTANCE;
040    
041            //a flat linked list for properties.
042            private Property firstProperty = PropertyListHead.INSTANCE;
043            private Property lastProperty = PropertyListHead.INSTANCE;
044            
045            public EventDataElement(String name) {
046                    this.name = name;
047            }
048            
049            /**
050             * Get the name of this element
051             */
052            public String getName() {
053                    return name;
054            }
055    
056            /**
057             * Get the text of this element, if any
058             */
059            public String getText() {
060                    return text;
061            }
062            
063            public void setText(String text) {
064                    this.text = text;
065            }
066            
067            /**
068             * Set a named attribute on this element.  Values may be null.  Calling
069             * this method multiple times with the same name will result in previous values
070             * being overwritten.
071             */
072            public void add(String name, Object value) {
073                    //just append for speed -- we compact to a unique set of names
074                    //when iterateProperties() is called.
075                    lastProperty.setNext(this, new Property(name, value));
076            }
077    
078            /**
079             * Add a child element with the given name to this element.
080             */
081            public EventDataElement addElement(String name) {
082                    EventDataElement ret = new EventDataElement(name);
083                    lastChild.setNext(this, ret);
084                    return ret;
085            }
086            
087            public void addElement(EventDataElement child) {
088                    lastChild.setNext(this, child);
089            }
090    
091            /** @return true if this element has no child properties or child elements */
092            public boolean isEmpty() {
093                    return text == null && firstProperty == PropertyListHead.INSTANCE && firstChild == ChildListHead.INSTANCE;
094            }
095            
096            /** @return true if this element is effectively null (should not be rendered in serialized output) */
097            public boolean isNull() {
098                    return false;
099            }
100            
101            /** iterate all properties previously added with {@link #add(String, Object)} */
102            public Iterator<? extends Map.Entry<String,Object>> iterateProperties() {
103                    return firstProperty.compact().iterate();
104            }
105    
106            /** 
107             * iterate all child elements of this event data element previously added with
108             * {@link #addElement(EventDataElement)} or {@link #addElement(String)} 
109             */
110            public Iterator<EventDataElement> iterateChildren() {
111                    return firstChild.iterate();
112            }
113            
114            /** set the next sibling in the linked list of children under <code>parent</code>. */
115            protected void setNext(EventDataElement parent, EventDataElement next) {
116                    parent.lastChild = this.next = next;
117            }
118            /** iterate the list of sibling event data elements starting at this element */
119            protected Iterator<EventDataElement> iterate() {
120                    return new DataLinkIterator<EventDataElement>(this);
121            }
122            
123            /**
124             * Return a concrete, fully-realized instance of this data, performing any deferred initialization
125             * of internal data structures.  This will likely be called many times for complex events, so it should
126             * return quickly.  This implementation simply returns "this".
127             * @param event the parent event
128             */
129            protected EventDataElement initialize(BehaviorEvent event) {
130                    return this;
131            }
132            
133            private static class ChildListHead extends ImmutableEventDataElement {
134                    
135                    private static final long serialVersionUID = 1511666816688289823L;
136                    
137                    static final ChildListHead INSTANCE = new ChildListHead();
138                    
139                    private ChildListHead() {
140                            super("");
141                    }
142                    
143                    @Override
144                    protected void setNext(EventDataElement parent, EventDataElement next) {
145                            //replace head/tail reference on parent with the provided real data element.
146                            parent.firstChild = parent.lastChild = next;
147                    }
148                    
149                    @Override
150                    public Iterator<EventDataElement> iterate() {
151                            return Collections.<EventDataElement>emptyList().iterator();
152                    }
153                    
154                    private Object readResolve() throws ObjectStreamException {
155                            return ChildListHead.INSTANCE;
156                    }
157            }
158    
159            private static class PropertyListHead extends Property {
160                    
161                    public static final Property INSTANCE = new PropertyListHead();
162    
163                    private PropertyListHead() {
164                            super(null, null);
165                    }
166    
167                    @Override
168                    protected void setNext(EventDataElement parent, Property next) {
169                            //replace head / tail elements on parent with the provided link.
170                            parent.firstProperty = parent.lastProperty = next;
171                    }
172    
173                    @Override
174                    protected Iterator<Property> iterate() {
175                            return Collections.<Property>emptyList().iterator();
176                    }
177                    
178                    @Override
179                    protected Property compact() {
180                            return this;
181                    }
182            }
183            
184            private static class Property extends DataLink<Property> 
185                    implements Map.Entry<String, Object> 
186            {
187                    private String key;
188                    private Object value;
189    
190                    protected Property(String key, Object value) {
191                            this.key = key;
192                            this.value = value;
193                    }
194                    
195                    protected void setNext(EventDataElement parent, Property next) {
196                            parent.lastProperty = this.next = next;
197                    }
198                    
199                    public String getKey() {
200                            return key;
201                    }
202    
203                    public Object getValue() {
204                            return value;
205                    }
206    
207                    public Object setValue(Object value) {
208                            Object ret = this.value;
209                            this.value = value;
210                            return ret;
211                    }
212    
213                    protected Iterator<Property> iterate() {
214                            return new DataLinkIterator<Property>(this);
215                    }
216                    
217                    /**
218                     * Eliminate any duplicate keys from the linked list starting at this
219                     * link, returning a reference to the head of the compacted list
220                     * for convenient chaining of method calls.
221                     */
222                    protected Property compact() {
223                            HashMap<String,Property> uniq = new HashMap<String,Property>();
224                            for (Property link = this, prev = null; 
225                                     link != null; 
226                                     prev = link, link = link.next) {
227                                    Property prior = uniq.get(link.key);
228                                    if (prior == null)
229                                            uniq.put(link.key, this);
230                                    else {
231                                            //previous link with same key; overwrite the
232                                            //value in the older entry and delete this link.
233                                            prior.value = link.value;
234                                            prev.next = link.next;
235                                            link = prev;
236                                    }
237                            }
238                            return this;
239                    }
240            }
241    }
242    
243    /**
244     * base class for a singly-linked list, with an iterator implementation.  provided
245     * for a measurable performance improvement over standard java collections.
246     */
247    class DataLink<T extends DataLink<T>> {
248            
249            protected T next;
250            
251            static class DataLinkIterator<T extends DataLink<T>> implements Iterator<T> {
252    
253                    private T current;
254                    
255                    protected DataLinkIterator(T head) {
256                            this.current = head;
257                    }
258                    
259                    public boolean hasNext() {
260                            return current != null;
261                    }
262    
263                    public T next() {
264                            if (current == null)
265                                    throw new NoSuchElementException();
266                            T ret = current;
267                            current = ret.next;
268                            return ret;
269                    }
270    
271                    public void remove() {
272                            throw new UnsupportedOperationException();
273                    }
274                    
275            }
276            
277    }