PageRenderTime 29ms CodeModel.GetById 15ms app.highlight 6ms RepoModel.GetById 1ms app.codeStats 1ms

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