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}