1<!DOCTYPE html>
2<!--
3Copyright (c) 2012 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="import" href="/tracing/base/event.html">
9<link rel="import" href="/tracing/base/guid.html">
10<link rel="import" href="/tracing/base/range.html">
11<link rel="import" href="/tracing/base/iteration_helpers.html">
12<link rel="import" href="/tracing/model/event_registry.html">
13
14<script>
15'use strict';
16
17tr.exportTo('tr.model', function() {
18
19  var EventRegistry = tr.model.EventRegistry;
20
21  var RequestSelectionChangeEvent = tr.b.Event.bind(
22      undefined, 'requestSelectionChange', true, false);
23
24  /**
25   * Represents a event set within a  and its associated set of tracks.
26   * @constructor
27   */
28  function EventSet(opt_events) {
29    this.bounds_dirty_ = true;
30    this.bounds_ = new tr.b.Range();
31    this.length_ = 0;
32    this.guid_ = tr.b.GUID.allocate();
33    this.pushed_guids_ = {};
34
35    if (opt_events) {
36      if (opt_events instanceof Array) {
37        for (var i = 0; i < opt_events.length; i++)
38          this.push(opt_events[i]);
39      } else if (opt_events instanceof EventSet) {
40        this.addEventSet(opt_events);
41      } else {
42        this.push(opt_events);
43      }
44    }
45  }
46  EventSet.prototype = {
47    __proto__: Object.prototype,
48
49    get bounds() {
50      if (this.bounds_dirty_)
51        this.resolveBounds_();
52      return this.bounds_;
53    },
54
55    get duration() {
56      if (this.bounds_.isEmpty)
57        return 0;
58      return this.bounds_.max - this.bounds_.min;
59    },
60
61    get length() {
62      return this.length_;
63    },
64
65    get guid() {
66      return this.guid_;
67    },
68
69    clear: function() {
70      for (var i = 0; i < this.length_; ++i)
71        delete this[i];
72      this.length_ = 0;
73      this.bounds_dirty_ = true;
74    },
75
76    resolveBounds_: function() {
77      this.bounds_.reset();
78      for (var i = 0; i < this.length_; i++)
79        this[i].addBoundsToRange(this.bounds_);
80      this.bounds_dirty_ = false;
81    },
82
83    // push pushes only unique events.
84    // If an event has been already pushed, do nothing.
85    push: function(event) {
86      if (event.guid == undefined)
87        throw new Error('Event must have a GUID');
88
89      if (this.contains(event))
90        return event;
91
92      this.pushed_guids_[event.guid] = true;
93      this[this.length_++] = event;
94      this.bounds_dirty_ = true;
95      return event;
96    },
97
98    contains: function(event) {
99      return this.pushed_guids_[event.guid];
100    },
101
102    indexOf: function(event) {
103      for (var i = 0; i < this.length; i++) {
104        if (this[i].guid === event.guid)
105          return i;
106      }
107      return -1;
108    },
109
110    addEventSet: function(eventSet) {
111      for (var i = 0; i < eventSet.length; i++)
112        this.push(eventSet[i]);
113    },
114
115    subEventSet: function(index, count) {
116      count = count || 1;
117
118      var eventSet = new EventSet();
119      eventSet.bounds_dirty_ = true;
120      if (index < 0 || index + count > this.length_)
121        throw new Error('Index out of bounds');
122
123      for (var i = index; i < index + count; i++)
124        eventSet.push(this[i]);
125
126      return eventSet;
127    },
128
129    intersectionIsEmpty: function(otherEventSet) {
130      return !this.some(function(event) {
131        return otherEventSet.contains(event);
132      });
133    },
134
135    equals: function(that) {
136      if (this.length !== that.length)
137        return false;
138      for (var i = 0; i < this.length; i++) {
139        var event = this[i];
140        if (that.pushed_guids_[event.guid] === undefined)
141          return false;
142      }
143      return true;
144    },
145
146    getEventsOrganizedByBaseType: function(opt_pruneEmpty) {
147      var allTypeInfos = EventRegistry.getAllRegisteredTypeInfos();
148
149      var events = this.getEventsOrganizedByCallback(function(event) {
150        var maxEventIndex = -1;
151        var maxEventTypeInfo = undefined;
152
153        allTypeInfos.forEach(function(eventTypeInfo, eventIndex) {
154          if (!(event instanceof eventTypeInfo.constructor))
155            return;
156          if (eventIndex > maxEventIndex) {
157            maxEventIndex = eventIndex;
158            maxEventTypeInfo = eventTypeInfo;
159          }
160        });
161
162        if (maxEventIndex == -1) {
163          console.log(event);
164          throw new Error('Unrecognized event type');
165        }
166
167        return maxEventTypeInfo.metadata.name;
168      });
169
170      if (!opt_pruneEmpty) {
171        allTypeInfos.forEach(function(eventTypeInfo) {
172          if (events[eventTypeInfo.metadata.name] === undefined)
173            events[eventTypeInfo.metadata.name] = new EventSet();
174        });
175      }
176
177      return events;
178    },
179
180    getEventsOrganizedByTitle: function() {
181      return this.getEventsOrganizedByCallback(function(event) {
182        if (event.title === undefined)
183          throw new Error('An event didn\'t have a title!');
184        return event.title;
185      });
186    },
187
188    getEventsOrganizedByCallback: function(cb) {
189      var eventsByCallback = {};
190      for (var i = 0; i < this.length; i++) {
191        var event = this[i];
192        var key = cb(event);
193
194        if (key === undefined)
195          throw new Error('An event could not be organized');
196
197        if (eventsByCallback[key] === undefined)
198          eventsByCallback[key] = new EventSet();
199
200        eventsByCallback[key].push(event);
201      }
202      return eventsByCallback;
203    },
204
205    enumEventsOfType: function(type, func) {
206      for (var i = 0; i < this.length_; i++)
207        if (this[i] instanceof type)
208          func(this[i]);
209    },
210
211    get userFriendlyName() {
212      if (this.length === 0) {
213        throw new Error('Empty event set');
214      }
215
216      var eventsByBaseType = this.getEventsOrganizedByBaseType(true);
217      var eventTypeName = tr.b.dictionaryKeys(eventsByBaseType)[0];
218
219      if (this.length === 1) {
220        var tmp = EventRegistry.getUserFriendlySingularName(eventTypeName);
221        return this[0].userFriendlyName;
222      }
223
224      var numEventTypes = tr.b.dictionaryLength(eventsByBaseType);
225      if (numEventTypes !== 1) {
226        return this.length + ' events of various types';
227      }
228
229      var tmp = EventRegistry.getUserFriendlyPluralName(eventTypeName);
230      return this.length + ' ' + tmp;
231    },
232
233    filter: function(fn, opt_this) {
234      var res = new EventSet();
235
236      this.forEach(function(slice) {
237        if (fn.call(this, slice))
238          res.push(slice);
239      }, opt_this);
240
241      return res;
242    },
243
244    toArray: function() {
245      var ary = [];
246      for (var i = 0; i < this.length; i++)
247        ary.push(this[i]);
248      return ary;
249    },
250
251    forEach: function(fn, opt_this) {
252      for (var i = 0; i < this.length; i++)
253        fn.call(opt_this, this[i], i);
254    },
255
256    map: function(fn, opt_this) {
257      var res = [];
258      for (var i = 0; i < this.length; i++)
259        res.push(fn.call(opt_this, this[i], i));
260      return res;
261    },
262
263    every: function(fn, opt_this) {
264      for (var i = 0; i < this.length; i++)
265        if (!fn.call(opt_this, this[i], i))
266          return false;
267      return true;
268    },
269
270    some: function(fn, opt_this) {
271      for (var i = 0; i < this.length; i++)
272        if (fn.call(opt_this, this[i], i))
273          return true;
274      return false;
275    },
276
277    asDict: function() {
278      var stable_ids = [];
279      this.forEach(function(event) {
280        stable_ids.push(event.stableId);
281      });
282      return {'events': stable_ids};
283    }
284  };
285
286  EventSet.IMMUTABLE_EMPTY_SET = (function() {
287    var s = new EventSet();
288    s.resolveBounds_();
289    s.push = function() {
290      throw new Error('Cannot push to an immutable event set');
291    };
292    s.addEventSet = function() {
293      throw new Error('Cannot add to an immutable event set');
294    };
295    Object.freeze(s);
296    return s;
297  })();
298
299  return {
300    EventSet: EventSet,
301    RequestSelectionChangeEvent: RequestSelectionChangeEvent
302  };
303});
304</script>
305