001: /*
002: * Jacareto Copyright (c) 2002-2005
003: * Applied Computer Science Research Group, Darmstadt University of
004: * Technology, Institute of Mathematics & Computer Science,
005: * Ludwigsburg University of Education, and Computer Based
006: * Learning Research Group, Aachen University. All rights reserved.
007: *
008: * Jacareto is free software; you can redistribute it and/or
009: * modify it under the terms of the GNU General Public
010: * License as published by the Free Software Foundation; either
011: * version 2 of the License, or (at your option) any later version.
012: *
013: * Jacareto is distributed in the hope that it will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
016: * General Public License for more details.
017: *
018: * You should have received a copy of the GNU General Public
019: * License along with Jacareto; if not, write to the Free
020: * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
021: *
022: */
023:
024: package jacareto.trackimpl;
025:
026: import jacareto.record.AudioClipRecordable;
027: import jacareto.record.MediaClipRecordable;
028: import jacareto.record.Record;
029: import jacareto.record.Recordable;
030: import jacareto.record.VectorRecord;
031: import jacareto.record.VideoClipRecordable;
032: import jacareto.track.TrackModel;
033: import jacareto.track.TrackModelEvent;
034: import jacareto.track.TrackModelListener;
035: import jacareto.track.block.Block;
036: import jacareto.track.block.BlockType;
037: import jacareto.trackimpl.blockimpl.DefaultBlock;
038: import jacareto.trackimpl.blockimpl.DefaultBlockFactory;
039: import jacareto.trackimpl.blockimpl.MediaBlock;
040:
041: import org.apache.commons.lang.Validate;
042: import org.apache.log4j.Logger;
043:
044: import java.util.ArrayList;
045: import java.util.Arrays;
046: import java.util.Collections;
047: import java.util.Comparator;
048: import java.util.HashMap;
049: import java.util.HashSet;
050: import java.util.Iterator;
051: import java.util.List;
052: import java.util.Map;
053: import java.util.Set;
054: import java.util.Vector;
055:
056: /**
057: * <p>
058: * Implementation of the {@link TrackModel} interface.
059: * </p>
060: *
061: * <p>
062: * The DefaultTrackModel holds all the Recordables which are visible in the {@link TrackViewPanel}
063: * </p>
064: *
065: * @author Oliver Specht
066: * @version $revision$
067: */
068: public class DefaultTrackModel implements TrackModel {
069: /** The Logger */
070: private static Logger LOG = Logger
071: .getLogger(DefaultTrackModel.class);
072:
073: /** The {@link TrackModelListener TrackModelListeners} this TrackModel contains */
074: private Set listeners = new HashSet();
075:
076: /** true if listeners should be informed */
077: private boolean isListenersEnabled = true;
078:
079: /** The Map which holds all start times */
080: private Map blockStartTimes = new HashMap();
081:
082: /** Holds all the blockEndTimes referenced by the end times of the blockEndTimes */
083: private Map blockEndTimes = new HashMap();
084:
085: /** Holds all aligned blocks with "aligned to" block as the key */
086: private Map alignedBlocks = new HashMap();
087:
088: private DefaultTrackModel(Record record) {
089: Validate.notNull(record);
090:
091: init(record);
092: }
093:
094: /**
095: * <p>
096: * Initializes the {@link TrackModel DefaultTrackModel} with the data from the given {@link
097: * Record record}.
098: * </p>
099: *
100: * @param record {@link Record}
101: */
102: private void init(Record record) {
103: long recordTime = 0;
104: Vector records = null;
105:
106: records = ((VectorRecord) record).getVector();
107:
108: Iterator iter = records.iterator();
109:
110: // add blocks to model including their start times
111: while (iter.hasNext()) {
112: Recordable recordable = (Recordable) iter.next();
113: Block blockToAdd = null;
114:
115: blockToAdd = DefaultBlockFactory.getInstance()
116: .createCustomBlock(recordable);
117:
118: Validate.notNull(blockToAdd);
119:
120: this .isListenersEnabled = false;
121:
122: addBlock(blockToAdd, recordTime);
123: this .isListenersEnabled = true;
124:
125: if (!(recordable instanceof MediaClipRecordable)) {
126: recordTime += recordable.getDuration();
127: }
128: }
129:
130: // calculateRecordEndTimes ();
131: this .setAlignments();
132: fireTrackModelChanged();
133: }
134:
135: void fireTrackModelChanged() {
136: if (this .isListenersEnabled) {
137: Iterator i = listeners.iterator();
138:
139: while (i.hasNext()) {
140: ((TrackModelListener) i.next())
141: .trackModelChanged(new TrackModelEvent(this ));
142: }
143: }
144: }
145:
146: /**
147: * Returns the block which contains the given recordable
148: *
149: * @param recordable {@link Recordable}
150: *
151: * @return Block
152: */
153: public Block getBlock(Recordable recordable) {
154: Validate.notNull(recordable, "The given Recordable is null!");
155: Validate.notNull(this .blockStartTimes);
156:
157: Iterator iter = this .blockStartTimes.keySet().iterator();
158:
159: while (iter.hasNext()) {
160: DefaultBlock block = (DefaultBlock) iter.next();
161:
162: if (block.equals(recordable)) {
163: return block;
164: }
165: }
166:
167: return null;
168: }
169:
170: /**
171: * Reinitializes the TrackModel with the given {@link Record}
172: *
173: * @param record {@link Record}
174: */
175: public void reInit(Record record) {
176: Validate.notNull(record);
177:
178: this .alignedBlocks.clear();
179: this .blockEndTimes.clear();
180: this .blockStartTimes.clear();
181:
182: init(record);
183: }
184:
185: /**
186: * Parses all blocks for alignments and sets the alignedTo blocks in the {@link
187: * DefaultTrackModel#alignedBlocks} list.
188: */
189: private void setAlignments() {
190: List blocks;
191:
192: for (int i = 0; i < 2; i++) {
193: if (i == 0) {
194: blocks = this .getBlocksInRange(BlockType.AUDIO, 0, this
195: .getEndTime(), false);
196: } else {
197: blocks = this .getBlocksInRange(BlockType.VIDEO, 0, this
198: .getEndTime(), false);
199: }
200:
201: Iterator blockIter = blocks.iterator();
202:
203: while (blockIter.hasNext()) {
204: MediaBlock block = (MediaBlock) blockIter.next();
205:
206: Recordable alignedTo = null;
207:
208: alignedTo = ((MediaClipRecordable) block
209: .getRecordable()).getAlignedTo();
210:
211: if (alignedTo != null) {
212: Block alignedToBlock = DefaultBlockFactory
213: .getInstance().createCustomBlock(alignedTo);
214: this .setEndAlignment(block, alignedToBlock);
215: }
216: }
217: }
218: }
219:
220: /**
221: * Creates a new TrackModel from the given TreeModel.
222: *
223: * @param record the {@link Record} to create the model from
224: *
225: * @return TrackModel
226: */
227: public static TrackModel create(Record record) {
228: Validate.notNull(record);
229:
230: return new DefaultTrackModel(record);
231: }
232:
233: /**
234: * {@inheritDoc}
235: */
236: public void addTrackModelListener(TrackModelListener listener) {
237: Validate.notNull(listener);
238: Validate.isTrue(!listeners.contains(listener));
239: listeners.add(listener);
240: }
241:
242: /**
243: * {@inheritDoc}
244: */
245: public void removeTrackModelListener(TrackModelListener listener) {
246: Validate.notNull(listener);
247: Validate.isTrue(listeners.contains(listener));
248:
249: listeners.remove(listener);
250: }
251:
252: /**
253: * Fires a {@link TrackModelEvent} to all listeners.
254: *
255: * @param block {@link Block}
256: */
257: protected void fireBlockTimeChangedEvent(Block block) {
258: TrackModelEvent blockTimeChangedEvent = new TrackModelEvent(
259: this );
260: blockTimeChangedEvent.setChangedBlock(block);
261:
262: // HashSet listenersDuplicate = new HashSet(listeners);
263: if (this .isListenersEnabled) {
264: Iterator i = this .listeners.iterator(); // listenersDuplicate.iterator ();
265:
266: while (i.hasNext()) {
267: TrackModelListener listener = (TrackModelListener) i
268: .next();
269: listener.blockTimeChanged(blockTimeChangedEvent);
270: }
271: }
272: }
273:
274: /**
275: * {@inheritDoc}
276: */
277: public void addBlock(Block block, long startTime) {
278: Validate.notNull(block);
279: Validate.isTrue(startTime >= 0,
280: "The start time of the block is less than zero: ",
281: startTime);
282:
283: this .blockStartTimes.put(block, new Long(startTime));
284: this .blockEndTimes.put(block, new Long(startTime
285: + block.getDuration()));
286:
287: TrackModelEvent event = new TrackModelEvent(this );
288: event.setChangedBlock(block);
289:
290: block.tellStartTime(startTime);
291:
292: fireBlockAdded(event);
293: }
294:
295: private void calculateRecordEndTimes() {
296: this .blockEndTimes.clear();
297:
298: Iterator iter = this .blockStartTimes.keySet().iterator();
299:
300: while (iter.hasNext()) {
301: Block block = (Block) iter.next();
302: long duration = block.getDuration();
303: long startTime = ((Long) this .blockStartTimes.get(block))
304: .longValue();
305: this .blockEndTimes.put(block,
306: new Long(startTime + duration));
307: }
308: }
309:
310: /**
311: * {@inheritDoc}
312: */
313: public void removeBlock(Block block) {
314: Validate.notNull(block);
315: Validate.isTrue(blockStartTimes.containsKey(block));
316:
317: blockStartTimes.remove(block);
318:
319: TrackModelEvent blockRemovedEvent = new TrackModelEvent(this );
320: blockRemovedEvent.setChangedBlock(block);
321:
322: fireBlockRemovedEvent(blockRemovedEvent);
323: }
324:
325: void fireBlockRemovedEvent(TrackModelEvent event) {
326: if (this .isListenersEnabled) {
327: Iterator i = listeners.iterator();
328:
329: while (i.hasNext()) {
330: ((TrackModelListener) i.next()).blockRemoved(event);
331: }
332: }
333: }
334:
335: /**
336: * Sets the start time of the given block.
337: *
338: * @param block the Block the start time has to be set for
339: * @param startTime the new start time
340: */
341: public void setStartTime(Block block, long startTime) {
342: Validate.notNull(block);
343: Validate.isTrue(startTime >= 0,
344: "The start time to be set is less than zero!",
345: startTime);
346: Validate.isTrue(blockStartTimes.containsKey(block));
347:
348: // change the blockStartTimes and the blockEndTimes
349: this .blockStartTimes.put(block, new Long(startTime));
350: block.tellStartTime(startTime);
351:
352: calculateRecordEndTimes();
353: }
354:
355: /**
356: * {@inheritDoc}
357: */
358: public long getStartTime(Block block) {
359: Validate.notNull(block);
360: Validate.notNull(this .blockStartTimes);
361:
362: try {
363: Validate.isTrue(this .blockStartTimes.containsKey(block));
364: } catch (IllegalArgumentException e) {
365: LOG
366: .error("DefaultTrackModel.getStartTime(block): Block is not contained in model: "
367: + block);
368:
369: return -1;
370: }
371:
372: return ((Long) this .blockStartTimes.get(block)).longValue();
373: }
374:
375: /**
376: * {@inheritDoc}
377: */
378: public void setEndAlignment(Block toAlign, Block alignTo) {
379: Validate.notNull(toAlign);
380: Validate.isTrue((toAlign.getType() == BlockType.AUDIO)
381: || (toAlign.getType() == BlockType.VIDEO));
382:
383: if (alignTo != null) {
384: Validate.isTrue(alignTo.getType() == BlockType.EVENT);
385:
386: if (isAlignmentAllowed(toAlign, alignTo)) {
387: // Store changes in local HashMap
388: alignedBlocks.put(toAlign, alignTo);
389:
390: // Store changes in record
391: if (toAlign.getType() == BlockType.AUDIO) {
392: ((AudioClipRecordable) ((DefaultBlock) toAlign)
393: .getRecordable())
394: .setAlignedTo(((DefaultBlock) alignTo)
395: .getRecordable());
396: } else {
397: ((VideoClipRecordable) ((DefaultBlock) toAlign)
398: .getRecordable())
399: .setAlignedTo(((DefaultBlock) alignTo)
400: .getRecordable());
401: }
402: }
403: } else {
404: this .alignedBlocks.remove(toAlign);
405: }
406: }
407:
408: /**
409: * Checks if the Block toAlign can be aligned to the Block alignTo. The Alignment is
410: * forbidden, if (startTime + duration) of toAlign are greater than the startTime of alignTo.
411: *
412: * @param toAlign Block to be aligned
413: * @param alignTo Block to be aligned to
414: *
415: * @return boolean
416: */
417: private boolean isAlignmentAllowed(Block toAlign, Block alignTo) {
418: if (((this .getStartTime(toAlign)) + toAlign.getDuration()) <= this
419: .getStartTime(alignTo)) {
420: return true;
421: }
422:
423: return false;
424: }
425:
426: /**
427: * {@inheritDoc}
428: */
429: public void removeEndAlignment(Block alignedBlock) {
430: Validate.notNull(alignedBlock);
431: Validate.isTrue(alignedBlocks.containsKey(alignedBlock));
432:
433: alignedBlocks.remove(alignedBlock);
434:
435: TrackModelEvent alignmentRemoved = new TrackModelEvent(this );
436: alignmentRemoved.setChangedBlock(alignedBlock);
437:
438: fireBlockAlignmentChanged(alignmentRemoved);
439: }
440:
441: void fireBlockAdded(TrackModelEvent event) {
442: if (this .isListenersEnabled) {
443: Iterator i = listeners.iterator();
444:
445: while (i.hasNext()) {
446: ((TrackModelListener) i.next()).blockAdded(event);
447: }
448: }
449: }
450:
451: void fireBlockAlignmentChanged(TrackModelEvent event) {
452: if (this .isListenersEnabled) {
453: Iterator i = listeners.iterator();
454:
455: while (i.hasNext()) {
456: ((TrackModelListener) i.next())
457: .blockAlignmentChanged(event);
458: }
459: }
460: }
461:
462: /**
463: * {@inheritDoc}
464: */
465: public boolean isEndAligned(Block block) {
466: Validate.notNull(block);
467: Validate.isTrue((block.getType() == BlockType.AUDIO)
468: || (block.getType() == BlockType.VIDEO));
469:
470: return alignedBlocks.containsKey(block);
471: }
472:
473: /**
474: * {@inheritDoc}
475: */
476: public Block getAlignedToBlock(Block alignedBlock) {
477: Validate.notNull(alignedBlock);
478: Validate.isTrue(alignedBlocks.containsKey(alignedBlock));
479:
480: return (Block) alignedBlocks.get(alignedBlock);
481: }
482:
483: /**
484: * Sorts the given map by values and returns an array of {@link Map.Entry} objects in correct
485: * natural order of the values.
486: *
487: * @param map the map to be sorted by value
488: *
489: * @return Map.Entry[]
490: */
491: private Map.Entry[] sortMapByValue(Map map) {
492: Set entrySet = map.entrySet();
493: Map.Entry[] entries = (Map.Entry[]) entrySet
494: .toArray(new Map.Entry[entrySet.size()]);
495:
496: Arrays.sort(entries, new Comparator() {
497: public int compare(Object o1, Object o2) {
498: Object v1 = ((Map.Entry) o1).getValue();
499: Object v2 = ((Map.Entry) o2).getValue();
500:
501: return ((Comparable) v1).compareTo(v2);
502: }
503: });
504:
505: return entries;
506: }
507:
508: /**
509: * {@inheritDoc}
510: */
511: public List getBlocksInRange(BlockType type, long rangeStart,
512: long rangeEnd, boolean all) {
513: Validate.isTrue((all && (type == null))
514: || (!all && (type != null)));
515: Validate.isTrue(rangeStart >= 0,
516: "The rangeStart time is less than zero!", rangeStart);
517: Validate.isTrue(rangeEnd >= 0,
518: "The rangeStart time is less than zero!", rangeEnd);
519:
520: List returnList = new ArrayList();
521:
522: Map.Entry[] entries = sortMapByValue(this .blockStartTimes);
523:
524: for (int i = 0; i < entries.length; i++) {
525: Block currentBlock = (Block) entries[i].getKey();
526:
527: if (currentBlock.getType().equals(type) || all) {
528: long startTime = this .getStartTime(currentBlock);
529: long endTime = this .getEndTime(currentBlock);
530:
531: if (
532: // all blocks which start before or at rangeStart and end after rangeStart ("reach into range from the left")
533: ((startTime <= rangeStart) && (endTime > rangeStart)) ||
534:
535: // all blocks which start after or at rangeStart but before rangeEnd
536: ((startTime >= rangeStart) && (startTime < rangeEnd))) {
537: returnList.add(currentBlock);
538: } else {
539: ;
540: }
541: }
542: }
543:
544: return returnList;
545: }
546:
547: /**
548: * {@inheritDoc}
549: */
550: public long getStartTime() {
551: return 0;
552: }
553:
554: /**
555: * Returns the end time (~maximum start time + duration at the moment)
556: *
557: * @return long end time of all tracks
558: */
559: public long getEndTime() {
560: if (this .blockEndTimes.size() > 0) {
561: Long maxEndTime = (Long) Collections.max(this .blockEndTimes
562: .values());
563:
564: return maxEndTime.longValue();
565: }
566:
567: return 0;
568: }
569:
570: /**
571: * {@inheritDoc}
572: */
573: public long getEndTime(Block block) {
574: Validate.notNull(block);
575: Validate.notNull(this .blockEndTimes);
576: Validate.isTrue(this .blockEndTimes.containsKey(block));
577:
578: return ((Long) this .blockEndTimes.get(block)).longValue();
579: }
580:
581: /**
582: * {@inheritDoc}
583: */
584: public boolean containsBlock(Block block) {
585: return this.blockStartTimes.containsKey(block);
586: }
587: }
|