1/* 2 * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26package javax.imageio.metadata; 27 28import java.util.ArrayList; 29import java.util.Collection; 30import java.util.HashMap; 31import java.util.Iterator; 32import java.util.List; 33import java.util.Locale; 34import java.util.Map; 35import java.util.MissingResourceException; 36import java.util.ResourceBundle; 37import javax.imageio.ImageTypeSpecifier; 38import com.sun.imageio.plugins.common.StandardMetadataFormat; 39 40/** 41 * A concrete class providing a reusable implementation of the 42 * {@code IIOMetadataFormat} interface. In addition, a static 43 * instance representing the standard, plug-in neutral 44 * {@code javax_imageio_1.0} format is provided by the 45 * {@code getStandardFormatInstance} method. 46 * 47 * <p> In order to supply localized descriptions of elements and 48 * attributes, a {@code ResourceBundle} with a base name of 49 * {@code this.getClass().getName() + "Resources"} should be 50 * supplied via the usual mechanism used by 51 * {@code ResourceBundle.getBundle}. Briefly, the subclasser 52 * supplies one or more additional classes according to a naming 53 * convention (by default, the fully-qualified name of the subclass 54 * extending {@code IIMetadataFormatImpl}, plus the string 55 * "Resources", plus the country, language, and variant codes 56 * separated by underscores). At run time, calls to 57 * {@code getElementDescription} or 58 * {@code getAttributeDescription} will attempt to load such 59 * classes dynamically according to the supplied locale, and will use 60 * either the element name, or the element name followed by a '/' 61 * character followed by the attribute name as a key. This key will 62 * be supplied to the {@code ResourceBundle}'s 63 * {@code getString} method, and the resulting localized 64 * description of the node or attribute is returned. 65 * 66 * <p> The subclass may supply a different base name for the resource 67 * bundles using the {@code setResourceBaseName} method. 68 * 69 * <p> A subclass may choose its own localization mechanism, if so 70 * desired, by overriding the supplied implementations of 71 * {@code getElementDescription} and 72 * {@code getAttributeDescription}. 73 * 74 * @see ResourceBundle#getBundle(String,Locale) 75 * 76 */ 77public abstract class IIOMetadataFormatImpl implements IIOMetadataFormat { 78 79 /** 80 * A {@code String} constant containing the standard format 81 * name, {@code "javax_imageio_1.0"}. 82 */ 83 public static final String standardMetadataFormatName = 84 "javax_imageio_1.0"; 85 86 private static IIOMetadataFormat standardFormat = null; 87 88 private String resourceBaseName = this.getClass().getName() + "Resources"; 89 90 private String rootName; 91 92 // Element name (String) -> Element 93 private HashMap<String, Element> elementMap = new HashMap<>(); 94 95 class Element { 96 String elementName; 97 98 int childPolicy; 99 int minChildren = 0; 100 int maxChildren = 0; 101 102 // Child names (Strings) 103 List<String> childList = new ArrayList<>(); 104 105 // Parent names (Strings) 106 List<String> parentList = new ArrayList<>(); 107 108 // List of attribute names in the order they were added 109 List<String> attrList = new ArrayList<>(); 110 // Attr name (String) -> Attribute 111 Map<String, Attribute> attrMap = new HashMap<>(); 112 113 ObjectValue<?> objectValue; 114 } 115 116 class Attribute { 117 String attrName; 118 119 int valueType = VALUE_ARBITRARY; 120 int dataType; 121 boolean required; 122 String defaultValue = null; 123 124 // enumeration 125 List<String> enumeratedValues; 126 127 // range 128 String minValue; 129 String maxValue; 130 131 // list 132 int listMinLength; 133 int listMaxLength; 134 } 135 136 class ObjectValue<T> { 137 int valueType = VALUE_NONE; 138 // ? extends T So that ObjectValue<Object> can take Class<?> 139 Class<? extends T> classType = null; 140 T defaultValue = null; 141 142 // Meaningful only if valueType == VALUE_ENUMERATION 143 List<? extends T> enumeratedValues = null; 144 145 // Meaningful only if valueType == VALUE_RANGE 146 Comparable<? super T> minValue = null; 147 Comparable<? super T> maxValue = null; 148 149 // Meaningful only if valueType == VALUE_LIST 150 int arrayMinLength = 0; 151 int arrayMaxLength = 0; 152 } 153 154 /** 155 * Constructs a blank {@code IIOMetadataFormatImpl} instance, 156 * with a given root element name and child policy (other than 157 * {@code CHILD_POLICY_REPEAT}). Additional elements, and 158 * their attributes and {@code Object} reference information 159 * may be added using the various {@code add} methods. 160 * 161 * @param rootName the name of the root element. 162 * @param childPolicy one of the {@code CHILD_POLICY_*} constants, 163 * other than {@code CHILD_POLICY_REPEAT}. 164 * 165 * @exception IllegalArgumentException if {@code rootName} is 166 * {@code null}. 167 * @exception IllegalArgumentException if {@code childPolicy} is 168 * not one of the predefined constants. 169 */ 170 public IIOMetadataFormatImpl(String rootName, 171 int childPolicy) { 172 if (rootName == null) { 173 throw new IllegalArgumentException("rootName == null!"); 174 } 175 if (childPolicy < CHILD_POLICY_EMPTY || 176 childPolicy > CHILD_POLICY_MAX || 177 childPolicy == CHILD_POLICY_REPEAT) { 178 throw new IllegalArgumentException("Invalid value for childPolicy!"); 179 } 180 181 this.rootName = rootName; 182 183 Element root = new Element(); 184 root.elementName = rootName; 185 root.childPolicy = childPolicy; 186 187 elementMap.put(rootName, root); 188 } 189 190 /** 191 * Constructs a blank {@code IIOMetadataFormatImpl} instance, 192 * with a given root element name and a child policy of 193 * {@code CHILD_POLICY_REPEAT}. Additional elements, and 194 * their attributes and {@code Object} reference information 195 * may be added using the various {@code add} methods. 196 * 197 * @param rootName the name of the root element. 198 * @param minChildren the minimum number of children of the node. 199 * @param maxChildren the maximum number of children of the node. 200 * 201 * @exception IllegalArgumentException if {@code rootName} is 202 * {@code null}. 203 * @exception IllegalArgumentException if {@code minChildren} 204 * is negative or larger than {@code maxChildren}. 205 */ 206 public IIOMetadataFormatImpl(String rootName, 207 int minChildren, 208 int maxChildren) { 209 if (rootName == null) { 210 throw new IllegalArgumentException("rootName == null!"); 211 } 212 if (minChildren < 0) { 213 throw new IllegalArgumentException("minChildren < 0!"); 214 } 215 if (minChildren > maxChildren) { 216 throw new IllegalArgumentException("minChildren > maxChildren!"); 217 } 218 219 Element root = new Element(); 220 root.elementName = rootName; 221 root.childPolicy = CHILD_POLICY_REPEAT; 222 root.minChildren = minChildren; 223 root.maxChildren = maxChildren; 224 225 this.rootName = rootName; 226 elementMap.put(rootName, root); 227 } 228 229 /** 230 * Sets a new base name for locating {@code ResourceBundle}s 231 * containing descriptions of elements and attributes for this 232 * format. 233 * 234 * <p> Prior to the first time this method is called, the base 235 * name will be equal to 236 * {@code this.getClass().getName() + "Resources"}. 237 * 238 * @param resourceBaseName a {@code String} containing the new 239 * base name. 240 * 241 * @exception IllegalArgumentException if 242 * {@code resourceBaseName} is {@code null}. 243 * 244 * @see #getResourceBaseName 245 */ 246 protected void setResourceBaseName(String resourceBaseName) { 247 if (resourceBaseName == null) { 248 throw new IllegalArgumentException("resourceBaseName == null!"); 249 } 250 this.resourceBaseName = resourceBaseName; 251 } 252 253 /** 254 * Returns the currently set base name for locating 255 * {@code ResourceBundle}s. 256 * 257 * @return a {@code String} containing the base name. 258 * 259 * @see #setResourceBaseName 260 */ 261 protected String getResourceBaseName() { 262 return resourceBaseName; 263 } 264 265 /** 266 * Utility method for locating an element. 267 * 268 * @param mustAppear if {@code true}, throw an 269 * {@code IllegalArgumentException} if no such node exists; 270 * if {@code false}, just return null. 271 */ 272 private Element getElement(String elementName, boolean mustAppear) { 273 if (mustAppear && (elementName == null)) { 274 throw new IllegalArgumentException("element name is null!"); 275 } 276 Element element = elementMap.get(elementName); 277 if (mustAppear && (element == null)) { 278 throw new IllegalArgumentException("No such element: " + 279 elementName); 280 } 281 return element; 282 } 283 284 private Element getElement(String elementName) { 285 return getElement(elementName, true); 286 } 287 288 // Utility method for locating an attribute 289 private Attribute getAttribute(String elementName, String attrName) { 290 Element element = getElement(elementName); 291 Attribute attr = element.attrMap.get(attrName); 292 if (attr == null) { 293 throw new IllegalArgumentException("No such attribute \"" + 294 attrName + "\"!"); 295 } 296 return attr; 297 } 298 299 // Setup 300 301 /** 302 * Adds a new element type to this metadata document format with a 303 * child policy other than {@code CHILD_POLICY_REPEAT}. 304 * 305 * @param elementName the name of the new element. 306 * @param parentName the name of the element that will be the 307 * parent of the new element. 308 * @param childPolicy one of the {@code CHILD_POLICY_*} 309 * constants, other than {@code CHILD_POLICY_REPEAT}, 310 * indicating the child policy of the new element. 311 * 312 * @exception IllegalArgumentException if {@code parentName} 313 * is {@code null}, or is not a legal element name for this 314 * format. 315 * @exception IllegalArgumentException if {@code childPolicy} 316 * is not one of the predefined constants. 317 */ 318 protected void addElement(String elementName, 319 String parentName, 320 int childPolicy) { 321 Element parent = getElement(parentName); 322 if (childPolicy < CHILD_POLICY_EMPTY || 323 childPolicy > CHILD_POLICY_MAX || 324 childPolicy == CHILD_POLICY_REPEAT) { 325 throw new IllegalArgumentException 326 ("Invalid value for childPolicy!"); 327 } 328 329 Element element = new Element(); 330 element.elementName = elementName; 331 element.childPolicy = childPolicy; 332 333 parent.childList.add(elementName); 334 element.parentList.add(parentName); 335 336 elementMap.put(elementName, element); 337 } 338 339 /** 340 * Adds a new element type to this metadata document format with a 341 * child policy of {@code CHILD_POLICY_REPEAT}. 342 * 343 * @param elementName the name of the new element. 344 * @param parentName the name of the element that will be the 345 * parent of the new element. 346 * @param minChildren the minimum number of children of the node. 347 * @param maxChildren the maximum number of children of the node. 348 * 349 * @exception IllegalArgumentException if {@code parentName} 350 * is {@code null}, or is not a legal element name for this 351 * format. 352 * @exception IllegalArgumentException if {@code minChildren} 353 * is negative or larger than {@code maxChildren}. 354 */ 355 protected void addElement(String elementName, 356 String parentName, 357 int minChildren, 358 int maxChildren) { 359 Element parent = getElement(parentName); 360 if (minChildren < 0) { 361 throw new IllegalArgumentException("minChildren < 0!"); 362 } 363 if (minChildren > maxChildren) { 364 throw new IllegalArgumentException("minChildren > maxChildren!"); 365 } 366 367 Element element = new Element(); 368 element.elementName = elementName; 369 element.childPolicy = CHILD_POLICY_REPEAT; 370 element.minChildren = minChildren; 371 element.maxChildren = maxChildren; 372 373 parent.childList.add(elementName); 374 element.parentList.add(parentName); 375 376 elementMap.put(elementName, element); 377 } 378 379 /** 380 * Adds an existing element to the list of legal children for a 381 * given parent node type. 382 * 383 * @param parentName the name of the element that will be the 384 * new parent of the element. 385 * @param elementName the name of the element to be added as a 386 * child. 387 * 388 * @exception IllegalArgumentException if {@code elementName} 389 * is {@code null}, or is not a legal element name for this 390 * format. 391 * @exception IllegalArgumentException if {@code parentName} 392 * is {@code null}, or is not a legal element name for this 393 * format. 394 */ 395 protected void addChildElement(String elementName, String parentName) { 396 Element parent = getElement(parentName); 397 Element element = getElement(elementName); 398 parent.childList.add(elementName); 399 element.parentList.add(parentName); 400 } 401 402 /** 403 * Removes an element from the format. If no element with the 404 * given name was present, nothing happens and no exception is 405 * thrown. 406 * 407 * @param elementName the name of the element to be removed. 408 */ 409 protected void removeElement(String elementName) { 410 Element element = getElement(elementName, false); 411 if (element != null) { 412 Iterator<String> iter = element.parentList.iterator(); 413 while (iter.hasNext()) { 414 String parentName = iter.next(); 415 Element parent = getElement(parentName, false); 416 if (parent != null) { 417 parent.childList.remove(elementName); 418 } 419 } 420 elementMap.remove(elementName); 421 } 422 } 423 424 /** 425 * Adds a new attribute to a previously defined element that may 426 * be set to an arbitrary value. 427 * 428 * @param elementName the name of the element. 429 * @param attrName the name of the attribute being added. 430 * @param dataType the data type (string format) of the attribute, 431 * one of the {@code DATATYPE_*} constants. 432 * @param required {@code true} if the attribute must be present. 433 * @param defaultValue the default value for the attribute, or 434 * {@code null}. 435 * 436 * @exception IllegalArgumentException if {@code elementName} 437 * is {@code null}, or is not a legal element name for this 438 * format. 439 * @exception IllegalArgumentException if {@code attrName} is 440 * {@code null}. 441 * @exception IllegalArgumentException if {@code dataType} is 442 * not one of the predefined constants. 443 */ 444 protected void addAttribute(String elementName, 445 String attrName, 446 int dataType, 447 boolean required, 448 String defaultValue) { 449 Element element = getElement(elementName); 450 if (attrName == null) { 451 throw new IllegalArgumentException("attrName == null!"); 452 } 453 if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) { 454 throw new IllegalArgumentException("Invalid value for dataType!"); 455 } 456 457 Attribute attr = new Attribute(); 458 attr.attrName = attrName; 459 attr.valueType = VALUE_ARBITRARY; 460 attr.dataType = dataType; 461 attr.required = required; 462 attr.defaultValue = defaultValue; 463 464 element.attrList.add(attrName); 465 element.attrMap.put(attrName, attr); 466 } 467 468 /** 469 * Adds a new attribute to a previously defined element that will 470 * be defined by a set of enumerated values. 471 * 472 * @param elementName the name of the element. 473 * @param attrName the name of the attribute being added. 474 * @param dataType the data type (string format) of the attribute, 475 * one of the {@code DATATYPE_*} constants. 476 * @param required {@code true} if the attribute must be present. 477 * @param defaultValue the default value for the attribute, or 478 * {@code null}. 479 * @param enumeratedValues a {@code List} of 480 * {@code String}s containing the legal values for the 481 * attribute. 482 * 483 * @exception IllegalArgumentException if {@code elementName} 484 * is {@code null}, or is not a legal element name for this 485 * format. 486 * @exception IllegalArgumentException if {@code attrName} is 487 * {@code null}. 488 * @exception IllegalArgumentException if {@code dataType} is 489 * not one of the predefined constants. 490 * @exception IllegalArgumentException if 491 * {@code enumeratedValues} is {@code null}. 492 * @exception IllegalArgumentException if 493 * {@code enumeratedValues} does not contain at least one 494 * entry. 495 * @exception IllegalArgumentException if 496 * {@code enumeratedValues} contains an element that is not a 497 * {@code String} or is {@code null}. 498 */ 499 protected void addAttribute(String elementName, 500 String attrName, 501 int dataType, 502 boolean required, 503 String defaultValue, 504 List<String> enumeratedValues) { 505 Element element = getElement(elementName); 506 if (attrName == null) { 507 throw new IllegalArgumentException("attrName == null!"); 508 } 509 if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) { 510 throw new IllegalArgumentException("Invalid value for dataType!"); 511 } 512 if (enumeratedValues == null) { 513 throw new IllegalArgumentException("enumeratedValues == null!"); 514 } 515 if (enumeratedValues.size() == 0) { 516 throw new IllegalArgumentException("enumeratedValues is empty!"); 517 } 518 Iterator<String> iter = enumeratedValues.iterator(); 519 while (iter.hasNext()) { 520 Object o = iter.next(); 521 if (o == null) { 522 throw new IllegalArgumentException 523 ("enumeratedValues contains a null!"); 524 } 525 if (!(o instanceof String)) { 526 throw new IllegalArgumentException 527 ("enumeratedValues contains a non-String value!"); 528 } 529 } 530 531 Attribute attr = new Attribute(); 532 attr.attrName = attrName; 533 attr.valueType = VALUE_ENUMERATION; 534 attr.dataType = dataType; 535 attr.required = required; 536 attr.defaultValue = defaultValue; 537 attr.enumeratedValues = enumeratedValues; 538 539 element.attrList.add(attrName); 540 element.attrMap.put(attrName, attr); 541 } 542 543 /** 544 * Adds a new attribute to a previously defined element that will 545 * be defined by a range of values. 546 * 547 * @param elementName the name of the element. 548 * @param attrName the name of the attribute being added. 549 * @param dataType the data type (string format) of the attribute, 550 * one of the {@code DATATYPE_*} constants. 551 * @param required {@code true} if the attribute must be present. 552 * @param defaultValue the default value for the attribute, or 553 * {@code null}. 554 * @param minValue the smallest (inclusive or exclusive depending 555 * on the value of {@code minInclusive}) legal value for the 556 * attribute, as a {@code String}. 557 * @param maxValue the largest (inclusive or exclusive depending 558 * on the value of {@code minInclusive}) legal value for the 559 * attribute, as a {@code String}. 560 * @param minInclusive {@code true} if {@code minValue} 561 * is inclusive. 562 * @param maxInclusive {@code true} if {@code maxValue} 563 * is inclusive. 564 * 565 * @exception IllegalArgumentException if {@code elementName} 566 * is {@code null}, or is not a legal element name for this 567 * format. 568 * @exception IllegalArgumentException if {@code attrName} is 569 * {@code null}. 570 * @exception IllegalArgumentException if {@code dataType} is 571 * not one of the predefined constants. 572 */ 573 protected void addAttribute(String elementName, 574 String attrName, 575 int dataType, 576 boolean required, 577 String defaultValue, 578 String minValue, 579 String maxValue, 580 boolean minInclusive, 581 boolean maxInclusive) { 582 Element element = getElement(elementName); 583 if (attrName == null) { 584 throw new IllegalArgumentException("attrName == null!"); 585 } 586 if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) { 587 throw new IllegalArgumentException("Invalid value for dataType!"); 588 } 589 590 Attribute attr = new Attribute(); 591 attr.attrName = attrName; 592 attr.valueType = VALUE_RANGE; 593 if (minInclusive) { 594 attr.valueType |= VALUE_RANGE_MIN_INCLUSIVE_MASK; 595 } 596 if (maxInclusive) { 597 attr.valueType |= VALUE_RANGE_MAX_INCLUSIVE_MASK; 598 } 599 attr.dataType = dataType; 600 attr.required = required; 601 attr.defaultValue = defaultValue; 602 attr.minValue = minValue; 603 attr.maxValue = maxValue; 604 605 element.attrList.add(attrName); 606 element.attrMap.put(attrName, attr); 607 } 608 609 /** 610 * Adds a new attribute to a previously defined element that will 611 * be defined by a list of values. 612 * 613 * @param elementName the name of the element. 614 * @param attrName the name of the attribute being added. 615 * @param dataType the data type (string format) of the attribute, 616 * one of the {@code DATATYPE_*} constants. 617 * @param required {@code true} if the attribute must be present. 618 * @param listMinLength the smallest legal number of list items. 619 * @param listMaxLength the largest legal number of list items. 620 * 621 * @exception IllegalArgumentException if {@code elementName} 622 * is {@code null}, or is not a legal element name for this 623 * format. 624 * @exception IllegalArgumentException if {@code attrName} is 625 * {@code null}. 626 * @exception IllegalArgumentException if {@code dataType} is 627 * not one of the predefined constants. 628 * @exception IllegalArgumentException if 629 * {@code listMinLength} is negative or larger than 630 * {@code listMaxLength}. 631 */ 632 protected void addAttribute(String elementName, 633 String attrName, 634 int dataType, 635 boolean required, 636 int listMinLength, 637 int listMaxLength) { 638 Element element = getElement(elementName); 639 if (attrName == null) { 640 throw new IllegalArgumentException("attrName == null!"); 641 } 642 if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) { 643 throw new IllegalArgumentException("Invalid value for dataType!"); 644 } 645 if (listMinLength < 0 || listMinLength > listMaxLength) { 646 throw new IllegalArgumentException("Invalid list bounds!"); 647 } 648 649 Attribute attr = new Attribute(); 650 attr.attrName = attrName; 651 attr.valueType = VALUE_LIST; 652 attr.dataType = dataType; 653 attr.required = required; 654 attr.listMinLength = listMinLength; 655 attr.listMaxLength = listMaxLength; 656 657 element.attrList.add(attrName); 658 element.attrMap.put(attrName, attr); 659 } 660 661 /** 662 * Adds a new attribute to a previously defined element that will 663 * be defined by the enumerated values {@code TRUE} and 664 * {@code FALSE}, with a datatype of 665 * {@code DATATYPE_BOOLEAN}. 666 * 667 * @param elementName the name of the element. 668 * @param attrName the name of the attribute being added. 669 * @param hasDefaultValue {@code true} if a default value 670 * should be present. 671 * @param defaultValue the default value for the attribute as a 672 * {@code boolean}, ignored if {@code hasDefaultValue} 673 * is {@code false}. 674 * 675 * @exception IllegalArgumentException if {@code elementName} 676 * is {@code null}, or is not a legal element name for this 677 * format. 678 * @exception IllegalArgumentException if {@code attrName} is 679 * {@code null}. 680 */ 681 protected void addBooleanAttribute(String elementName, 682 String attrName, 683 boolean hasDefaultValue, 684 boolean defaultValue) { 685 List<String> values = new ArrayList<>(); 686 values.add("TRUE"); 687 values.add("FALSE"); 688 689 String dval = null; 690 if (hasDefaultValue) { 691 dval = defaultValue ? "TRUE" : "FALSE"; 692 } 693 addAttribute(elementName, 694 attrName, 695 DATATYPE_BOOLEAN, 696 true, 697 dval, 698 values); 699 } 700 701 /** 702 * Removes an attribute from a previously defined element. If no 703 * attribute with the given name was present in the given element, 704 * nothing happens and no exception is thrown. 705 * 706 * @param elementName the name of the element. 707 * @param attrName the name of the attribute being removed. 708 * 709 * @exception IllegalArgumentException if {@code elementName} 710 * is {@code null}, or is not a legal element name for this format. 711 */ 712 protected void removeAttribute(String elementName, String attrName) { 713 Element element = getElement(elementName); 714 element.attrList.remove(attrName); 715 element.attrMap.remove(attrName); 716 } 717 718 /** 719 * Allows an {@code Object} reference of a given class type 720 * to be stored in nodes implementing the named element. The 721 * value of the {@code Object} is unconstrained other than by 722 * its class type. 723 * 724 * <p> If an {@code Object} reference was previously allowed, 725 * the previous settings are overwritten. 726 * 727 * @param elementName the name of the element. 728 * @param classType a {@code Class} variable indicating the 729 * legal class type for the object value. 730 * @param required {@code true} if an object value must be present. 731 * @param defaultValue the default value for the 732 * {@code Object} reference, or {@code null}. 733 * @param <T> the type of the object. 734 * 735 * @exception IllegalArgumentException if {@code elementName} 736 * is {@code null}, or is not a legal element name for this format. 737 */ 738 protected <T> void addObjectValue(String elementName, 739 Class<T> classType, 740 boolean required, 741 T defaultValue) 742 { 743 Element element = getElement(elementName); 744 ObjectValue<T> obj = new ObjectValue<>(); 745 obj.valueType = VALUE_ARBITRARY; 746 obj.classType = classType; 747 obj.defaultValue = defaultValue; 748 749 element.objectValue = obj; 750 } 751 752 /** 753 * Allows an {@code Object} reference of a given class type 754 * to be stored in nodes implementing the named element. The 755 * value of the {@code Object} must be one of the values 756 * given by {@code enumeratedValues}. 757 * 758 * <p> If an {@code Object} reference was previously allowed, 759 * the previous settings are overwritten. 760 * 761 * @param elementName the name of the element. 762 * @param classType a {@code Class} variable indicating the 763 * legal class type for the object value. 764 * @param required {@code true} if an object value must be present. 765 * @param defaultValue the default value for the 766 * {@code Object} reference, or {@code null}. 767 * @param enumeratedValues a {@code List} of 768 * {@code Object}s containing the legal values for the 769 * object reference. 770 * @param <T> the type of the object. 771 * 772 * @exception IllegalArgumentException if {@code elementName} 773 * is {@code null}, or is not a legal element name for this format. 774 * @exception IllegalArgumentException if 775 * {@code enumeratedValues} is {@code null}. 776 * @exception IllegalArgumentException if 777 * {@code enumeratedValues} does not contain at least one 778 * entry. 779 * @exception IllegalArgumentException if 780 * {@code enumeratedValues} contains an element that is not 781 * an instance of the class type denoted by {@code classType} 782 * or is {@code null}. 783 */ 784 protected <T> void addObjectValue(String elementName, 785 Class<T> classType, 786 boolean required, 787 T defaultValue, 788 List<? extends T> enumeratedValues) 789 { 790 Element element = getElement(elementName); 791 if (enumeratedValues == null) { 792 throw new IllegalArgumentException("enumeratedValues == null!"); 793 } 794 if (enumeratedValues.size() == 0) { 795 throw new IllegalArgumentException("enumeratedValues is empty!"); 796 } 797 Iterator<? extends T> iter = enumeratedValues.iterator(); 798 while (iter.hasNext()) { 799 Object o = iter.next(); 800 if (o == null) { 801 throw new IllegalArgumentException("enumeratedValues contains a null!"); 802 } 803 if (!classType.isInstance(o)) { 804 throw new IllegalArgumentException("enumeratedValues contains a value not of class classType!"); 805 } 806 } 807 808 ObjectValue<T> obj = new ObjectValue<>(); 809 obj.valueType = VALUE_ENUMERATION; 810 obj.classType = classType; 811 obj.defaultValue = defaultValue; 812 obj.enumeratedValues = enumeratedValues; 813 814 element.objectValue = obj; 815 } 816 817 /** 818 * Allows an {@code Object} reference of a given class type 819 * to be stored in nodes implementing the named element. The 820 * value of the {@code Object} must be within the range given 821 * by {@code minValue} and {@code maxValue}. 822 * Furthermore, the class type must implement the 823 * {@code Comparable} interface. 824 * 825 * <p> If an {@code Object} reference was previously allowed, 826 * the previous settings are overwritten. 827 * 828 * @param elementName the name of the element. 829 * @param classType a {@code Class} variable indicating the 830 * legal class type for the object value. 831 * @param defaultValue the default value for the 832 * @param minValue the smallest (inclusive or exclusive depending 833 * on the value of {@code minInclusive}) legal value for the 834 * object value, as a {@code String}. 835 * @param maxValue the largest (inclusive or exclusive depending 836 * on the value of {@code minInclusive}) legal value for the 837 * object value, as a {@code String}. 838 * @param minInclusive {@code true} if {@code minValue} 839 * is inclusive. 840 * @param maxInclusive {@code true} if {@code maxValue} 841 * is inclusive. 842 * @param <T> the type of the object. 843 * 844 * @exception IllegalArgumentException if {@code elementName} 845 * is {@code null}, or is not a legal element name for this 846 * format. 847 */ 848 protected <T extends Object & Comparable<? super T>> void 849 addObjectValue(String elementName, 850 Class<T> classType, 851 T defaultValue, 852 Comparable<? super T> minValue, 853 Comparable<? super T> maxValue, 854 boolean minInclusive, 855 boolean maxInclusive) 856 { 857 Element element = getElement(elementName); 858 ObjectValue<T> obj = new ObjectValue<>(); 859 obj.valueType = VALUE_RANGE; 860 if (minInclusive) { 861 obj.valueType |= VALUE_RANGE_MIN_INCLUSIVE_MASK; 862 } 863 if (maxInclusive) { 864 obj.valueType |= VALUE_RANGE_MAX_INCLUSIVE_MASK; 865 } 866 obj.classType = classType; 867 obj.defaultValue = defaultValue; 868 obj.minValue = minValue; 869 obj.maxValue = maxValue; 870 871 element.objectValue = obj; 872 } 873 874 /** 875 * Allows an {@code Object} reference of a given class type 876 * to be stored in nodes implementing the named element. The 877 * value of the {@code Object} must an array of objects of 878 * class type given by {@code classType}, with at least 879 * {@code arrayMinLength} and at most 880 * {@code arrayMaxLength} elements. 881 * 882 * <p> If an {@code Object} reference was previously allowed, 883 * the previous settings are overwritten. 884 * 885 * @param elementName the name of the element. 886 * @param classType a {@code Class} variable indicating the 887 * legal class type for the object value. 888 * @param arrayMinLength the smallest legal length for the array. 889 * @param arrayMaxLength the largest legal length for the array. 890 * 891 * @exception IllegalArgumentException if {@code elementName} is 892 * not a legal element name for this format. 893 */ 894 protected void addObjectValue(String elementName, 895 Class<?> classType, 896 int arrayMinLength, 897 int arrayMaxLength) { 898 Element element = getElement(elementName); 899 ObjectValue<Object> obj = new ObjectValue<>(); 900 obj.valueType = VALUE_LIST; 901 obj.classType = classType; 902 obj.arrayMinLength = arrayMinLength; 903 obj.arrayMaxLength = arrayMaxLength; 904 905 element.objectValue = obj; 906 } 907 908 /** 909 * Disallows an {@code Object} reference from being stored in 910 * nodes implementing the named element. 911 * 912 * @param elementName the name of the element. 913 * 914 * @exception IllegalArgumentException if {@code elementName} is 915 * not a legal element name for this format. 916 */ 917 protected void removeObjectValue(String elementName) { 918 Element element = getElement(elementName); 919 element.objectValue = null; 920 } 921 922 // Utility method 923 924 // Methods from IIOMetadataFormat 925 926 // Root 927 928 public String getRootName() { 929 return rootName; 930 } 931 932 // Multiplicity 933 934 public abstract boolean canNodeAppear(String elementName, 935 ImageTypeSpecifier imageType); 936 937 public int getElementMinChildren(String elementName) { 938 Element element = getElement(elementName); 939 if (element.childPolicy != CHILD_POLICY_REPEAT) { 940 throw new IllegalArgumentException("Child policy not CHILD_POLICY_REPEAT!"); 941 } 942 return element.minChildren; 943 } 944 945 public int getElementMaxChildren(String elementName) { 946 Element element = getElement(elementName); 947 if (element.childPolicy != CHILD_POLICY_REPEAT) { 948 throw new IllegalArgumentException("Child policy not CHILD_POLICY_REPEAT!"); 949 } 950 return element.maxChildren; 951 } 952 953 private String getResource(String key, Locale locale) { 954 if (locale == null) { 955 locale = Locale.getDefault(); 956 } 957 958 /** 959 * Per the class documentation, resource bundles, including localized ones 960 * are intended to be delivered by the subclasser - ie supplier of the 961 * metadataformat. For the standard format and all standard plugins that 962 * is the JDK. For 3rd party plugins that they will supply their own. 963 * This includes plugins bundled with applets/applications. 964 * In all cases this means it is sufficient to search for those resource 965 * in the module that is providing the MetadataFormatImpl subclass. 966 */ 967 try { 968 ResourceBundle bundle = ResourceBundle.getBundle(resourceBaseName, locale, 969 this.getClass().getModule()); 970 return bundle.getString(key); 971 } catch (MissingResourceException e) { 972 return null; 973 } 974 } 975 976 /** 977 * Returns a {@code String} containing a description of the 978 * named element, or {@code null}. The description will be 979 * localized for the supplied {@code Locale} if possible. 980 * 981 * <p> The default implementation will first locate a 982 * {@code ResourceBundle} using the current resource base 983 * name set by {@code setResourceBaseName} and the supplied 984 * {@code Locale}, using the fallback mechanism described in 985 * the comments for {@code ResourceBundle.getBundle}. If a 986 * {@code ResourceBundle} is found, the element name will be 987 * used as a key to its {@code getString} method, and the 988 * result returned. If no {@code ResourceBundle} is found, 989 * or no such key is present, {@code null} will be returned. 990 * 991 * <p> If {@code locale} is {@code null}, the current 992 * default {@code Locale} returned by {@code Locale.getLocale} 993 * will be used. 994 * 995 * @param elementName the name of the element. 996 * @param locale the {@code Locale} for which localization 997 * will be attempted. 998 * 999 * @return the element description. 1000 * 1001 * @exception IllegalArgumentException if {@code elementName} 1002 * is {@code null}, or is not a legal element name for this format. 1003 * 1004 * @see #setResourceBaseName 1005 */ 1006 public String getElementDescription(String elementName, 1007 Locale locale) { 1008 Element element = getElement(elementName); 1009 return getResource(elementName, locale); 1010 } 1011 1012 // Children 1013 1014 public int getChildPolicy(String elementName) { 1015 Element element = getElement(elementName); 1016 return element.childPolicy; 1017 } 1018 1019 public String[] getChildNames(String elementName) { 1020 Element element = getElement(elementName); 1021 if (element.childPolicy == CHILD_POLICY_EMPTY) { 1022 return null; 1023 } 1024 return element.childList.toArray(new String[0]); 1025 } 1026 1027 // Attributes 1028 1029 public String[] getAttributeNames(String elementName) { 1030 Element element = getElement(elementName); 1031 List<String> names = element.attrList; 1032 1033 String[] result = new String[names.size()]; 1034 return names.toArray(result); 1035 } 1036 1037 public int getAttributeValueType(String elementName, String attrName) { 1038 Attribute attr = getAttribute(elementName, attrName); 1039 return attr.valueType; 1040 } 1041 1042 public int getAttributeDataType(String elementName, String attrName) { 1043 Attribute attr = getAttribute(elementName, attrName); 1044 return attr.dataType; 1045 } 1046 1047 public boolean isAttributeRequired(String elementName, String attrName) { 1048 Attribute attr = getAttribute(elementName, attrName); 1049 return attr.required; 1050 } 1051 1052 public String getAttributeDefaultValue(String elementName, 1053 String attrName) { 1054 Attribute attr = getAttribute(elementName, attrName); 1055 return attr.defaultValue; 1056 } 1057 1058 public String[] getAttributeEnumerations(String elementName, 1059 String attrName) { 1060 Attribute attr = getAttribute(elementName, attrName); 1061 if (attr.valueType != VALUE_ENUMERATION) { 1062 throw new IllegalArgumentException 1063 ("Attribute not an enumeration!"); 1064 } 1065 1066 List<String> values = attr.enumeratedValues; 1067 String[] result = new String[values.size()]; 1068 return values.toArray(result); 1069 } 1070 1071 public String getAttributeMinValue(String elementName, String attrName) { 1072 Attribute attr = getAttribute(elementName, attrName); 1073 if (attr.valueType != VALUE_RANGE && 1074 attr.valueType != VALUE_RANGE_MIN_INCLUSIVE && 1075 attr.valueType != VALUE_RANGE_MAX_INCLUSIVE && 1076 attr.valueType != VALUE_RANGE_MIN_MAX_INCLUSIVE) { 1077 throw new IllegalArgumentException("Attribute not a range!"); 1078 } 1079 1080 return attr.minValue; 1081 } 1082 1083 public String getAttributeMaxValue(String elementName, String attrName) { 1084 Attribute attr = getAttribute(elementName, attrName); 1085 if (attr.valueType != VALUE_RANGE && 1086 attr.valueType != VALUE_RANGE_MIN_INCLUSIVE && 1087 attr.valueType != VALUE_RANGE_MAX_INCLUSIVE && 1088 attr.valueType != VALUE_RANGE_MIN_MAX_INCLUSIVE) { 1089 throw new IllegalArgumentException("Attribute not a range!"); 1090 } 1091 1092 return attr.maxValue; 1093 } 1094 1095 public int getAttributeListMinLength(String elementName, String attrName) { 1096 Attribute attr = getAttribute(elementName, attrName); 1097 if (attr.valueType != VALUE_LIST) { 1098 throw new IllegalArgumentException("Attribute not a list!"); 1099 } 1100 1101 return attr.listMinLength; 1102 } 1103 1104 public int getAttributeListMaxLength(String elementName, String attrName) { 1105 Attribute attr = getAttribute(elementName, attrName); 1106 if (attr.valueType != VALUE_LIST) { 1107 throw new IllegalArgumentException("Attribute not a list!"); 1108 } 1109 1110 return attr.listMaxLength; 1111 } 1112 1113 /** 1114 * Returns a {@code String} containing a description of the 1115 * named attribute, or {@code null}. The description will be 1116 * localized for the supplied {@code Locale} if possible. 1117 * 1118 * <p> The default implementation will first locate a 1119 * {@code ResourceBundle} using the current resource base 1120 * name set by {@code setResourceBaseName} and the supplied 1121 * {@code Locale}, using the fallback mechanism described in 1122 * the comments for {@code ResourceBundle.getBundle}. If a 1123 * {@code ResourceBundle} is found, the element name followed 1124 * by a "/" character followed by the attribute name 1125 * ({@code elementName + "/" + attrName}) will be used as a 1126 * key to its {@code getString} method, and the result 1127 * returned. If no {@code ResourceBundle} is found, or no 1128 * such key is present, {@code null} will be returned. 1129 * 1130 * <p> If {@code locale} is {@code null}, the current 1131 * default {@code Locale} returned by {@code Locale.getLocale} 1132 * will be used. 1133 * 1134 * @param elementName the name of the element. 1135 * @param attrName the name of the attribute. 1136 * @param locale the {@code Locale} for which localization 1137 * will be attempted, or {@code null}. 1138 * 1139 * @return the attribute description. 1140 * 1141 * @exception IllegalArgumentException if {@code elementName} 1142 * is {@code null}, or is not a legal element name for this format. 1143 * @exception IllegalArgumentException if {@code attrName} is 1144 * {@code null} or is not a legal attribute name for this 1145 * element. 1146 * 1147 * @see #setResourceBaseName 1148 */ 1149 public String getAttributeDescription(String elementName, 1150 String attrName, 1151 Locale locale) { 1152 Element element = getElement(elementName); 1153 if (attrName == null) { 1154 throw new IllegalArgumentException("attrName == null!"); 1155 } 1156 Attribute attr = element.attrMap.get(attrName); 1157 if (attr == null) { 1158 throw new IllegalArgumentException("No such attribute!"); 1159 } 1160 1161 String key = elementName + "/" + attrName; 1162 return getResource(key, locale); 1163 } 1164 1165 private ObjectValue<?> getObjectValue(String elementName) { 1166 Element element = getElement(elementName); 1167 ObjectValue<?> objv = element.objectValue; 1168 if (objv == null) { 1169 throw new IllegalArgumentException("No object within element " + 1170 elementName + "!"); 1171 } 1172 return objv; 1173 } 1174 1175 public int getObjectValueType(String elementName) { 1176 Element element = getElement(elementName); 1177 ObjectValue<?> objv = element.objectValue; 1178 if (objv == null) { 1179 return VALUE_NONE; 1180 } 1181 return objv.valueType; 1182 } 1183 1184 public Class<?> getObjectClass(String elementName) { 1185 ObjectValue<?> objv = getObjectValue(elementName); 1186 return objv.classType; 1187 } 1188 1189 public Object getObjectDefaultValue(String elementName) { 1190 ObjectValue<?> objv = getObjectValue(elementName); 1191 return objv.defaultValue; 1192 } 1193 1194 public Object[] getObjectEnumerations(String elementName) { 1195 ObjectValue<?> objv = getObjectValue(elementName); 1196 if (objv.valueType != VALUE_ENUMERATION) { 1197 throw new IllegalArgumentException("Not an enumeration!"); 1198 } 1199 List<?> vlist = objv.enumeratedValues; 1200 Object[] values = new Object[vlist.size()]; 1201 return vlist.toArray(values); 1202 } 1203 1204 public Comparable<?> getObjectMinValue(String elementName) { 1205 ObjectValue<?> objv = getObjectValue(elementName); 1206 if ((objv.valueType & VALUE_RANGE) != VALUE_RANGE) { 1207 throw new IllegalArgumentException("Not a range!"); 1208 } 1209 return objv.minValue; 1210 } 1211 1212 public Comparable<?> getObjectMaxValue(String elementName) { 1213 ObjectValue<?> objv = getObjectValue(elementName); 1214 if ((objv.valueType & VALUE_RANGE) != VALUE_RANGE) { 1215 throw new IllegalArgumentException("Not a range!"); 1216 } 1217 return objv.maxValue; 1218 } 1219 1220 public int getObjectArrayMinLength(String elementName) { 1221 ObjectValue<?> objv = getObjectValue(elementName); 1222 if (objv.valueType != VALUE_LIST) { 1223 throw new IllegalArgumentException("Not a list!"); 1224 } 1225 return objv.arrayMinLength; 1226 } 1227 1228 public int getObjectArrayMaxLength(String elementName) { 1229 ObjectValue<?> objv = getObjectValue(elementName); 1230 if (objv.valueType != VALUE_LIST) { 1231 throw new IllegalArgumentException("Not a list!"); 1232 } 1233 return objv.arrayMaxLength; 1234 } 1235 1236 // Standard format descriptor 1237 1238 private static synchronized void createStandardFormat() { 1239 if (standardFormat == null) { 1240 standardFormat = new StandardMetadataFormat(); 1241 } 1242 } 1243 1244 /** 1245 * Returns an {@code IIOMetadataFormat} object describing the 1246 * standard, plug-in neutral {@code javax.imageio_1.0} 1247 * metadata document format described in the comment of the 1248 * {@code javax.imageio.metadata} package. 1249 * 1250 * @return a predefined {@code IIOMetadataFormat} instance. 1251 */ 1252 public static IIOMetadataFormat getStandardFormatInstance() { 1253 createStandardFormat(); 1254 return standardFormat; 1255 } 1256} 1257