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