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

Last change on this file since 903 was 903, checked in by davidb, 10 years ago

Changes needed after restructuring of Settings code/classes

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