add the ability to lock entities against purging
[potlatch2.git] / net / systemeD / halcyon / connection / Entity.as
1 package net.systemeD.halcyon.connection {
2
3     import flash.events.EventDispatcher;
4     import flash.utils.Dictionary;
5     
6     import net.systemeD.halcyon.connection.actions.*;
7
8     public class Entity extends EventDispatcher {
9         private var _id:Number;
10         private var _version:uint;
11         private var _uid:Number;
12         private var _timestamp:String;
13         private var tags:Object = {};
14         private var modified:Boolean = false;
15                 private var _loaded:Boolean = true;
16                 private var parents:Dictionary = new Dictionary();
17                 public var locked:Boolean = false;                                              // lock against purging when off-screen
18                 public var deleted:Boolean = false;
19
20         public function Entity(id:Number, version:uint, tags:Object, loaded:Boolean, uid:Number, timestamp:String) {
21             this._id = id;
22             this._version = version;
23             this._uid = uid;
24             this._timestamp = timestamp;
25             this.tags = tags;
26                         this._loaded = loaded;
27             modified = id < 0;
28         }
29
30         public function get id():Number {
31             return _id;
32         }
33
34         public function get version():uint {
35             return _version;
36         }
37
38         public function get uid():Number {
39             return _uid;
40         }
41
42         public function get loaded():Boolean {
43             return _loaded;
44         }
45
46                 public function updateEntityProperties(version:uint, tags:Object, loaded:Boolean, uid:Number, timestamp:String):void {
47                         _version=version; this.tags=tags; _loaded=loaded; _uid = uid; _timestamp = timestamp;
48                 }
49
50                 public function renumber(newID:Number, newVersion:uint):void {
51                         this._id = newID;
52                         this._version = newVersion;
53                 }
54
55                 // Tag-handling methods
56
57         public function hasTags():Boolean {
58             for (var key:String in tags)
59                 return true;
60             return false;
61         }
62
63         public function hasInterestingTags():Boolean {
64             for (var key:String in tags) {
65               if (key != "attribution" && key != "created_by" && key != "source" && key.indexOf('tiger:') != 0) {
66                 //trace(key);
67                 return true;
68               }
69             }
70             return false;
71         }
72
73         public function isUneditedTiger():Boolean {
74             // todo: make this match the rules from the tiger edited map
75             // http://github.com/MapQuest/TIGER-Edited-map/blob/master/inc/layer-tiger.xml.inc
76             if (this is Way && (uid == 7168 || uid == 15169 || uid == 20587)) {//todo fixme etc
77               return true;
78             }
79             return false;
80         }
81
82         public function getTag(key:String):String {
83             return tags[key];
84         }
85
86                 public function tagIs(key:String,value:String):Boolean {
87                         if (!tags[key]) { return false; }
88                         return tags[key]==value;
89                 }
90         
91         public function setTag(key:String, value:String, performAction:Function):void {
92             performAction(new SetTagAction(this, key, value));
93         }
94
95         public function renameTag(oldKey:String, newKey:String, performAction:Function):void {
96             performAction(new SetTagKeyAction(this, oldKey, newKey));
97         }
98
99         public function getTagList():TagList {
100             return new TagList(tags);
101         }
102
103         public function getTagsCopy():Object {
104             var copy:Object = {};
105             for (var key:String in tags )
106                 copy[key] = tags[key];
107             return copy;
108         }
109
110                 public function getTagsHash():Object {
111                         // hm, not sure we should be doing this, but for read-only purposes
112                         // it's faster than using getTagsCopy
113                         return tags;
114                 }
115
116         public function getTagArray():Array {
117             var copy:Array = [];
118             for (var key:String in tags )
119                 copy.push(new Tag(this, key, tags[key]));
120             return copy;
121         }
122
123                 // Clean/dirty methods
124
125         public function get isDirty():Boolean {
126             return modified;
127         }
128
129         public function markClean():void {
130             modified = false;
131         }
132
133         internal function markDirty():void {
134             modified = true;
135         }
136
137                 // Delete entity
138                 
139                 public function remove(performAction:Function):void {
140                         // to be overridden
141                 }
142                 
143                 public function isDeleted():Boolean {
144                     return deleted;
145                 }
146                 
147                 public function setDeletedState(isDeleted:Boolean):void {
148                     deleted = isDeleted;
149             if (this is Node) {
150               var n:Node = Node(this);
151               if (isDeleted) {
152                 Connection.getConnection().removeDupe(n);
153               } else {
154                 Connection.getConnection().addDupe(n);
155               }
156             }
157                 }
158                 
159                 internal function isEmpty():Boolean {
160                         return false;   // to be overridden
161                 }
162
163                 public function nullify():void {
164                         // this retains a dummy entity in memory, for entities that we no longer need
165                         // but which are part of a still-in-memory relation
166                         nullifyEntity();
167                 }
168                 
169                 protected function nullifyEntity():void {
170                         // this is the common nullify behaviour for all entity types (we'd call this by super() if ActionScript let us)
171                         _version=0;
172                         _loaded=false;
173                         tags={};
174                 }
175                 
176                 
177                 public function within(left:Number,right:Number,top:Number,bottom:Number):Boolean {
178                         return true;    // to be overridden
179                 }
180
181                 public function removeFromParents(performAction:Function):void {
182                         for (var o:Object in parents) {
183                                 if (o is Relation) { Relation(o).removeMember(this, performAction); }
184                                 else if (o is Way) { Way(o).removeNode(Node(this), performAction); }
185                                 if (o.isEmpty()) { o.remove(performAction); }
186                         }
187                 }
188
189                 // Parent handling
190                 
191                 public function addParent(parent:Entity):void {
192                         parents[parent]=true;
193                         
194                         if ( parent is Relation )
195                             dispatchEvent(new RelationMemberEvent(Connection.ADDED_TO_RELATION, this, parent as Relation, -1));
196                 }
197
198                 public function removeParent(parent:Entity):void {
199                         delete parents[parent];
200
201                         if ( parent is Relation )
202                             dispatchEvent(new RelationMemberEvent(Connection.REMOVED_FROM_RELATION, this, parent as Relation, -1));
203                 }
204                 
205                 public function get parentWays():Array {
206                         var a:Array=[];
207                         for (var o:Object in parents) {
208                                 if (o is Way) { a.push(o); }
209                         }
210                         return a;
211                 }
212
213                 public function get hasParents():Boolean {
214                         for (var o:Object in parents) { return true; }
215                         return false;
216                 }
217                 
218                 public function get hasParentWays():Boolean {
219                         for (var o:Object in parents) {
220                                 if (o is Way) { return true; }
221                         }
222                         return false;
223                 }
224                 
225                 public function get numParentWays():uint {
226                         var i:uint=0;
227                         for (var o:Object in parents) {
228                                 if (o is Way) { i++; }
229                         }
230                         return i;
231                 }
232                 
233                 public function get parentRelations():Array {
234                         var a:Array=[];
235                         for (var o:Object in parents) {
236                                 if (o is Relation) { a.push(o); }
237                         }
238                         return a;
239                 }
240                 
241                 public function findParentRelationsOfType(type:String, role:String=null):Array {
242                         var a:Array=[];
243                         for (var o:Object in parents) {
244                                 if (o is Relation && Relation(o).tagIs('type',type) && (role==null || Relation(o).hasMemberInRole(this,role))) { 
245                                         a.push(o);
246                                 }
247                         }
248                         return a;
249                 }
250                 
251                 public function countParentObjects(within:Object):uint {
252                         var count:uint=0;
253                         for (var o:Object in parents) {
254                                 if (o.getType()==within.entity && o.getTag(within.k)) {
255                                         if (within.v && within.v!=o.getTag(within.k)) { break; }
256                                         if (within.role && !Relation(o).hasMemberInRole(this,within.role)) { break; }
257                                         count++;
258                                 }
259                         }
260                         return count;
261                 }
262                 
263                 public function get parentObjects():Array {
264                         var a:Array=[];
265                         for (var o:Object in parents) { a.push(o); }
266                         return a;
267                 }
268                 
269                 public function hasParent(entity:Entity):Boolean {
270             return parents[entity] == true;
271         }
272
273                 public function get memberships():Array {
274                         var list:Array=[];
275                         for (var o:Object in parents) {
276                                 if (o is Relation) {
277                                         for (var i:uint=0; i<o.length; i++) {
278                                                 if (o.getMember(i).entity==this) {
279                                                         list.push( { relation:o, position:i, role: o.getMember(i).role } );
280                                                 }
281                                         }
282                                 }
283                         }
284             // it's useful to return in a sorted order, even if the relations are interleaved
285             // e.g. [{r0 p1},{r1 p1},{r0 p4}]
286                         return list.sortOn("position"); 
287                 }
288
289                 // Resume/suspend redraw
290                 
291                 public function suspend():void {
292                         dispatchEvent(new EntityEvent(Connection.SUSPEND_REDRAW, this));
293                 }
294                 
295                 public function resume():void {
296                         dispatchEvent(new EntityEvent(Connection.RESUME_REDRAW, this));
297                 }
298
299                 // To be overridden
300
301                 public function getDescription():String {
302                         var basic:String=this.getType()+" "+_id;
303                         if (tags['ref'] && tags['name']) { return tags['ref']+' '+tags['name']+' ('+basic+')'; }
304                         if (tags['ref']) { return tags['ref']+' ('+basic+')'; }
305                         if (tags['name']) { return tags['name']+' ('+basic+')'; }
306                         return basic;
307                 }
308
309         public function getType():String {
310             return '';
311         }
312
313     }
314
315 }
316