/YEDNode.j

http://github.com/rheimbuch/YED · Unknown · 317 lines · 268 code · 49 blank · 0 comment · 0 complexity · 5de72a6061e70f073386bd5987a17928 MD5 · raw file

  1. @import <Foundation/CPObject.j>
  2. @import <Foundation/CPSet.j>
  3. @import <Foundation/CPException.j>
  4. // Constants
  5. YEDNodeCycleException = "YEDNodeCycleException";
  6. YEDNodeNotAllowedException = "YEDNodeNotAllowedException";
  7. /*
  8. * Performs a depth-first-search of an edge set,
  9. * tracking each node encountered. If a node is
  10. * encountered more than once, then a cycle has
  11. * been found.
  12. */
  13. YEDNodeGraphHasCycles = function(aNode, traverseParents)
  14. {
  15. CPLog.trace("YEDNodeGraphHasCycles: Testing for cycles starting at %s", [aNode name]);
  16. traverseParents = traverseParents || NO;
  17. var stack = []
  18. function isAcyclic(node)
  19. {
  20. if([stack containsObject:node])
  21. {
  22. return false;
  23. }
  24. stack.push(node);
  25. CPLog.trace("YEDNodeGraphHasCycles: at node %s", [node name]);
  26. var targetNode = nil;
  27. var nodeIter = traverseParents ? [[node inEdges] objectEnumerator] :
  28. [[node outEdges] objectEnumerator];
  29. traverseParents ? CPLog.trace("YEDNodeGraphHasCycles: Traversing inEdges") :
  30. CPLog.trace("YEDNodeGraphHasCycles: Travering outEdges");
  31. while(targetNode = [nodeIter nextObject])
  32. {
  33. if(!isAcyclic(targetNode, traverseParents))
  34. {
  35. return false;
  36. }
  37. }
  38. stack.pop();
  39. return true;
  40. }
  41. var result = isAcyclic(aNode);
  42. CPLog.trace("YEDNodeGraphHasCycles: Graph staring at %s is acyclic: %s", [aNode name], result);
  43. return !result;
  44. };
  45. @implementation YEDNode : CPObject
  46. {
  47. CPString name @accessors;
  48. CPSet outEdges @accessors(readonly);
  49. CPSet inEdges @accessors(readonly);
  50. CPSet allowsConnectionsTo @accessors(readonly);
  51. CPSet allowsConnectionsFrom @accessors(readonly);
  52. BOOL isAcyclic @accessors;
  53. }
  54. - (id)init
  55. {
  56. self = [super init];
  57. if(self)
  58. {
  59. outEdges = [CPSet set];
  60. inEdges = [CPSet set];
  61. allowsConnectionsTo = [CPSet setWithObject:[self className]];
  62. allowsConnectionsFrom = [CPSet setWithObject:[self className]];
  63. isAcyclic = NO;
  64. }
  65. return self;
  66. }
  67. - (id)initAcyclic
  68. {
  69. self = [self init]
  70. if(self)
  71. {
  72. isAcyclic = YES;
  73. }
  74. return self;
  75. }
  76. + (id)node
  77. {
  78. return [[self alloc] init];
  79. }
  80. + (id)nodeWithName:(CPString)aName
  81. {
  82. var node = [self node];
  83. [node setName:aName];
  84. return node;
  85. }
  86. + (id)acyclicNode
  87. {
  88. return [[self alloc] initAcyclic];
  89. }
  90. + (id)acyclicNodeWithName:(CPString)aName
  91. {
  92. var node = [self acyclicNode];
  93. [node setName:aName]
  94. return node;
  95. }
  96. - (BOOL)isNode
  97. {
  98. return YES;
  99. }
  100. // - (BOOL)isEqual:(id)other
  101. // {
  102. // if(other === self)
  103. // return YES;
  104. // if(!other || ![other isKindOfClass:[self class]])
  105. // return NO
  106. // return [self isEqualToNode:other];
  107. // }
  108. //
  109. // - (BOOL)isEqualToNode:(YEDNode)otherNode
  110. // {
  111. // if(otherNode === self)
  112. // return YES;
  113. // if(![[self name] isEqual:[otherNode name]])
  114. // return NO;
  115. // if(![[self isAcyclic] isEqual:[otherNode isAcyclic]])
  116. // return NO;
  117. // if(![[self allowsConnectionsTo] isEqualToSet:[otherNode allowsConnectionsTo]])
  118. // return NO;
  119. // if(![[self allowsConnectionsFrom] isEqualToSet:[otherNode allowsConnectionsFrom]])
  120. // return NO;
  121. // return YES;
  122. // }
  123. - (BOOL)hasOutgoingEdgeTo:(YEDNode)otherNode
  124. {
  125. return [outEdges containsObject:otherNode];
  126. }
  127. - (BOOL)hasIncomingEdgeFrom:(YEDNode)otherNode
  128. {
  129. return [otherNode hasOutgoingEdgeTo:self];
  130. }
  131. - (void)directedEdgeTo:(YEDNode)otherNode
  132. {
  133. if([self canConnectTo:otherNode])
  134. {
  135. CPLog.trace("directedEdgeTo: %s allowed to connect to %s", [self name], [otherNode name]);
  136. // If the otherNode has no incoming or outgoing edges, then it cannot introduce
  137. // any cycles into the graph. We test because adding a single, indepedent node
  138. // is a common case.
  139. var otherNodeCouldIntroduceCycles = [[otherNode inEdges] anyObject] || [[otherNode outEdges] anyObject];
  140. CPLog.trace("directedEdgeTo: %s could introduce a cycle?: %s", [otherNode name], otherNodeCouldIntroduceCycles);
  141. //Add the node to the graph first
  142. [[self outEdges] addObject:otherNode];
  143. [[otherNode inEdges] addObject:self];
  144. if(isAcyclic && otherNodeCouldIntroduceCycles)
  145. {
  146. if([self cycleInDescendents] || [self cycleInParents])
  147. {
  148. CPLog.warn("directedEdgeTo: detected a cycle! removing edge from %s to %s", [self name], [otherNode name]);
  149. //If adding the edge/connection introduces cycles, we should remove it.
  150. [[self outEdges] removeObject:otherNode];
  151. [[otherNode inEdges] removeObject:self];
  152. [CPException raise:YEDNodeCycleException reason:"Connecting the node would introduce a cycle."];
  153. }
  154. }
  155. }
  156. else
  157. {
  158. CPLog.warn("directedEdgeTo: %s not allowed to connect to %s", [self name], [otherNode name]);
  159. [CPException raise:YEDNodeNotAllowedException reason:"Connecting to this node type is not allowed."];
  160. }
  161. }
  162. - (void)directedEdgeFrom:(YEDNode)otherNode
  163. {
  164. [otherNode directedEdgeTo:self];
  165. }
  166. - (void)removeDirectedEdgeTo:(YEDNode)otherNode
  167. {
  168. [[self outEdges] removeObject:otherNode];
  169. [[otherNode inEdges] removeObject:self];
  170. }
  171. - (void)disconnectFromAllNodes
  172. {
  173. var node = nil,
  174. outIter = [outEdges objectEnumerator],
  175. inIter = [inEdges objectEnumerator];
  176. while(node = [outIter nextObject])
  177. {
  178. [self removeDirectedEdgeTo:node];
  179. }
  180. while(node = [inIter nextObject])
  181. {
  182. [node removeDirectedEdgeTo:self];
  183. }
  184. }
  185. /*
  186. * Is the node allowed to connect to another node.
  187. * By default all YEDNodes can connect to any other node.
  188. */
  189. - (BOOL)canConnectTo:(YEDNode)otherNode
  190. {
  191. var allowTo = false;
  192. var allowToIter = [allowsConnectionsTo objectEnumerator];
  193. var allowedNodeClass = nil;
  194. while(allowedNodeClass = objj_lookUpClass([allowToIter nextObject]))
  195. {
  196. if([otherNode isKindOfClass:allowedNodeClass])
  197. allowTo = true;
  198. }
  199. var allowFrom = false;
  200. var allowFromIter = [[otherNode allowsConnectionsFrom] objectEnumerator];
  201. var allowedNodeClass = nil;
  202. while(allowedNodeClass = objj_lookUpClass([allowFromIter nextObject]))
  203. {
  204. if([self isKindOfClass:allowedNodeClass])
  205. allowFrom = true;
  206. }
  207. return allowTo && allowFrom;
  208. }
  209. - (BOOL)canRecieveConnectionFrom:(YEDNode)otherNode
  210. {
  211. return [otherNode canConnectTo:self];
  212. }
  213. - (CPSet)allDescendents
  214. {
  215. var descendents = [CPSet set];
  216. function traverse(node) {
  217. [descendents unionSet:[node outEdges]];
  218. var iter = [[node outEdges] objectEnumerator];
  219. while(node = [iter nextObject])
  220. {
  221. traverse(node);
  222. }
  223. }
  224. traverse(self);
  225. return descendents;
  226. }
  227. - (BOOL)cycleInDescendents
  228. {
  229. return YEDNodeGraphHasCycles(self,NO);
  230. }
  231. - (BOOL)cycleInParents
  232. {
  233. return YEDNodeGraphHasCycles(self,YES);
  234. }
  235. @end
  236. // Coding Keys
  237. var YEDNodeNameKey = @"YEDNodeNameKey",
  238. YEDNodeIsAcyclicKey = @"YEDNodeIsAcyclicKey",
  239. YEDNodeOutEdgesKey = @"YEDNodeOutEdgesKey",
  240. YEDNodeInEdgesKey = @"YEDNodeInEdgesKey",
  241. YEDNodeAllowsConnectionsToKey = @"YEDNodeAllowsConnectionsToKey",
  242. YEDNodeAllowsConnectionsFromKey = @"YEDNodeAllowsConnectionsFromKey";
  243. @implementation YEDNode (CPCoding)
  244. - (id)initWithCoder:(CPCoder)coder
  245. {
  246. self = [super init];
  247. if(self)
  248. {
  249. name = [coder decodeObjectForKey:YEDNodeNameKey];
  250. isAcyclic = [coder decodeObjectForKey:YEDNodeIsAcyclicKey];
  251. outEdges = [coder decodeObjectForKey:YEDNodeOutEdgesKey];
  252. inEdges = [coder decodeObjectForKey:YEDNodeInEdgesKey];
  253. allowsConnectionsTo = [coder decodeObjectForKey:YEDNodeAllowsConnectionsToKey];
  254. allowsConnectionsFrom = [coder decodeObjectForKey:YEDNodeAllowsConnectionsFromKey];
  255. }
  256. return self;
  257. }
  258. - (void)encodeWithCoder:(CPCoder)coder
  259. {
  260. // [super encodeWithCoder:coder];
  261. [coder encodeObject:name forKey:YEDNodeNameKey];
  262. [coder encodeObject:isAcyclic forKey:YEDNodeIsAcyclicKey];
  263. [coder encodeObject:outEdges forKey:YEDNodeOutEdgesKey];
  264. [coder encodeObject:inEdges forKey:YEDNodeInEdgesKey];
  265. [coder encodeObject:allowsConnectionsTo forKey:YEDNodeAllowsConnectionsToKey];
  266. [coder encodeObject:allowsConnectionsFrom forKey:YEDNodeAllowsConnectionsFromKey];
  267. }
  268. @end