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 }