source: trunk/faces/apollo/src/org/apollo/audio/structure/AudioStructureModel.java@ 754

Last change on this file since 754 was 754, checked in by jts21, 10 years ago

Move Apollo stuff to faces/apollo folder, add Apollo build script. TODO: Link in Apollo resources (icons) correctly (probably using the same system as used for the main Expeditee icons)

File size: 50.2 KB
Line 
1package org.apollo.audio.structure;
2
3import java.io.FileNotFoundException;
4import java.io.IOException;
5import java.lang.reflect.InvocationTargetException;
6import java.util.HashMap;
7import java.util.HashSet;
8import java.util.LinkedList;
9import java.util.List;
10import java.util.Map;
11import java.util.Queue;
12import java.util.Set;
13import java.util.Stack;
14
15import javax.sound.sampled.UnsupportedAudioFileException;
16import javax.swing.SwingUtilities;
17
18import org.apollo.audio.ApolloSubjectChangedEvent;
19import org.apollo.io.AudioIO;
20import org.apollo.io.AudioPathManager;
21import org.apollo.mvc.AbstractSubject;
22import org.apollo.mvc.SubjectChangedEvent;
23import org.apollo.util.ExpediteeFileTextSearch;
24import org.apollo.util.Mutable;
25import org.apollo.util.TextItemSearchResult;
26import org.apollo.widgets.LinkedTrack;
27import org.apollo.widgets.SampledTrack;
28import org.apollo.widgets.TrackWidgetCommons;
29import org.expeditee.gui.DisplayIO;
30import org.expeditee.gui.Frame;
31import org.expeditee.gui.FrameGraphics;
32import org.expeditee.gui.FrameIO;
33import org.expeditee.settings.UserSettings;
34import org.expeditee.io.Conversion;
35import org.expeditee.items.ItemUtils;
36import org.expeditee.items.widgets.InteractiveWidget;
37
38/**
39 * A thread safe model of the heirarchical structure of a track graph...
40 * abstracted from Expeditee frames and audio widgets.
41 *
42 * The track widgets notifty this model for keeping the structure consistent with
43 * expeditees data.
44 *
45 *
46 * <b>Thread safe convention:</b>
47 * OverdubbedFrame's and TrackGraphInfo are always handled on the swing thread.
48 * This class provides asynch loading routines but that is purely for loading - not
49 * handling!
50 *
51 * The track graph is a loop-free directed graph.
52 *
53 * A graph root is a graph of OverdubbedFrame's
54 *
55 * Although a {@link TrackGraphNode} or a {@link LinkedTracksGraphNode} may reside
56 * in multiple graphs, there is always only one instance of them - they
57 * are just shared.
58 *
59 * There are multiple graphs because Graphs can be mutually exlusive.
60 * Or some frames cannot be reached unless they are a starting point (aka root)
61 *
62 * @author Brook Novak
63 *
64 */
65public class AudioStructureModel extends AbstractSubject {
66
67 /**
68 * A loop-free directed graph.
69 * For each graph root, there can be no other graph root that is
70 * reachable (other than itself).
71 */
72 private Set<OverdubbedFrame> graphRoots = new HashSet<OverdubbedFrame>(); // SHARED RESOURCE
73
74 /** All overdubbed frames loaded in memory. MAP = Framename -> Overdub instance */
75 private Map<String, OverdubbedFrame> allOverdubbedFrames = new HashMap<String, OverdubbedFrame>(); // SHARED RESOURCE
76
77 /** For resources {@link AudioStructureModel#graphRoots} and {@link AudioStructureModel#allOverdubbedFrames} */
78 private Object sharedResourceLocker = new Object();
79
80 private boolean cancelFetch = false;
81
82 private DelayedModelUpdator delayedModelUpdator = null;
83
84 private static AudioStructureModel instance = new AudioStructureModel();
85
86 private AudioStructureModel() { }
87
88 public static AudioStructureModel getInstance() {
89 return instance;
90 }
91
92 /**
93 * <b>MUST NOT BE IN THE EXPEDITEE THREAD! OTHERWISE WILL DEFINITELY DEADLOCK</b>
94 *
95 * Same as {@link #fetchGraph(String)} but waits for any updates to finish before
96 * the fetch.
97 *
98 * Intention: really to highlight the need to call waitOnUpdates prior to this ... but
99 * may not bee neccessary...
100 *
101 * @param rootFrameName
102 * refer to #fetchGraph(String)
103 * @return
104 * refer to #fetchGraph(String)
105 *
106 * @throws NullPointerException
107 * if rootFrameName is null.
108 *
109 * @throws IllegalArgumentException
110 * If rootFrameName is not a valid framename.
111 *
112 * @throws InterruptedException
113 * refer to #fetchGraph(String)
114 *
115 * @throws TrackGraphLoopException
116 * refer to #fetchGraph(String) -
117 * <B>However</B> - it could also be that the reason was due to the thread being interupted while
118 * waiting on a update to finish. Thus may want to manually wait for updates prior - see
119 * {@link #waitOnUpdates()}.
120 *
121 * @see #fetchGraph(String)
122 */
123 public synchronized OverdubbedFrame fetchLatestGraph(String rootFrameName)
124 throws InterruptedException, TrackGraphLoopException {
125 waitOnUpdates();
126 return fetchGraph(rootFrameName);
127 }
128
129 /**
130 * May have to read from file if not yet loaded in memory.
131 * Thus it could take some time.
132 *
133 * Thread safe.
134 *
135 * NOTE: The intention is that this is called on a dedicated thread... other than
136 * swings thread. However once a OverdubbedFrame is returned be sure
137 * to only use it on the swing thread by convention.
138 *
139 * <b>MUST NOT NE IN THE EXPEDITEE THREAD! OTHERWISE WILL DEFINITELY DEADLOCK</b>
140 *
141 * @param rootFrameName
142 * Must not be null. Mustn't be a link - must be the <i>framename</i>
143 *
144 * @return
145 * The overdubbed frame.
146 * Null if the frame does not exist - or if it does exist but there
147 * are no track / linked-track widgets on it. Not that it can return
148 * a overdubbed frame that contains a heirarchy of linked overdubbed frames
149 * but no actual playable tracks.
150 *
151 * @throws NullPointerException
152 * if rootFrameName is null.
153 *
154 * @throws IllegalArgumentException
155 * If rootFrameName is not a valid framename.
156 *
157 * @throws InterruptedException
158 * If the fetch request was cancelled because the model
159 * has changed in some way during the read. Must retry the fetch.
160 *
161 * @throws TrackGraphLoopException
162 * If the requested root introduces loops to the current graph state.
163 * The loop trace will be provided in the exception.
164 *
165 * @see TrackGraphLoopException#getFullLoopTrace()
166 *
167 */
168 public synchronized OverdubbedFrame fetchGraph(String rootFrameName)
169 throws InterruptedException, TrackGraphLoopException {
170
171 if(rootFrameName == null) throw new NullPointerException("rootFrameName");
172 if(!FrameIO.isValidFrameName(rootFrameName))
173 throw new IllegalArgumentException("the rootFrameName \""
174 + rootFrameName +"\" is not a valid framename");
175
176 // reset flag
177 cancelFetch = false; // note that race conditions here is beside the point...meaningless
178
179 OverdubbedFrame rootODFrame = null;
180 synchronized(sharedResourceLocker) {
181 rootODFrame = allOverdubbedFrames.get(rootFrameName.toLowerCase());
182 }
183
184 if (rootODFrame != null) {
185 return rootODFrame;
186 }
187
188 // There is no overdub frame loaded for the requested frame.
189 // Thus create a new root
190 Map<String, OverdubbedFrame> newGraph = new HashMap<String, OverdubbedFrame> ();
191 rootODFrame = buildGraph(rootFrameName, newGraph); // throws InterruptedException's
192
193 // There exists no such frame or is not an actual overdubbed frame
194 if (rootODFrame == null || rootODFrame.isEmpty()) return null;
195
196 // Must run on swing thread for checking for loops before commiting the new graph to
197 // the model.
198 NewGraphCommitor commit = new NewGraphCommitor(rootODFrame, newGraph);
199 try {
200 SwingUtilities.invokeAndWait(commit);
201 } catch (InvocationTargetException e) {
202 e.printStackTrace();
203 assert(false);
204 }
205
206 // Check if commit aborted
207 if (commit.abortedCommit) {
208
209 if (cancelFetch) { // Due to cancel request?
210 throw new InterruptedException();
211 }
212
213 // Must be due to loop
214 assert (!commit.loopTrace.isEmpty());
215 throw new TrackGraphLoopException(commit.loopTrace);
216 }
217
218 return rootODFrame;
219 }
220
221 /**
222 * Assumption: that this is called from another thread other than the swing thread.
223 *
224 * @param rootFrameName
225 *
226 * @param newNodes
227 *
228 * @return
229 * Null if rootFrameName does not exist
230 *
231 * @throws InterruptedException
232 */
233 private OverdubbedFrame buildGraph(String rootFrameName, Map<String, OverdubbedFrame> newNodes)
234 throws InterruptedException
235 {
236 // Must be a frame name, not a link
237 assert(rootFrameName != null);
238 assert(FrameIO.isValidFrameName(rootFrameName));
239
240 // If cancelled the immediatly abort fetch
241 if (cancelFetch) { // check for cancel request
242 throw new InterruptedException();
243 }
244
245 // Look for existing node on previously loaded graph...
246 OverdubbedFrame oframe = null;
247 synchronized(sharedResourceLocker) {
248 oframe = allOverdubbedFrames.get(rootFrameName.toLowerCase());
249 }
250
251 // Check to see if not already created this node during this recursive call
252 if (oframe == null)
253 oframe = newNodes.get(rootFrameName.toLowerCase());
254
255 if (oframe != null) return oframe;
256
257 // There are no existing nodes ... thus create a new node from searching for
258 // the frame from file
259
260 // But first .. look in expeditee's cache since the cache might not be consistent with the file system yet
261 // .. or in the common case - the root could be the current frame.
262 ExpediteeCachedTrackInfoFetcher cacheFetch = new ExpediteeCachedTrackInfoFetcher(rootFrameName);
263
264 try {
265 SwingUtilities.invokeAndWait(cacheFetch);
266 } catch (InvocationTargetException e) {
267 e.printStackTrace();
268 assert(false);
269 }
270
271 /* Localfilename -> TrackModelData. */
272 Map<String, TrackModelData> tracks = null;
273
274 /* VirtualFilename -> LinkedTrackModelData. */
275 Map<String, LinkedTrackModelData> linkedTracks = null;
276
277 if (cacheFetch.tracks != null) {
278
279 tracks = cacheFetch.tracks;
280 assert(cacheFetch.linkedTracks != null);
281 linkedTracks = cacheFetch.linkedTracks;
282
283 } else { // search on file system
284
285 String trackPrefix = ItemUtils.GetTag(ItemUtils.TAG_IWIDGET) + ": ";
286 String linkedTrackPrefix = trackPrefix;
287
288 trackPrefix += SampledTrack.class.getName();
289 linkedTrackPrefix += LinkedTrack.class.getName();
290
291 String fullPath = null;
292 for (int i = 0; i < UserSettings.FrameDirs.get().size(); i++) { // RISKY CODE - IN EXPEDITEE SPACE FROM RANDOM TRHEAD
293 String possiblePath = UserSettings.FrameDirs.get().get(i);
294 fullPath = FrameIO.getFrameFullPathName(possiblePath, rootFrameName);
295 if (fullPath != null)
296 break;
297 }
298
299 // does even exist?
300 if (fullPath == null) {
301 return null;
302 }
303
304 try {
305
306 // Perform prefix search
307 List<TextItemSearchResult> results = ExpediteeFileTextSearch.prefixSearch(
308 fullPath,
309 new String[] {trackPrefix, linkedTrackPrefix});
310
311 // Parse search results
312 tracks = new HashMap<String, TrackModelData>();
313 linkedTracks = new HashMap<String, LinkedTrackModelData>();
314
315 for (TextItemSearchResult result : results) {
316
317 // Track widget
318 if (result.text.startsWith(trackPrefix)) {
319
320 String name = null;
321 String localFileName = null;
322 Mutable.Long initiationTime = null;
323
324 for (String data : result.data) { // read data lines
325 data = data.trim();
326
327 if (data.startsWith(SampledTrack.META_LOCALNAME_TAG) &&
328 data.length() > SampledTrack.META_LOCALNAME_TAG.length()) {
329 localFileName = data.substring(SampledTrack.META_LOCALNAME_TAG.length());
330
331 } else if (data.startsWith(TrackWidgetCommons.META_INITIATIONTIME_TAG)
332 && data.length() > TrackWidgetCommons.META_INITIATIONTIME_TAG.length()) {
333
334 try {
335 initiationTime = Mutable.createMutableLong(Long.parseLong(
336 data.substring(TrackWidgetCommons.META_INITIATIONTIME_TAG.length())));
337 } catch (NumberFormatException e) { /* Consume */ }
338
339 } else if (data.startsWith(TrackWidgetCommons.META_NAME_TAG)
340 && data.length() > TrackWidgetCommons.META_NAME_TAG.length()) {
341
342 name = data.substring(TrackWidgetCommons.META_NAME_TAG.length());
343
344 }
345 }
346
347 // Add track to map
348 if (localFileName != null) {
349 tracks.put(localFileName, new TrackModelData(
350 initiationTime, -1, name, result.position.y)); // pass -1 for running time to signify that must be read from audio file
351 }
352
353 // Linked track widget
354 } else {
355
356 assert(result.text.startsWith(linkedTrackPrefix));
357
358 // If the linked track infact has a link
359 if (result.explink != null && result.explink.length() > 0) {
360
361 Mutable.Long initiationTime = null;
362 String virtualFilename = null;
363 String name = null;
364
365 for (String data : result.data) { // read data lines
366 data = data.trim();
367
368 // OK OK, Smell in code here - duplicated from above. - sorta
369 if (data.startsWith(LinkedTrack.META_VIRTUALNAME_TAG) &&
370 data.length() > LinkedTrack.META_VIRTUALNAME_TAG.length()) {
371 virtualFilename = data.substring(LinkedTrack.META_VIRTUALNAME_TAG.length());
372
373 } else if (data.startsWith(TrackWidgetCommons.META_INITIATIONTIME_TAG)
374 && data.length() > TrackWidgetCommons.META_INITIATIONTIME_TAG.length()) {
375
376 try {
377 initiationTime = Mutable.createMutableLong(Long.parseLong(
378 data.substring(TrackWidgetCommons.META_INITIATIONTIME_TAG.length())));
379 } catch (NumberFormatException e) { /* Consume */ }
380
381 } else if (data.startsWith(TrackWidgetCommons.META_NAME_TAG)
382 && data.length() > TrackWidgetCommons.META_NAME_TAG.length()) {
383
384 name = data.substring(TrackWidgetCommons.META_NAME_TAG.length());
385
386 }
387 }
388
389 // Add linked track to map
390 if (virtualFilename != null) {
391 linkedTracks.put(virtualFilename, new LinkedTrackModelData(
392 initiationTime, result.explink, name, result.position.y)); // pass -1 for running time to signify that must be read from audio file
393 }
394
395 }
396
397
398 }
399
400 } // Proccess next result
401
402 } catch (FileNotFoundException e) {
403 e.printStackTrace();
404 return null;
405 } catch (IOException e) {
406 e.printStackTrace();
407 return null;
408 }
409
410 }
411
412 if (cancelFetch) { // check for cancel request
413 throw new InterruptedException();
414 }
415
416 assert(tracks != null);
417 assert(linkedTracks != null);
418
419 // Add new node (avoid infite recursion)
420 oframe = new OverdubbedFrame(rootFrameName);
421 newNodes.put(rootFrameName.toLowerCase(), oframe);
422
423 // Load track times from file
424 for (String localFilename : tracks.keySet()) {
425
426 if (cancelFetch) { // check for cancel request
427 throw new InterruptedException();
428 }
429
430 TrackModelData tmodel = tracks.get(localFilename);
431 if (tmodel.runningTimeMS <= 0) {
432 try {
433
434 tmodel.runningTimeMS = AudioIO.getRunningTime(AudioPathManager.AUDIO_HOME_DIRECTORY + localFilename);
435
436 } catch (IOException e) {
437 e.printStackTrace();
438 } catch (UnsupportedAudioFileException e) {
439 e.printStackTrace();
440 }
441 }
442
443 // If was able to get the runnning time ... then include in model
444 if (tmodel.runningTimeMS > 0) {
445
446 if (tmodel.initiationTime == null) {
447 tmodel.initiationTime = Mutable.createMutableLong(oframe.getFirstInitiationTime());
448 // Remember: initiation times are relative, so setting to zero
449 // could be obscure.
450 }
451
452 oframe.addTrack(new TrackGraphNode(
453 tmodel.initiationTime.value,
454 localFilename,
455 tmodel.runningTimeMS,
456 tmodel.name,
457 tmodel.ypos));
458
459 } // otheriwse omit from model.
460
461 }
462
463 // Get linked OverdubbedFrame's.. recurse
464 for (String virtualFilename : linkedTracks.keySet()) {
465
466 LinkedTrackModelData linkedTrackModel = linkedTracks.get(virtualFilename);
467
468 // At this point, the link may / maynot be absolute,
469 String linkedFrameName = linkedTrackModel.frameLink;
470 assert(linkedFrameName != null);
471
472 if (linkedFrameName.length() == 0) continue;
473
474 if (!(Character.isLetter(linkedFrameName.charAt(0)))) { // relative link
475 // Heres a trick: to get the frameset of the relative link - its just the
476 // current root framenames frameset
477 linkedFrameName = Conversion.getFramesetName(rootFrameName) + linkedFrameName; // WARNING: IN EXPEDITEE THREAD
478 }
479
480 OverdubbedFrame od = buildGraph(linkedFrameName, newNodes); // Recurse
481
482 if (od == null) { // bad link or empty frame
483 od = new OverdubbedFrame(linkedFrameName);
484 }
485
486 if (linkedTrackModel.initiationTime == null) {
487 linkedTrackModel.initiationTime = Mutable.createMutableLong(oframe.getFirstInitiationTime());
488 // Remember: initiation times are relative, so setting to zero
489 // could be obscure.
490 }
491
492 oframe.addLinkedTrack(new LinkedTracksGraphNode(
493 linkedTrackModel.initiationTime.value,
494 od,
495 virtualFilename,
496 linkedTrackModel.name,
497 linkedTrackModel.ypos));
498
499 }
500
501 return oframe;
502 }
503
504 /**
505 *
506 * @author Brook Novak
507 *
508 */
509 private class TrackModelData {
510
511 TrackModelData(Mutable.Long initiationTime, long runningTimeMS, String name, int ypos) {
512 this.initiationTime = initiationTime;
513 this.runningTimeMS = runningTimeMS;
514 this.name = name;
515 this.ypos = ypos;
516 }
517
518 /** Null if unavilable */
519 Mutable.Long initiationTime;
520
521 /** negative if unavailable */
522 long runningTimeMS;
523
524 /** Can be null. Trackname */
525 String name;
526
527 int ypos;
528 }
529
530 /**
531 *
532 * @author Brook Novak
533 *
534 */
535 private class LinkedTrackModelData {
536
537 LinkedTrackModelData(Mutable.Long initiationTime, String frameLink, String name, int ypos) {
538 assert(frameLink != null);
539 this.initiationTime = initiationTime;
540 this.frameLink = frameLink;
541 this.name = name;
542 this.ypos = ypos;
543 }
544 /** Null if unavilable */
545 Mutable.Long initiationTime;
546
547 /** The framename */
548 String frameLink;
549
550 /** Can be null. Trackname */
551 String name;
552
553 /** The y pixel position */
554 int ypos;
555 }
556
557 /**
558 * Used for fetching track info in expeditees cache
559 *
560 * @author Brook Novak
561 *
562 */
563 private class ExpediteeCachedTrackInfoFetcher implements Runnable
564 {
565 private String rootFrameName;
566
567 /** Localfilename -> TrackModelData. Not null if frame was in memory. */
568 Map<String, TrackModelData> tracks = null;
569
570 /** VirtualFilename -> LinkedTrackModelData. Not null if frame was in memory. */
571 Map<String, LinkedTrackModelData> linkedTracks = null;
572
573 ExpediteeCachedTrackInfoFetcher(String rootFrameName)
574 {
575 assert(rootFrameName != null);
576 this.rootFrameName = rootFrameName;
577 }
578
579 public void run()
580 {
581 assert(rootFrameName != null);
582
583 // Check if the current frame
584 Frame rootFrame = null;
585 if (DisplayIO.getCurrentFrame() != null && DisplayIO.getCurrentFrame().getName() != null
586 && DisplayIO.getCurrentFrame().getName().equals(rootFrameName)) {
587 rootFrame = DisplayIO.getCurrentFrame();
588 } else {
589 // Check if in cache
590 rootFrame = FrameIO.FrameFromCache(rootFrameName);
591 }
592
593 // Frame exists in memory... rummage around for track meta data
594 if (rootFrame != null) {
595 tracks = new HashMap<String, TrackModelData>();
596 linkedTracks = new HashMap<String, LinkedTrackModelData>();
597
598 for (InteractiveWidget iw : rootFrame.getInteractiveWidgets()) {
599
600 if (iw instanceof SampledTrack) {
601 SampledTrack sampledTrackWidget = (SampledTrack)iw;
602
603 TrackGraphNode tinf =
604 AudioStructureModel.getInstance().getTrackGraphInfo(
605 sampledTrackWidget.getLocalFileName(),
606 rootFrameName);
607
608 Mutable.Long initTime = (tinf != null) ? Mutable.createMutableLong(tinf.getInitiationTime()) :
609 sampledTrackWidget.getInitiationTimeFromMeta();
610
611 long runningTime = sampledTrackWidget.getRunningMSTimeFromRawAudio();
612 if (runningTime <= 0) runningTime = -1; // signify to load from file
613
614 tracks.put(sampledTrackWidget.getLocalFileName(),
615 new TrackModelData(
616 initTime,
617 runningTime,
618 sampledTrackWidget.getName(),
619 sampledTrackWidget.getY()));
620
621 // NOTE: If the audio was sotred in a recovery file - then the widget would
622 // be deleted - thus no need to consider recovery files.
623
624 } else if (iw instanceof LinkedTrack) {
625 LinkedTrack linkedTrackWidget = (LinkedTrack)iw;
626
627 LinkedTracksGraphNode ltinf =
628 AudioStructureModel.getInstance().getLinkedTrackGraphInfo(
629 linkedTrackWidget.getVirtualFilename(),
630 rootFrameName);
631
632 Mutable.Long initTime = (ltinf != null) ? Mutable.createMutableLong(ltinf.getInitiationTime()) :
633 linkedTrackWidget.getInitiationTimeFromMeta();
634
635 // Don't consider track-links without any links
636 if (linkedTrackWidget.getLink() != null) {
637 linkedTracks.put(linkedTrackWidget.getVirtualFilename(),
638 new LinkedTrackModelData(
639 initTime,
640 linkedTrackWidget.getLink(),
641 linkedTrackWidget.getName(),
642 linkedTrackWidget.getY()));
643 }
644
645 }
646
647 }
648
649 }
650 }
651 }
652
653
654 /**
655 * Used for commiting a new graph in a thread-safe way.
656 * Checks for loops in the graph before commiting.
657 *
658 * If did not commit, then {@link #abortedCommit} will be true. Otherwise it will be false.
659 *
660 * if {@link #loopTrace} is not empty after the run, then the commit
661 * was aborted because there was a loop. (And {@link #loopTrace} contains the trace).
662 * The only other reason for aborting the commit was if a cancel was requested
663 * (@see TrackGraphModel#cancelFetch)
664 *
665 * @author Brook Novak
666 *
667 *
668 */
669 private class NewGraphCommitor implements Runnable
670 {
671 private final OverdubbedFrame rootODFrame;
672 private final Map<String, OverdubbedFrame> newGraph;
673
674 private boolean abortedCommit = false;
675
676 /** If not empty after the run, then the commit */
677 Stack<OverdubbedFrame> loopTrace = new Stack<OverdubbedFrame>();
678
679 NewGraphCommitor(OverdubbedFrame rootODFrame, Map<String, OverdubbedFrame> newGraph)
680 {
681 assert(rootODFrame != null);
682 assert(newGraph != null);
683
684 this.rootODFrame = rootODFrame;
685 this.newGraph = newGraph;
686 }
687
688 public void run()
689 {
690
691 // Theoretically this would never need to be called ... but for super safety
692 // keep sync block - since dealing with shared resources.
693 synchronized(sharedResourceLocker) {
694
695 // At anytime during this point, the new graph could become invalid
696 // during this time while officially adding it to the "consistant" model..
697 // which is obviously BAD.
698 if (cancelFetch) {
699 abortedCommit = true;
700 return; // important: while locked shared resources
701 }
702
703 // Check that the graph is loop free.
704 boolean ilf = isLoopFree(rootODFrame, loopTrace);
705 if (ilf) { // check from existing roots POV's
706 for (OverdubbedFrame existingRoot : graphRoots) {
707 loopTrace.clear();
708 ilf = isLoopFree(existingRoot, loopTrace);
709 if (!ilf) break;
710 }
711 }
712
713 if (!ilf) {
714 assert (!loopTrace.isEmpty()); // loopTrace will contain the trace
715 abortedCommit = true;
716 return;
717 } else {
718 assert (loopTrace.isEmpty());
719 }
720
721 // Since we are creating a new root, existing roots might
722 // be reachable from the new root, thus remove reachable
723 // roots ... since they are no longer roots ...
724 List<OverdubbedFrame> redundantRoots = new LinkedList<OverdubbedFrame>();
725 for (OverdubbedFrame existingRoot : graphRoots) {
726 if (rootODFrame.getChild(existingRoot.getFrameName()) != null) {
727 redundantRoots.add(existingRoot); // this root is reachable from the new root
728 }
729 }
730
731 // Get rid of the redundant roots
732 graphRoots.removeAll(redundantRoots);
733
734 // Commit the new loop-free graph to the model
735 graphRoots.add(rootODFrame); // add the mutex or superceeding root
736 allOverdubbedFrames.putAll(newGraph); // Include new graph into all allOverdubbedFrames
737 }
738 }
739 }
740//
741// /**
742// * Determines if a node is reachable from a given node.
743// *
744// * @param source
745// * Must not be null.
746// *
747// * @param target
748// * Must not be null.
749// *
750// * @param visited
751// * Must be empty. Must not be null.
752// *
753// * @return
754// * True if target is reachable via source.
755// */
756// private boolean isReachable(OverdubbedFrame source, OverdubbedFrame target, Set<OverdubbedFrame> visited) {
757// assert(source != null);
758// assert(target != null);
759// assert(visited != null);
760//
761// // Base cases
762// if (source == target) return true;
763// else if (visited.contains(source)) return false;
764//
765// // Remember visited node
766// visited.add(source);
767//
768// // Recurse
769// for (Iterator<LinkedTracksGraphInfo> itor = source.getLinkedTrackIterator(); itor.hasNext();) {
770// LinkedTracksGraphInfo lti = itor.next();
771//
772// if (isReachable(lti.getLinkedFrame(), target, visited)) {
773// return true;
774// }
775// }
776//
777// // Reached end of this nodes links - found no match
778// return false;
779// }
780
781 /**
782 * Determines if a graph is loop free from a starting point.
783 * <B>MUST BE ON THE SWING THREAD</B>
784 *
785 * @param current
786 * Where to look for loops from.
787 *
788 * @param visitedStack
789 * Must be empty. Used internally. If the
790 * function returns false, then the stack will provde a trace of the loop.
791 *
792 * @return
793 * True if the graph at current is loop free.
794 */
795 private boolean isLoopFree(OverdubbedFrame current, Stack<OverdubbedFrame> visitedStack) {
796
797 if (visitedStack.contains(current)) { // found a loop
798 visitedStack.add(current); // add to strack for loop trace.
799 return false;
800 }
801
802 visitedStack.push(current); // save current node to stack
803
804 for (LinkedTracksGraphNode tl : current.getLinkedTracksCopy()) {
805
806 boolean ilf = isLoopFree(tl.getLinkedFrame(), visitedStack);
807
808 if (!ilf) return false;
809
810 }
811
812 visitedStack.pop(); // pop the current node
813
814 return true;
815
816 }
817
818 /**
819 * Gets a TrackGraphInfo. <b>MUST BE ON EXPEDITEE THREAD</b>
820 *
821 * @param localFilename
822 * Must not be null.
823 *
824 * @param parentFrameName
825 * If null - the search will be slower.
826 *
827 * @return
828 * The TrackGraphInfo for the given track. Null if not in model
829 */
830 public TrackGraphNode getTrackGraphInfo(String localFilename, String parentFrameName) {
831
832 synchronized(sharedResourceLocker) {
833
834 assert(localFilename != null);
835
836 if (parentFrameName != null) {
837 OverdubbedFrame odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
838
839
840 if (odframe != null) {
841 return odframe.getTrack(localFilename);
842 }
843
844 } else { // parentFrameName is null .. do search
845
846 for (OverdubbedFrame odframe : allOverdubbedFrames.values()) {
847 TrackGraphNode tinf = odframe.getTrack(localFilename);
848 if (tinf != null) return tinf;
849 }
850 }
851 }
852
853 return null;
854 }
855
856
857 /**
858 * Gets a TrackGraphInfo. <b>MUST BE ON EXPEDITEE THREAD</b>
859 *
860 * @param virtualFilename
861 * Must not be null.
862 *
863 * @param parentFrameName
864 * If null - the search will be slower.
865 *
866 * @return
867 * The LinkedTracksGraphInfo for the given virtualFilename. Null if not in model
868 */
869 public LinkedTracksGraphNode getLinkedTrackGraphInfo(String virtualFilename, String parentFrameName) {
870
871 synchronized(sharedResourceLocker) {
872
873 assert(virtualFilename != null);
874
875 if (parentFrameName != null) {
876 OverdubbedFrame odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
877
878
879 if (odframe != null) {
880 return odframe.getLinkedTrack(virtualFilename);
881 }
882
883 } else {
884
885 for (OverdubbedFrame odframe : allOverdubbedFrames.values()) {
886 LinkedTracksGraphNode ltinf = odframe.getLinkedTrack(virtualFilename);
887 if (ltinf != null) return ltinf;
888 }
889
890 }
891 }
892
893 return null;
894 }
895
896
897 /**
898 * <B>MUST BE ON SWING THREAD</B>.
899 * Gets the parent ODFrame of a track - given the tracks local filename.
900 *
901 * @param localFilename
902 * The local filename of the track to get the frame for. Must not be null.
903 *
904 * @return
905 * The parent. Null if no parent exists.
906 */
907 public OverdubbedFrame getParentOverdubbedFrame(String localFilename) {
908 assert(localFilename != null);
909
910 synchronized (allOverdubbedFrames) {
911 for (OverdubbedFrame odf : allOverdubbedFrames.values()) {
912 if (odf.containsTrack(localFilename)) {
913 return odf;
914 }
915 }
916 }
917
918 return null;
919 }
920
921 /**
922 * Gets an overdubbed frame representation of a given frame.
923 *
924 * @param framename
925 * The name of the frame.
926 *
927 * @return
928 * The OverdubbedFrame for the given framename. Null if does not exist.
929 */
930 public OverdubbedFrame getOverdubbedFrame(String framename) {
931 assert(framename != null);
932
933 synchronized (allOverdubbedFrames) {
934 return allOverdubbedFrames.get(framename.toLowerCase());
935 }
936 }
937
938 /**
939 * <B>MUST BE ON SWING THREAD</B>.
940 * Keeps model consistent with expeditee.
941 *
942 * @param localFilename
943 * Must not be null.
944 *
945 * @param parentFrameName
946 * Can be null.
947 *
948 * @param currentRunningTime
949 * I.e. the new running time after the edit. Must be larger than zero. In milliseconds.
950 */
951 public void onTrackWidgetAudioEdited(String localFilename, String parentFrameName, long currentRunningTime) {
952
953 boolean doNotify = false;
954 TrackGraphNode tinf = null;
955
956 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
957
958 assert(localFilename != null);
959 assert (currentRunningTime > 0);
960
961 // Locate parent frame
962 OverdubbedFrame odframe = null;
963
964 if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
965 else odframe = getParentOverdubbedFrame(localFilename);
966
967 // adjust running time in model
968 if (odframe != null) { // is loaded?
969 tinf = odframe.getTrack(localFilename);
970 assert(tinf != null); // due to the assumption that the model is consistent
971 if (tinf.getRunningTime() != currentRunningTime) {
972 tinf.setRunningTime(currentRunningTime);
973 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
974 // It will die in its own time - and will always be cancelled because it locks
975 // the current locked object.
976 cancelFetch = doNotify = true;
977 }
978 }
979
980
981 }
982
983 // Notify observers.
984 if (doNotify)
985 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_TRACK_EDITED, tinf));
986 }
987
988 /**
989 * <B>MUST BE ON SWING THREAD</B>.
990 * Keeps model consistent with expeditee.
991 *
992 * @param localFilename
993 * Must not be null.
994 *
995 * @param parentFrameName
996 * Can be null.
997 *
998 * @param newInitiationTime
999 * In milliseconds.Relative - i.e. can be negative
1000 *
1001 * @param name
1002 * The name given to the linked track. Can be null if there is no name...
1003 *
1004 * @param ypos
1005 * The Y-pixel position of the track.
1006 *
1007 * @param currentRunningTime
1008 * Must be larger than zero. In milliseconds. Used in case the model has not been create for the widget.
1009 */
1010 public void onTrackWidgetAnchored(
1011 String localFilename, String parentFrameName,
1012 long newInitiationTime, long currentRunningTime,
1013 String name, int ypos) {
1014
1015 boolean doNotify = false;
1016 TrackGraphNode tinf = null;
1017
1018 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1019
1020 assert(localFilename != null);
1021
1022 // Locate parent frame
1023 OverdubbedFrame odframe = null;
1024
1025 if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1026 else odframe = getParentOverdubbedFrame(localFilename);
1027
1028 // adjust initiation time in model
1029 if (odframe != null) { // is loaded?
1030 tinf = odframe.getTrack(localFilename);
1031
1032 if (tinf != null && tinf.getInitiationTime() != newInitiationTime) {
1033 tinf.setInitiationTime(newInitiationTime);
1034 cancelFetch = doNotify = true;
1035
1036 } else { // if there is no model but overdub frame is in memory - then create new model for this track.
1037
1038 tinf = new TrackGraphNode(
1039 newInitiationTime,
1040 localFilename,
1041 currentRunningTime,
1042 name,
1043 ypos);
1044
1045 odframe.addTrack(tinf); // safe because on swing thread
1046
1047 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1048 cancelFetch = doNotify = true;
1049 // It will die in its own time - and will always be cancelled because it locks
1050 // the current locked object.
1051 }
1052
1053
1054 }
1055
1056 }
1057
1058 // Notify observers.
1059 if (doNotify)
1060 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_TRACK_ADDED, tinf));
1061
1062 }
1063
1064 /**
1065 * <B>MUST BE ON SWING THREAD</B>.
1066 * Keeps model consistent with expeditee.
1067 *
1068 * Note: Invoke when picked up - not when a frame changes... also if it
1069 * is removed due to XRaymode - in that case the whole model will be released.
1070 *
1071 * @param localFilename
1072 * Must not be null.
1073 *
1074 * @param parentFrameName
1075 * Can be null. If given will be faster.
1076 *
1077 */
1078 public void onTrackWidgetRemoved(String localFilename, String parentFrameName) {
1079 TrackGraphNode tinf = null;
1080 boolean doNotify = false;
1081
1082 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1083
1084 if (FrameGraphics.isXRayMode()) { // discard whole model
1085
1086 // Neccessary because if the user goes into xray then moves to a new frame
1087 // then it wil screw everything up. Also note they can delete the text source items
1088 // in xray mode.
1089 // This basically will cause short load times to occur again...
1090 allOverdubbedFrames.clear();
1091 graphRoots.clear();
1092
1093 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1094 cancelFetch = doNotify = true;
1095 // It will die in its own time - and will always be cancelled because it locks
1096 // the current locked object.
1097
1098 } else {
1099
1100 // Locate parent frame
1101 OverdubbedFrame odframe = null;
1102
1103 if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1104 else odframe = getParentOverdubbedFrame(localFilename);
1105
1106 if (odframe != null) {
1107 tinf = odframe.removeTrack(localFilename);
1108 //assert(tinf != null);
1109 cancelFetch = doNotify = true;
1110 }
1111
1112 }
1113
1114 }
1115
1116 // Notify observers.
1117 if (doNotify)
1118 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_TRACK_REMOVED, tinf));
1119 }
1120
1121 /**
1122 * <B>MUST BE ON SWING THREAD</B>.
1123 * Keeps model consistent with expeditee.
1124 *
1125 * @param localFilename
1126 * Must not be null.
1127 *
1128 * @param parentFrameName
1129 * Can be null.
1130 *
1131 * @param currentRunningTime
1132 * I.e. the new running time after the edit. Must be larger than zero. In milliseconds.
1133 */
1134 public void onTrackWidgetNameChanged(String localFilename, String parentFrameName, String newName) {
1135
1136 boolean doNotify = false;
1137
1138 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1139
1140 assert(localFilename != null);
1141
1142 // Locate parent frame
1143 OverdubbedFrame odframe = null;
1144
1145 if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1146 else odframe = getParentOverdubbedFrame(localFilename);
1147
1148 // adjust name in model
1149 if (odframe != null) { // is loaded?
1150 AbstractTrackGraphNode tinf = odframe.getTrack(localFilename);
1151 assert(tinf != null); // due to the assumption that the model is consistent
1152 if (tinf.getName() != newName) {
1153 tinf.setName(newName);
1154 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1155 cancelFetch = doNotify = true;
1156 // It will die in its own time - and will always be cancelled because it locks
1157 // the current locked object.
1158 }
1159 }
1160
1161 }
1162
1163 // Notify observers.
1164 if (doNotify)
1165 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.NAME_CHANGED, localFilename));
1166 }
1167
1168
1169 /**
1170 * <B>MUST BE ON SWING THREAD</B>.
1171 * Keeps model consistent with expeditee.
1172 *
1173 * @param localFilename
1174 * Must not be null.
1175 *
1176 * @param parentFrameName
1177 * Can be null.
1178 *
1179 * @param initTime
1180 * The initiation time in ms
1181 *
1182 * @param yPos
1183 * The y pixel position.
1184 *
1185 */
1186 public void onTrackWidgetPositionChanged(String localFilename, String parentFrameName, long initTime, int yPos) {
1187
1188 boolean doNotify = false;
1189 AbstractTrackGraphNode tinf = null;
1190
1191 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1192
1193 assert(localFilename != null);
1194
1195 // Locate parent frame
1196 OverdubbedFrame odframe = null;
1197
1198 if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1199 else odframe = getParentOverdubbedFrame(localFilename);
1200
1201 // adjust name in model
1202 if (odframe != null) { // is loaded?
1203 tinf = odframe.getTrack(localFilename);
1204 if (tinf != null &&
1205 (tinf.getInitiationTime() != initTime || tinf.getYPixelPosition() != yPos)) {
1206 tinf.setInitiationTime(initTime);
1207 tinf.setYPixelPosition(yPos);
1208 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1209 cancelFetch = doNotify = true;
1210 // It will die in its own time - and will always be cancelled because it locks
1211 // the current locked object.
1212 }
1213 }
1214
1215 }
1216
1217 // Notify observers.
1218 if (doNotify)
1219 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_TRACK_POSITION_CHANGED, tinf));
1220 }
1221
1222
1223 /**
1224 * <B>MUST BE ON SWING THREAD</B>.
1225 * Keeps model consistent with expeditee.
1226 *
1227 * @param virtualFilename
1228 * Must not be null.
1229 *
1230 * @param parentFrameName
1231 * Can be null.
1232 *
1233 * @param initTime
1234 * The initiation time in ms
1235 *
1236 * @param yPos
1237 * The y pixel position.
1238 *
1239 */
1240 public void onLinkedTrackWidgetPositionChanged(String virtualFilename, String parentFrameName, long initTime, int yPos) {
1241
1242 boolean doNotify = false;
1243 AbstractTrackGraphNode tinf = null;
1244
1245 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1246
1247 assert(virtualFilename != null);
1248
1249 // Locate parent frame
1250 OverdubbedFrame odframe = null;
1251
1252 if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1253 else odframe = getParentOverdubbedFrame(virtualFilename);
1254
1255 // adjust name in model
1256 if (odframe != null) { // is loaded?
1257 tinf = odframe.getLinkedTrack(virtualFilename);
1258 if (tinf != null &&
1259 (tinf.getInitiationTime() != initTime || tinf.getYPixelPosition() != yPos)) {
1260
1261 tinf.setInitiationTime(initTime);
1262 tinf.setYPixelPosition(yPos);
1263
1264 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1265 cancelFetch = doNotify = true;
1266 // It will die in its own time - and will always be cancelled because it locks
1267 // the current locked object.
1268 }
1269 }
1270 }
1271
1272 // Notify observers.
1273 if (doNotify)
1274 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_LINKED_TRACK_POSITION_CHANGED, tinf));
1275 }
1276
1277 /**
1278 * <B>MUST BE ON SWING THREAD</B>.
1279 * Keeps model consistent with expeditee.
1280 *
1281 * @param virtualFilename
1282 * Must not be null.
1283 *
1284 * @param parentFrameName
1285 * Can be null.
1286 *
1287 * @param currentRunningTime
1288 * I.e. the new running time after the edit. Must be larger than zero. In milliseconds.
1289 */
1290 public void onLinkedTrackWidgetNameChanged(String virtualFilename, String parentFrameName, String newName) {
1291
1292 boolean doNotify = false;
1293
1294 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1295
1296 assert(virtualFilename != null);
1297
1298 // Locate parent frame
1299 OverdubbedFrame odframe = null;
1300
1301 if (parentFrameName != null) odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1302 else odframe = getParentOverdubbedFrame(virtualFilename);
1303
1304 // adjust name in model
1305 if (odframe != null) { // is loaded?
1306 AbstractTrackGraphNode tinf = odframe.getLinkedTrack(virtualFilename);
1307 if (tinf != null && tinf.getName() != newName) {
1308 tinf.setName(newName);
1309 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1310 cancelFetch = doNotify = true;
1311 // It will die in its own time - and will always be cancelled because it locks
1312 // the current locked object.
1313 }
1314 }
1315 }
1316
1317 // Notify observers.
1318 if (doNotify)
1319 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.NAME_CHANGED, virtualFilename));
1320 }
1321
1322
1323 /**
1324 * <B>MUST BE ON SWING THREAD</B>.
1325 * Keeps model consistent with expeditee.
1326 *
1327 * Doe not use for changing a the widgets link. Must first remove, then acnhor with new link -
1328 * tracklinks links are immutable.
1329 *
1330 * @param virtualFilename
1331 * Must not be null.
1332 *
1333 * @param parentFrameName
1334 * Must not be null.
1335 *
1336 * @param newInitiationTime
1337 * In milliseconds.Relative - i.e. can be negative
1338 *
1339 * @param absoluteLinkedFrame
1340 * Must not be null. Must be a valid framename (absolute).
1341 *
1342 * @param name
1343 * The name given to the linked track. Can be null if there is no name...
1344 *
1345 * @param ypos
1346 * The Y-pixel position of the track.
1347 *
1348 */
1349 public void onLinkedTrackWidgetAnchored(
1350 String virtualFilename, String parentFrameName,
1351 long newInitiationTime, String absoluteLinkedFrame,
1352 String name, int ypos) {
1353
1354 boolean doNotify = false;
1355
1356 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1357
1358 assert(virtualFilename != null);
1359 assert(parentFrameName != null);
1360 assert(absoluteLinkedFrame != null);
1361 assert(FrameIO.isValidFrameName(absoluteLinkedFrame));
1362
1363 // Locate parent frame
1364 OverdubbedFrame odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1365
1366 if (odframe != null) { // is loaded?
1367
1368 LinkedTracksGraphNode linkInf = odframe.getLinkedTrack(virtualFilename);
1369
1370 if (linkInf != null) {
1371
1372 // Update the initation time
1373 linkInf.setInitiationTime(newInitiationTime);
1374
1375 // The link should be consistant - check for miss-use of procedure call
1376 assert(linkInf.getLinkedFrame().getFrameName().equalsIgnoreCase(absoluteLinkedFrame));
1377
1378 } else { // if there is no model but overdub frame is in memory - then create new model for this track.
1379
1380 updateLater(new LinkedTrackUpdate(
1381 newInitiationTime, absoluteLinkedFrame,
1382 virtualFilename, parentFrameName, name, ypos));
1383
1384 }
1385
1386 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1387 cancelFetch = doNotify = true;
1388 // It will die in its own time - and will always be cancelled because it locks
1389 // the current locked object.
1390 }
1391
1392 }
1393
1394 // Notify observers.
1395 if (doNotify)
1396 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_LINKED_TRACK_ADDED, virtualFilename));
1397
1398 }
1399
1400 /**
1401 * <B>MUST BE ON SWING THREAD</B>.
1402 * Keeps model consistent with expeditee.
1403 *
1404 * Note: Invoke when picked up - not when a frame changes... also if it
1405 * is removed due to XRaymode - in that case the whole model will be released.
1406 *
1407 * <b>Also</b> invoke if the track tracks link has changed.
1408 *
1409 * @param virtualFilename
1410 * Must not be null.
1411 *
1412 * @param parentFrameName
1413 * Can be null. If given will be faster.
1414 *
1415 */
1416 public void onLinkedTrackWidgetRemoved(String virtualFilename, String parentFrameName) {
1417
1418 boolean doNotify = false;
1419
1420 synchronized(sharedResourceLocker) { // IMPORTANT: Must wait for new graphs to be added to the shared resources
1421
1422
1423 if (FrameGraphics.isXRayMode()) { // discard whole model
1424
1425 // Neccessary because if the user goes into xray then moves to a new frame
1426 // then it wil screw everything up. Also note they can delete the text source items
1427 // in xray mode.
1428 // This basically will cause short load times to occur again...
1429 allOverdubbedFrames.clear();
1430 graphRoots.clear();
1431
1432
1433 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1434 cancelFetch = doNotify = true;
1435 // It will die in its own time - and will always be cancelled because it locks
1436 // the current locked object.
1437 } else {
1438
1439 // Locate parent frame
1440 OverdubbedFrame odframe = null;
1441
1442 if (parentFrameName != null) {
1443 odframe = allOverdubbedFrames.get(parentFrameName.toLowerCase());
1444 } else {
1445 for (OverdubbedFrame odf : allOverdubbedFrames.values()) {
1446 if (odf.containsLinkedTrack(virtualFilename)) {
1447 odframe = odf;
1448 break;
1449 }
1450 }
1451 }
1452
1453 if (odframe != null) {
1454 LinkedTracksGraphNode linkInf = odframe.getLinkedTrack(virtualFilename);
1455
1456 if (linkInf != null) {
1457 boolean didRemove = odframe.removeLinkedTrack(linkInf);
1458 assert(didRemove);
1459
1460 // Track links are the actual links in the directed graph. THus if they are
1461 // removed then the graph must be checked for creating new root nodes.
1462 // That is, the removed link may have isolated a node (or group of nodes)
1463 // for which must be reachable via their own start state...
1464 boolean isReachable = false;
1465 for (OverdubbedFrame existingRoot : graphRoots) {
1466 if (existingRoot.getChild(linkInf.getLinkedFrame().getFrameName()) != null) {
1467 isReachable = true;
1468 break;
1469 }
1470
1471 }
1472
1473 // Ensure that the frame is reachable
1474 if (!isReachable) {
1475 graphRoots.add(linkInf.getLinkedFrame());
1476 }
1477
1478 // Note: a fetch might be waiting on this - i.e in progress. Thus must be cancelled.
1479 cancelFetch = doNotify = true;
1480 // It will die in its own time - and will always be cancelled because it locks
1481 // the current locked object.
1482
1483 }
1484
1485
1486 }
1487
1488 }
1489
1490 }
1491
1492 // Notify observers.
1493 if (doNotify)
1494 fireSubjectChanged(new SubjectChangedEvent(ApolloSubjectChangedEvent.GRAPH_LINKED_TRACK_REMOVED, virtualFilename));
1495 }
1496
1497 /**
1498 * @return
1499 * True if the graph model is updating.
1500 */
1501 public boolean isUpdating() {
1502 return (delayedModelUpdator != null && delayedModelUpdator.isAlive());
1503 }
1504
1505
1506 /**
1507 * Waits for updates to finish.
1508 *
1509 * <b>MUST NOT BE ON SWING THREAD - OR MAY DEADLOCK</b>
1510 *
1511 * @throws InterruptedException
1512 * if any thread has interrupted the current thread
1513 */
1514 public void waitOnUpdates() throws InterruptedException {
1515 if (delayedModelUpdator != null && delayedModelUpdator.isAlive()) {
1516 delayedModelUpdator.join();
1517 }
1518 }
1519
1520 /**
1521 * Queues an update for the consistant model to be updated later... since it may take some time.
1522 *
1523 * @param update
1524 * The update to do. Must not be null.
1525 */
1526 private void updateLater(LinkedTrackUpdate update) {
1527
1528 synchronized(updateQueue) {
1529
1530 assert(update != null);
1531
1532 // Add the update tot he queu
1533 updateQueue.add(update);
1534
1535 // Ensure that the update thread is alive
1536 if (delayedModelUpdator == null || !delayedModelUpdator.isAlive()) {
1537 delayedModelUpdator = new DelayedModelUpdator();
1538 delayedModelUpdator.start();
1539 }
1540
1541 }
1542
1543 }
1544
1545 private Queue<LinkedTrackUpdate> updateQueue = new LinkedList<LinkedTrackUpdate>(); // SHARED RESOURCE
1546
1547 /**
1548 * Used for queuing update data.
1549 *
1550 *
1551 * @author Brook Novak
1552 *
1553 */
1554 private class LinkedTrackUpdate extends LinkedTrackModelData {
1555
1556 LinkedTrackUpdate(
1557 long initiationTime,
1558 String absoluteLink,
1559 String virtualFilename,
1560 String parentFrameToAddTo,
1561 String name,
1562 int ypos) {
1563
1564 super(Mutable.createMutableLong(initiationTime), absoluteLink, name, ypos);
1565
1566 assert(virtualFilename != null);
1567 assert(parentFrameToAddTo != null);
1568 assert(FrameIO.isValidFrameName(absoluteLink));
1569
1570 this.virtualFilename = virtualFilename;
1571 this.parentFrameToAddTo = parentFrameToAddTo;
1572 }
1573
1574 String virtualFilename;
1575 String parentFrameToAddTo;
1576
1577 }
1578
1579 /**
1580 *
1581 * @author Brook Novak
1582 *
1583 */
1584 private class DelayedModelUpdator extends Thread {
1585
1586 public void run() {
1587 while (true) {
1588 LinkedTrackUpdate update;
1589 synchronized(updateQueue) {
1590 if (updateQueue.isEmpty()) return; // important: only quits when updateQueue is synched
1591 update = updateQueue.poll();
1592 }
1593
1594 assert(update != null);
1595
1596 // Keep trying the update until success or encountered loops
1597 while (true) {
1598 try {
1599
1600 OverdubbedFrame odframe = fetchGraph(update.frameLink);
1601 if (odframe == null) { // does not exist or is empty...
1602 odframe = new OverdubbedFrame(update.frameLink);
1603 }
1604
1605
1606 // add to parent only if does not already exist - also being aware of loops
1607 final LinkedTrackUpdate updateData = update;
1608 final OverdubbedFrame linkedODFrame = odframe;
1609
1610 try {
1611 SwingUtilities.invokeAndWait(new Runnable() {
1612
1613 public void run() { // on swing thread
1614
1615 synchronized(sharedResourceLocker) {
1616
1617 try { // The fetch will be cancelled after this block - within the lock
1618
1619 OverdubbedFrame parentFrame = allOverdubbedFrames.get(updateData.parentFrameToAddTo.toLowerCase());
1620 if (parentFrame == null) {
1621 // No need to both adding - because model does not even reference this new link...
1622 // must have cleared while updating... e.g. user could have switched into xray mode.
1623 return;
1624 }
1625
1626 assert(updateData.initiationTime != null);
1627
1628 // Create the linked track instance
1629 LinkedTracksGraphNode linktInf = new LinkedTracksGraphNode(
1630 updateData.initiationTime.value,
1631 linkedODFrame,
1632 updateData.virtualFilename,
1633 updateData.name,
1634 updateData.ypos);
1635
1636 // Check that the graph will be loop free
1637 parentFrame.addLinkedTrack(linktInf); // ADDING TEMPORARILY
1638
1639 // Check from parent frame
1640 Stack<OverdubbedFrame> loopTrace = new Stack<OverdubbedFrame>();
1641 if (!isLoopFree(parentFrame, loopTrace)) {
1642 parentFrame.removeLinkedTrack(linktInf);
1643 // Ignore link - has loop
1644 return;
1645 }
1646
1647 // Check for all existing graph roots
1648 for (OverdubbedFrame existingRoot : graphRoots) {
1649 loopTrace.clear();
1650 if (!isLoopFree(existingRoot, loopTrace)) {
1651 parentFrame.removeLinkedTrack(linktInf);
1652 // Ignore link - has loop
1653 return;
1654 }
1655 }
1656
1657 // Otherwise leave the link in its place
1658
1659 // Ensure that the linked frame is in the allOverdubbedFrames set
1660 allOverdubbedFrames.put(linkedODFrame.getFrameName().toLowerCase(), linkedODFrame);
1661
1662 } finally { // IMPORTANT: CANCEL WHILE LOCKED
1663
1664 // Cancel any fetches
1665 cancelFetch = true;
1666 }
1667
1668 } // release lock
1669
1670
1671 }
1672 });
1673 } catch (InvocationTargetException e) {
1674 e.printStackTrace();
1675 assert(false);
1676 }
1677
1678 // Done with this update...
1679 break;
1680
1681
1682 } catch (InterruptedException e) { // Canceled
1683 // Consume and retry
1684
1685 } catch (TrackGraphLoopException e) { // bad link
1686 // Consume - since is fine - the model will just ignore the link
1687 break;
1688 }
1689 } // retry fetch
1690
1691 } // proccess next update
1692
1693 } // finished updating
1694 }
1695
1696}
Note: See TracBrowser for help on using the repository browser.