1 // MODEL
  2 
  3 /**
  4     @class
  5     @constructor
  6 
  7     @description
  8 
  9     Class for operating on indexed array representations of objects.
 10 
 11     For example, if we have a lot of objects with similar attrbutes, e.g.:
 12 
 13     <pre class="code">
 14         [
 15             {start: 1, end: 2, strand: -1},
 16             {start: 5, end: 6, strand: 1},
 17             ...
 18         ]
 19     </pre>
 20 
 21     @description
 22     we can represent them more compactly (e.g., in JSON) something like this:
 23 
 24     <pre class="code">
 25         class = ["start", "end", "strand"]
 26         [
 27             [1, 2, -1],
 28             [5, 6, 1],
 29             ...
 30         ]
 31     </pre>
 32 
 33     If we want to represent a few different kinds of objects in our big list,
 34     we can have multiple "class" arrays, and tag each object to identify
 35     which "class" array describes it.
 36 
 37     For example, if we have a lot of instances of a few types of objects,
 38     like this:
 39 
 40     <pre class="code">
 41         [
 42             {start: 1, end: 2, strand: 1, id: 1},
 43             {start: 5, end: 6, strand: 1, id: 2},
 44             ...
 45             {start: 10, end: 20, chunk: 1},
 46             {start: 30, end: 40, chunk: 2},
 47             ...
 48         ]
 49     </pre>
 50 
 51     We could use the first array position to indicate the "class" for the
 52     object, like this:
 53 
 54     <pre class="code">
 55         classes = [["start", "end", "strand", "id"], ["start", "end", "chunk"]]
 56         [
 57             [0, 1, 2, 1, 1],
 58             [0, 5, 6, 1, 2],
 59             ...
 60             [1, 10, 20, 1],
 61             [1, 30, 40, 1]
 62         ]
 63     </pre>
 64 
 65     Also, if we occasionally want to add an ad-hoc attribute, we could just
 66     stick an optional dictionary onto the end:
 67 
 68     <pre class="code">
 69         classes = [["start", "end", "strand", "id"], ["start", "end", "chunk"]]
 70         [
 71             [0, 1, 2, 1, 1],
 72             [0, 5, 6, 1, 2, {foo: 1}]
 73         ]
 74     </pre>
 75 
 76     Given that individual objects are being represented by arrays, generic
 77     code needs some way to differentiate arrays that are meant to be objects
 78     from arrays that are actually meant to be arrays.
 79     So for each class, we include a dict with <attribute name>: true mappings
 80     for each attribute that is meant to be an array.
 81 
 82     Also, in cases where some attribute values are the same for all objects
 83     in a particular set, it may be convenient to define a "prototype"
 84     with default values for all objects in the set
 85 
 86     In the end, we get something like this:
 87 
 88     <pre class="code">
 89         classes=[
 90             {'attributes': ['Start', 'End', 'Subfeatures'],
 91              'proto': {'Chrom': 'chr1'},
 92              'isArrayAttr': {Subfeatures: true}}
 93             ]
 94     </pre>
 95 
 96     That's what this class facilitates.
 97 */
 98 function ArrayRepr (classes) {
 99     this.classes = classes;
100     this.fields = [];
101     for (var cl = 0; cl < classes.length; cl++) {
102         this.fields[cl] = {};
103         for (var f = 0; f < classes[cl].attributes.length; f++) {
104             this.fields[cl][classes[cl].attributes[f]] = f + 1;
105         }
106         if (classes[cl].proto === undefined)
107             classes[cl].proto = {};
108         if (classes[cl].isArrayAttr === undefined)
109             classes[cl].isArrayAttr = {};
110     }
111 }
112 
113 /**
114  * @private
115  */
116 ArrayRepr.prototype.attrIndices = function(attr) {
117     return this.classes.map(
118         function(x) {
119             var i = x.attributes.indexOf(attr);
120             return i >= 0 ? i + 1 : undefined;
121         }
122     );
123 };
124 
125 ArrayRepr.prototype.get = function(obj, attr) {
126     if (attr in this.fields[obj[0]]) {
127         return obj[this.fields[obj[0]][attr]];
128     } else {
129         var adhocIndex = this.classes[obj[0]].attributes.length + 1;
130         if ((adhocIndex >= obj.length) || (!(attr in obj[adhocIndex]))) {
131             if (attr in this.classes[obj[0]].proto)
132                 return this.classes[obj[0]].proto[attr];
133             return undefined;
134         }
135         return obj[adhocIndex][attr];
136     }
137 };
138 
139 ArrayRepr.prototype.set = function(obj, attr, val) {
140     if (attr in this.fields[obj[0]]) {
141         obj[this.fields[obj[0]][attr]] = val;
142     } else {
143         var adhocIndex = self.classes[obj[0]].length + 1;
144         if (adhocIndex >= obj.length)
145             obj[adhocIndex] = {};
146         obj[adhocIndex][attr] = val;
147     }
148 };
149 
150 ArrayRepr.prototype.makeSetter = function(attr) {
151     var self = this;
152     return function(obj, val) { self.set(obj, attr, val); };
153 };
154 
155 ArrayRepr.prototype.makeGetter = function(attr) {
156     var self = this;
157     return function(obj) { return self.get(obj, attr); };
158 };
159 
160 ArrayRepr.prototype.makeFastSetter = function(attr) {
161     // can be used only if attr is guaranteed to be in
162     // the "classes" array for this object
163     var indices = this.attrIndices(attr);
164     return function(obj, val) {
165         if (indices[obj[0]] !== undefined)
166             obj[indices[obj[0]]] = val;
167     };
168 };
169 
170 ArrayRepr.prototype.makeFastGetter = function(attr) {
171     // can be used only if attr is guaranteed to be in
172     // the "classes" array for this object
173     var indices = this.attrIndices(attr);
174     return function(obj) {
175         if (indices[obj[0]] !== undefined)
176             return obj[indices[obj[0]]];
177         else
178             return undefined;
179     };
180 };
181 
182 ArrayRepr.prototype.construct = function(self, obj, klass) {
183     var result = new Array(self.classes[klass].length);
184     for (var attr in obj) {
185         this.set(result, attr, obj[attr]);
186     }
187     return result;
188 };
189 
190 
191 /**
192 
193 Returns fast pre-compiled getter and setter functions for use with
194 Arrays that use this representation.
195 
196 When the returned <code>get</code> and <code>set</code> functions are
197 added as methods to an Array that contains data in this
198 representation, they provide fast access by name to the data.
199 
200 @returns {Object} <code>{ get: function() {...}, set: function(val) {...} }</code>
201 
202 @example
203 var accessors = attrs.accessors();
204 var feature = get_feature_from_someplace();
205 feature.get = accessors.get;
206 // print out the feature start and end
207 console.log( feature.get('start') + ',' + feature.get('end') );
208 
209 */
210 ArrayRepr.prototype.accessors = function () {
211     return this._accessors = this._accessors || this._makeAccessors();
212 };
213 
214 /**
215  * @private
216  */
217 ArrayRepr.prototype._makeAccessors = function() {
218     var that = this,
219         accessors = {
220             get: function(field) {
221                 var f = this.get.field_accessors[field];
222                 if( f )
223                     return f.call(this);
224                 else
225                     return undefined;
226             },
227             set: function(field,val) {
228                 var f = this.set.field_accessors[field];
229                 if( f )
230                     return f.call(this,val);
231                 else
232                     return undefined;
233             }
234         };
235     accessors.get.field_accessors = {};
236     accessors.set.field_accessors = {};
237 
238     // make a data structure as: { attr_name: [offset,offset,offset], }
239     // that will be convenient for finding the location of the attr
240     // for a given class like: indexForAttr{attrname}[classnum]
241     var indices = {};
242     dojo.forEach( this.classes, function(cdef,classnum) {
243         dojo.forEach( cdef.attributes || [], function(attrname,offset) {
244             attrname = attrname.toLowerCase();
245             indices[attrname] = indices[attrname] || [];
246             indices[attrname][classnum] = offset + 1;
247         });
248     });
249 
250     // use that to make precalculated get and set accessors for each field
251     for( var attrname in indices ) {
252         if( ! indices.hasOwnProperty(attrname) ) continue;
253 
254         // get
255         accessors.get.field_accessors[ attrname ] = (function() {
256             var attr_indices = indices[attrname];
257             return !attr_indices ? function() { return undefined; } : function() {
258                 return this[ attr_indices[ this[0] ] ];
259             };
260         })();
261 
262         // set
263         accessors.set.field_accessors[ attrname ] = (function() {
264             var attr_indices =  indices[attrname];
265             return !attr_indices ? function() { return undefined; } : function(v) {
266                 return ( this[ attr_indices[ this[0] ] ] = v );
267             };
268         })();
269     }
270 
271 
272     return accessors;
273 };
274 
275 /*
276 
277 Copyright (c) 2007-2010 The Evolutionary Software Foundation
278 
279 Created by Mitchell Skinner <mitch_skinner@berkeley.edu>
280 
281 This package and its accompanying libraries are free software; you can
282 redistribute it and/or modify it under the terms of the LGPL (either
283 version 2.1, or at your option, any later version) or the Artistic
284 License 2.0.  Refer to LICENSE for the full license text.
285 
286 */
287