1/*****************************************************
2*
3*  Copyright 2009 Adobe Systems Incorporated.  All Rights Reserved.
4*
5*****************************************************
6*  The contents of this file are subject to the Mozilla Public License
7*  Version 1.1 (the "License"); you may not use this file except in
8*  compliance with the License. You may obtain a copy of the License at
9*  http://www.mozilla.org/MPL/
10*
11*  Software distributed under the License is distributed on an "AS IS"
12*  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
13*  License for the specific language governing rights and limitations
14*  under the License.
15*
16*
17*  The Initial Developer of the Original Code is Adobe Systems Incorporated.
18*  Portions created by Adobe Systems Incorporated are Copyright (C) 2009 Adobe Systems
19*  Incorporated. All Rights Reserved.
20*
21*****************************************************/
22package org.osmf.elements.compositeClasses
23{
24	import __AS3__.vec.Vector;
25
26	import flash.errors.IllegalOperationError;
27	import flash.events.Event;
28	import flash.utils.Dictionary;
29
30	import org.osmf.events.MetadataEvent;
31	import org.osmf.metadata.Metadata;
32	import org.osmf.metadata.MetadataGroup;
33	import org.osmf.metadata.MetadataSynthesizer;
34	import org.osmf.metadata.NullMetadataSynthesizer;
35	import org.osmf.utils.OSMFStrings;
36
37	CONFIG::LOGGING
38	{
39	import org.osmf.logging.Logger;
40	}
41
42	[ExcludeClass]
43
44	/**
45	 * Event fired when a child was added to the composite.
46	 *
47	 *  @langversion 3.0
48	 *  @playerversion Flash 10
49	 *  @playerversion AIR 1.5
50	 *  @productversion OSMF 1.0
51	 */
52	[Event(name="childAdd", type="org.osmf.events.CompositeMetadataEvent")]
53
54	/**
55	 * Event fired when a child was removed from the composite.
56	 *
57	 *  @langversion 3.0
58	 *  @playerversion Flash 10
59	 *  @playerversion AIR 1.5
60	 *  @productversion OSMF 1.0
61	 */
62	[Event(name="childRemove", type="org.osmf.events.CompositeMetadataEvent")]
63
64	/**
65	 * Event fired when a new metadata group emerged.
66	 *
67	 *  @langversion 3.0
68	 *  @playerversion Flash 10
69	 *  @playerversion AIR 1.5
70	 *  @productversion OSMF 1.0
71	 */
72	[Event(name="metadataGroupAdd", type="org.osmf.event.CompositeMetadataEvent")]
73
74	/**
75	 * Event fired when an existing metadata group ceased to exist.
76	 *
77	 *  @langversion 3.0
78	 *  @playerversion Flash 10
79	 *  @playerversion AIR 1.5
80	 *  @productversion OSMF 1.0
81	 */
82	[Event(name="metadataGroupRemove", type="org.osmf.event.CompositeMetadataEvent")]
83
84	/**
85	 * Event fired when a metadata group changed.
86	 *
87	 *  @langversion 3.0
88	 *  @playerversion Flash 10
89	 *  @playerversion AIR 1.5
90	 *  @productversion OSMF 1.0
91	 */
92	[Event(name="metadataGroupChange", type="org.osmf.event.CompositeMetadataEvent")]
93
94	/**
95	 * @private
96	 *
97	 * Defines a collection of meta data that keeps track of a collection
98	 * of child meta data references as a plain list.
99	 *
100	 * By default, no synthesis takes place. External clients can inspect metadata
101	 * groups at will, and monitor them for change. However, the class provides
102	 * an infrastructure for synthesis like so:
103	 *
104	 * By using 'addMetadataSynthesizer' and 'removeMetadataSynthesizer', clients can
105	 * define how metadata groups of a given name space will be synthesized into
106	 * a new metadata. If a metadata group changes that matches an added metadata
107	 * synthesizer's namespace, then this synthesizer is used to synthesize the
108	 * composite value. After synthesis, the value gets added as one of the
109	 * composite's own metadatas.
110	 *
111	 * If a CompositeMetadata instance is itself a child of another
112	 * CompositeMetadata instance, then any metadata synthesizer that is set on
113	 * the parent instance, will be used by the child instance automatically
114	 * too. If the child has its own metadata synthesizer listed, than that
115	 * synthesizer takes precedence over the inherited one.
116	 *
117	 * Last, metadata synthesis occurs if the first metadata from a metadata group
118	 * returns a metadata synthesizer for its synthesizer property. Metadata
119	 * synthesizers set on this class directly, or indirectly via its parent,
120	 * take precedence over the metadata's suggested synthesizer.
121	 *
122	 *
123	 *  @langversion 3.0
124	 *  @playerversion Flash 10
125	 *  @playerversion AIR 1.5
126	 *  @productversion OSMF 1.0
127	 */
128	public class CompositeMetadata extends Metadata
129	{
130		// Public Interface
131		//
132
133		/**
134		 * Constructor
135		 *
136		 *  @langversion 3.0
137		 *  @playerversion Flash 10
138		 *  @playerversion AIR 1.5
139		 *  @productversion OSMF 1.0
140		 */
141		public function CompositeMetadata()
142		{
143			super();
144
145			children = new Vector.<Metadata>();
146			childMetadataGroups = new Dictionary();
147
148			metadataSynthesizers = new Dictionary();
149		}
150
151		/**
152		 * Adds a metadata child.
153		 *
154		 * @param child The child to add.
155		 * @throws IllegalOperationError Thrown if the specified child is
156		 * already a child.
157		 * @throws ArgumentError if the specified child is null.
158		 *
159		 *  @langversion 3.0
160		 *  @playerversion Flash 10
161		 *  @playerversion AIR 1.5
162		 *  @productversion OSMF 1.0
163		 */
164		public function addChild(child:Metadata):void
165		{
166			if (child == null)
167			{
168				throw new ArgumentError(OSMFStrings.getString(OSMFStrings.NULL_PARAM));
169			}
170
171			var childIndex:int = children.indexOf(child);
172			if (childIndex != -1)
173			{
174				throw new IllegalOperationError();
175			}
176			else
177			{
178				children.push(child);
179
180				child.addEventListener
181					( MetadataEvent.VALUE_ADD
182					, onChildMetadataAdd
183					);
184
185				child.addEventListener
186					( MetadataEvent.VALUE_REMOVE
187					, onChildMetadataRemove
188					);
189
190				if (child is CompositeMetadata)
191				{
192					child.addEventListener
193						( CompositeMetadataEvent.METADATA_GROUP_CHANGE
194						, onChildMetadataGroupChange
195						);
196				}
197
198				for each (var url:String in child.keys)
199				{
200					processChildMetadataAdd
201						( child
202						, child.getValue(url) as Metadata
203						, url
204						);
205				}
206
207				dispatchEvent
208					( new CompositeMetadataEvent
209						( CompositeMetadataEvent.CHILD_ADD
210						, false, false
211						, child
212						)
213					);
214			}
215		}
216
217		/**
218		 * Removes a metadata child.
219		 *
220		 * @param child The child to remove.
221		 * @throws IllegalOperationError Thrown if the specified child is
222		 * not a child.
223		 * @throws ArgumentError if the specified child is null.
224		 *
225		 *  @langversion 3.0
226		 *  @playerversion Flash 10
227		 *  @playerversion AIR 1.5
228		 *  @productversion OSMF 1.0
229		 */
230		public function removeChild(child:Metadata):void
231		{
232			if (child == null)
233			{
234				throw new ArgumentError(OSMFStrings.getString(OSMFStrings.NULL_PARAM));
235			}
236
237			var childIndex:int = children.indexOf(child);
238			if (childIndex == -1)
239			{
240				throw new IllegalOperationError(OSMFStrings.getString(OSMFStrings.INVALID_PARAM));
241			}
242			else
243			{
244				children.splice(childIndex,1);
245
246				child.removeEventListener
247					( MetadataEvent.VALUE_ADD
248					, onChildMetadataAdd
249					);
250
251				child.removeEventListener
252					( MetadataEvent.VALUE_REMOVE
253					, onChildMetadataRemove
254					);
255
256				if (child is CompositeMetadata)
257				{
258					child.removeEventListener
259						( CompositeMetadataEvent.METADATA_GROUP_CHANGE
260						, onChildMetadataGroupChange
261						);
262				}
263
264				for each (var url:String in child.keys)
265				{
266					processChildMetadataRemove
267						( child
268						, child.getValue(url) as Metadata
269						, url
270						);
271				}
272
273				dispatchEvent
274					( new CompositeMetadataEvent
275						( CompositeMetadataEvent.CHILD_REMOVE
276						, false, false
277						, child
278						)
279					);
280			}
281		}
282
283		/**
284		 * Defines the number of children.
285		 *
286		 *  @langversion 3.0
287		 *  @playerversion Flash 10
288		 *  @playerversion AIR 1.5
289		 *  @productversion OSMF 1.0
290		 */
291		public function get numChildren():int
292		{
293			return children.length;
294		}
295
296		/**
297		 * Fetches the child at the indicated index.
298		 *
299		 * @param index The index of the child metadata to fetch.
300		 * @return The requested metadata.
301		 * @throws RangeError Thrown when the specified index is out of
302		 * bounds.
303		 *
304		 *  @langversion 3.0
305		 *  @playerversion Flash 10
306		 *  @playerversion AIR 1.5
307		 *  @productversion OSMF 1.0
308		 */
309		public function getChildAt(index:int):Metadata
310		{
311			if (index >= children.length || index < 0)
312			{
313				throw new RangeError(OSMFStrings.getString(OSMFStrings.INVALID_PARAM));
314			}
315
316			return children[index];
317		}
318
319		/**
320		 * Defines the composition mode that will be forwarded to metadata
321		 * synthesizers when synthesizing a merged metadata is required.
322		 *
323		 *  @langversion 3.0
324		 *  @playerversion Flash 10
325		 *  @playerversion AIR 1.5
326		 *  @productversion OSMF 1.0
327		 */
328		public function set mode(value:String):void
329		{
330			if (_mode != value)
331			{
332				_mode = value;
333
334				processSynthesisDependencyChanged();
335			}
336		}
337		public function get mode():String
338		{
339			return _mode;
340		}
341
342		/**
343		 * Defines the active metadata that will be forwarded
344		 * to metadata synthesizers when synthesizing a merged metadata is
345		 * required.
346		 *
347		 *  @langversion 3.0
348		 *  @playerversion Flash 10
349		 *  @playerversion AIR 1.5
350		 *  @productversion OSMF 1.0
351		 */
352		public function set activeChild(value:Metadata):void
353		{
354			if (_activeChild != value)
355			{
356				_activeChild = value;
357
358				processSynthesisDependencyChanged();
359			}
360		}
361		public function get activeChild():Metadata
362		{
363			return _activeChild;
364		}
365
366		/**
367		 * Adds a metadata synthesizer.
368		 *
369		 * A metadata synthesizer can synthesize a metadata from a given MetadataGroup,
370		 * composition mode, and active child (if any).
371		 *
372		 * Only one synthesizer can be registered for a given namespace URL.
373		 *
374		 * @param namespaceURL The namespace URL to synthesize values for.
375		 * @param synthesizer The metadata synthesizer to add.
376		 *
377		 *  @langversion 3.0
378		 *  @playerversion Flash 10
379		 *  @playerversion AIR 1.5
380		 *  @productversion OSMF 1.0
381		 */
382		public function addMetadataSynthesizer(namespaceURL:String, synthesizer:MetadataSynthesizer):void
383		{
384			if (synthesizer == null)
385			{
386				throw new ArgumentError(OSMFStrings.getString(OSMFStrings.NULL_PARAM));
387			}
388
389			if (getMetadataSynthesizer(namespaceURL) != null)
390			{
391				throw new ArgumentError(OSMFStrings.getString(OSMFStrings.INVALID_PARAM));
392			}
393
394			metadataSynthesizers[namespaceURL] = synthesizer;
395		}
396
397		/**
398		 * Removes a metadata synthesizer.
399		 *
400		 * @param namespaceURL The namespace URL that corresponds to the synthesizer to remove.
401		 * @throws ArgumentError If namespaceURL is null.
402		 *
403		 *  @langversion 3.0
404		 *  @playerversion Flash 10
405		 *  @playerversion AIR 1.5
406		 *  @productversion OSMF 1.0
407		 */
408		public function removeMetadataSynthesizer(namespaceURL:String):void
409		{
410			if (namespaceURL == null)
411			{
412				throw new ArgumentError(OSMFStrings.getString(OSMFStrings.NULL_PARAM));
413			}
414
415			if (getMetadataSynthesizer(namespaceURL) != null)
416			{
417				delete metadataSynthesizers[namespaceURL];
418			}
419		}
420
421		/**
422		 * Fetches the metadata synthesizer (if any) for the given namespace URL.
423		 *
424		 * @param namespaceURL The namespace to retrieve the set synthesizer for.
425		 * @return The requested syntesizer, if it was set, null otherwise.
426		 *
427		 *  @langversion 3.0
428		 *  @playerversion Flash 10
429		 *  @playerversion AIR 1.5
430		 *  @productversion OSMF 1.0
431		 */
432		public function getMetadataSynthesizer(namespaceURL:String):MetadataSynthesizer
433		{
434			var result:MetadataSynthesizer;
435
436			if (namespaceURL != null)
437			{
438				for (var rawUrl:String in metadataSynthesizers)
439				{
440					if (rawUrl == namespaceURL)
441					{
442						result = metadataSynthesizers[rawUrl];
443						break;
444					}
445				}
446			}
447
448			return result;
449		}
450
451		/**
452		 * Collects the namespaces of the metadata groups that are currently in existence.
453		 *
454		 * @return The collected namespaces.
455		 *
456		 *  @langversion 3.0
457		 *  @playerversion Flash 10
458		 *  @playerversion AIR 1.5
459		 *  @productversion OSMF 1.0
460		 */
461		public function getMetadataGroupNamespaceURLs():Vector.<String>
462		{
463			var result:Vector.<String> = new Vector.<String>();
464
465			for (var url:String in childMetadataGroups)
466			{
467				result.push(url);
468			}
469
470			return result;
471		}
472
473		/**
474		 * Fetches the metadata group for the given namenspace.
475		 *
476		 * @param namespaceURL The namespace to fetch the metadata group for.
477		 * @return The requested metadata group, or null if there is no such group.
478		 *
479		 *  @langversion 3.0
480		 *  @playerversion Flash 10
481		 *  @playerversion AIR 1.5
482		 *  @productversion OSMF 1.0
483		 */
484		public function getMetadataGroup(namespaceURL:String):MetadataGroup
485		{
486			if (namespaceURL == null)
487			{
488				throw new ArgumentError(OSMFStrings.getString(OSMFStrings.NULL_PARAM));
489			}
490
491			return childMetadataGroups[namespaceURL];
492		}
493
494		// Internals
495		//
496
497		private function processChildMetadataAdd(child:Metadata, metadata:Metadata, metadataNamespaceURL:String):void
498		{
499			var groupAddEvent:CompositeMetadataEvent;
500
501			if (metadata != null)
502			{
503				var childrenNamespaceURL:String = metadataNamespaceURL;
504
505				var metadataGroup:MetadataGroup = childMetadataGroups[childrenNamespaceURL];
506				if (metadataGroup == null)
507				{
508					childMetadataGroups[childrenNamespaceURL]
509						= metadataGroup
510						= new MetadataGroup(childrenNamespaceURL);
511
512					metadataGroup.addEventListener(Event.CHANGE, onMetadataGroupChange);
513
514					groupAddEvent
515						= new CompositeMetadataEvent
516							( CompositeMetadataEvent.METADATA_GROUP_ADD
517							, false, false
518							, child
519							, metadataNamespaceURL
520							, metadata
521							, metadataGroup
522							);
523				}
524
525				metadataGroup.addMetadata(child, metadata);
526			}
527
528			if (groupAddEvent != null)
529			{
530				dispatchEvent(groupAddEvent);
531			}
532		}
533
534		private function processChildMetadataRemove(child:Metadata, metadata:Metadata, metadataNamespaceURL:String):void
535		{
536			var groupRemoveEvent:CompositeMetadataEvent;
537
538			if (metadata != null)
539			{
540				var childrenNamespaceURL:String = metadataNamespaceURL;
541				var metadataGroup:MetadataGroup = childMetadataGroups[childrenNamespaceURL];
542				metadataGroup.removeMetadata(child, metadata);
543
544				if (metadataGroup.metadatas.length == 0)
545				{
546					metadataGroup.removeEventListener(Event.CHANGE, onMetadataGroupChange);
547
548					groupRemoveEvent
549						= new CompositeMetadataEvent
550							( CompositeMetadataEvent.METADATA_GROUP_REMOVE
551							, false, false
552							, child
553							, metadataNamespaceURL
554							, metadata
555							, metadataGroup
556							);
557
558					delete childMetadataGroups[childrenNamespaceURL];
559
560				}
561			}
562
563			if (groupRemoveEvent != null)
564			{
565				dispatchEvent(groupRemoveEvent);
566			}
567		}
568
569		private function processSynthesisDependencyChanged():void
570		{
571			for each (var metadataGroup:MetadataGroup in childMetadataGroups)
572			{
573				onMetadataGroupChange(null, metadataGroup);
574			}
575		}
576
577		// Event Handlers
578		//
579
580		private function onChildMetadataAdd(event:MetadataEvent):void
581		{
582			processChildMetadataAdd(event.target as Metadata, event.value as Metadata, event.key);
583		}
584
585		private function onChildMetadataRemove(event:MetadataEvent):void
586		{
587			processChildMetadataRemove(event.target as Metadata, event.value as Metadata, event.key);
588		}
589
590		private function onChildMetadataGroupChange(event:CompositeMetadataEvent):void
591		{
592			// If no one before us was able to deliver a metadata synthesizer, then perhaps we can:
593			if (event.suggestedMetadataSynthesizer == null)
594			{
595				event.suggestedMetadataSynthesizer = metadataSynthesizers[event.metadataGroup.namespaceURL];
596			}
597
598			var clonedEvent:CompositeMetadataEvent
599				=	event.clone()
600				as	CompositeMetadataEvent;
601
602			// Re-dispatch the event:
603			dispatchEvent(clonedEvent);
604
605			// If we didn't assign a metadata synthesizer, then perhaps another handler did:
606			if (event.suggestedMetadataSynthesizer == null)
607			{
608				event.suggestedMetadataSynthesizer = clonedEvent.suggestedMetadataSynthesizer;
609			}
610		}
611
612		private function onMetadataGroupChange(event:Event, metadataGroup:MetadataGroup = null):void
613		{
614			// This method is invoked as both a regular event handler, as well as directly
615			// from processSynthesisDependencyChanged. In the latter case, the event will be
616			// null, and the metadataGroup parameter will be set instead. To be prudent, check
617			// for a metadata group being present either way:
618			metadataGroup ||= event ? event.target as MetadataGroup : null;
619			if (metadataGroup == null)
620			{
621				throw new IllegalOperationError(OSMFStrings.getString(OSMFStrings.NULL_PARAM));
622			}
623
624			var synthesizedMetadata:Metadata;
625			var metadataSynthesizer:MetadataSynthesizer = metadataSynthesizers[metadataGroup.namespaceURL];
626
627			var localEvent:CompositeMetadataEvent
628				= new CompositeMetadataEvent
629					( CompositeMetadataEvent.METADATA_GROUP_CHANGE
630					, false, false
631					, null, null, null
632					, metadataGroup
633					, metadataSynthesizer
634					)
635
636			dispatchEvent(localEvent);
637
638 			// If no metadata synthesizer is set yet, then first see if any of the
639			// event handlers provided us with one. If not, then use the metadata's
640			// default synthesizer (if it provides for one):
641			metadataSynthesizer
642				||= localEvent.suggestedMetadataSynthesizer
643				// If no synthesizer has been suggested by our parents, then look
644				// at the metadata group, and take the synthesizer set on the first
645				// metadata that we encounter:
646				||	(	(metadataGroup.metadatas.length > 0)
647							? metadataGroup.metadatas[0].synthesizer
648							: null
649				 	)
650				// Last, revert to the default synthesizer:
651				|| new MetadataSynthesizer();
652
653			// If the activeChild was just removed, don't let it influence the
654			// synthesis decision.
655			var serialElementActiveChild:Metadata = _activeChild;
656			if (_activeChild != null && children.indexOf(_activeChild) == -1)
657			{
658				serialElementActiveChild = null;
659			}
660
661			// Run the metadata synthesizer:
662			synthesizedMetadata
663				= metadataSynthesizer.synthesize
664					( metadataGroup.namespaceURL
665					, this
666					, metadataGroup.metadatas
667					, _mode
668					, serialElementActiveChild
669					);
670
671			if (synthesizedMetadata == null)
672			{
673				// If the synthesized value is null, then we might need to clear
674				// out a previously set value. Don't clear out the value if the
675				// current synthesizer is a null synthesizer.
676				var currentMetadata:Metadata = getValue(metadataGroup.namespaceURL) as Metadata;
677				if	(	currentMetadata != null
678					&&	currentMetadata.synthesizer is NullMetadataSynthesizer == false
679					)
680				{
681					CONFIG::LOGGING { logger.debug("removing metadata {0}", metadataGroup.namespaceURL); }
682					removeValue(metadataGroup.namespaceURL);
683				}
684			}
685			else
686			{
687				// Add, or overwrite the last set metadata value:
688				addValue(metadataGroup.namespaceURL, synthesizedMetadata);
689			}
690		}
691
692		private var children:Vector.<Metadata>;
693		private var childMetadataGroups:Dictionary;
694		private var metadataSynthesizers:Dictionary;
695
696		private var _mode:String;
697		private var _activeChild:Metadata;
698
699		CONFIG::LOGGING private static const logger:org.osmf.logging.Logger = org.osmf.logging.Log.getLogger("org.osmf.elements.compositeClasses.CompositeMetadata");
700	}
701}