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 26 package javax.imageio.metadata; 27 28 import java.util.ArrayList; 29 import java.util.Collection; 30 import java.util.HashMap; 31 import java.util.Iterator; 32 import java.util.List; 33 import java.util.Locale; 34 import java.util.Map; 35 import java.util.MissingResourceException; 36 import java.util.ResourceBundle; 37 import javax.imageio.ImageTypeSpecifier; 38 import 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 */ 77 public 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 */ IIOMetadataFormatImpl(String rootName, int childPolicy)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 */ IIOMetadataFormatImpl(String rootName, int minChildren, int maxChildren)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 */ setResourceBaseName(String resourceBaseName)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 */ getResourceBaseName()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 */ getElement(String elementName, boolean mustAppear)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 getElement(String elementName)284 private Element getElement(String elementName) { 285 return getElement(elementName, true); 286 } 287 288 // Utility method for locating an attribute getAttribute(String elementName, String attrName)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 */ addElement(String elementName, String parentName, int childPolicy)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 */ addElement(String elementName, String parentName, int minChildren, int maxChildren)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 */ addChildElement(String elementName, String parentName)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 */ removeElement(String elementName)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 */ addAttribute(String elementName, String attrName, int dataType, boolean required, String defaultValue)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 */ addAttribute(String elementName, String attrName, int dataType, boolean required, String defaultValue, List<String> enumeratedValues)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 */ addAttribute(String elementName, String attrName, int dataType, boolean required, String defaultValue, String minValue, String maxValue, boolean minInclusive, boolean maxInclusive)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 */ addAttribute(String elementName, String attrName, int dataType, boolean required, int listMinLength, int listMaxLength)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 */ addBooleanAttribute(String elementName, String attrName, boolean hasDefaultValue, boolean defaultValue)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 */ removeAttribute(String elementName, String attrName)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 */ addObjectValue(String elementName, Class<T> classType, boolean required, T defaultValue)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 */ addObjectValue(String elementName, Class<T> classType, boolean required, T defaultValue, List<? extends T> enumeratedValues)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 addObjectValue(String elementName, Class<T> classType, T defaultValue, Comparable<? super T> minValue, Comparable<? super T> maxValue, boolean minInclusive, boolean maxInclusive)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 */ addObjectValue(String elementName, Class<?> classType, int arrayMinLength, int arrayMaxLength)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 */ removeObjectValue(String elementName)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 getRootName()928 public String getRootName() { 929 return rootName; 930 } 931 932 // Multiplicity 933 canNodeAppear(String elementName, ImageTypeSpecifier imageType)934 public abstract boolean canNodeAppear(String elementName, 935 ImageTypeSpecifier imageType); 936 getElementMinChildren(String elementName)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 getElementMaxChildren(String elementName)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 getResource(String key, Locale locale)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 */ getElementDescription(String elementName, Locale locale)1006 public String getElementDescription(String elementName, 1007 Locale locale) { 1008 Element element = getElement(elementName); 1009 return getResource(elementName, locale); 1010 } 1011 1012 // Children 1013 getChildPolicy(String elementName)1014 public int getChildPolicy(String elementName) { 1015 Element element = getElement(elementName); 1016 return element.childPolicy; 1017 } 1018 getChildNames(String elementName)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 getAttributeNames(String elementName)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 getAttributeValueType(String elementName, String attrName)1037 public int getAttributeValueType(String elementName, String attrName) { 1038 Attribute attr = getAttribute(elementName, attrName); 1039 return attr.valueType; 1040 } 1041 getAttributeDataType(String elementName, String attrName)1042 public int getAttributeDataType(String elementName, String attrName) { 1043 Attribute attr = getAttribute(elementName, attrName); 1044 return attr.dataType; 1045 } 1046 isAttributeRequired(String elementName, String attrName)1047 public boolean isAttributeRequired(String elementName, String attrName) { 1048 Attribute attr = getAttribute(elementName, attrName); 1049 return attr.required; 1050 } 1051 getAttributeDefaultValue(String elementName, String attrName)1052 public String getAttributeDefaultValue(String elementName, 1053 String attrName) { 1054 Attribute attr = getAttribute(elementName, attrName); 1055 return attr.defaultValue; 1056 } 1057 getAttributeEnumerations(String elementName, String attrName)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 getAttributeMinValue(String elementName, String attrName)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 getAttributeMaxValue(String elementName, String attrName)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 getAttributeListMinLength(String elementName, String attrName)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 getAttributeListMaxLength(String elementName, String attrName)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 */ getAttributeDescription(String elementName, String attrName, Locale locale)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 getObjectValue(String elementName)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 getObjectValueType(String elementName)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 getObjectClass(String elementName)1184 public Class<?> getObjectClass(String elementName) { 1185 ObjectValue<?> objv = getObjectValue(elementName); 1186 return objv.classType; 1187 } 1188 getObjectDefaultValue(String elementName)1189 public Object getObjectDefaultValue(String elementName) { 1190 ObjectValue<?> objv = getObjectValue(elementName); 1191 return objv.defaultValue; 1192 } 1193 getObjectEnumerations(String elementName)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 getObjectMinValue(String elementName)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 getObjectMaxValue(String elementName)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 getObjectArrayMinLength(String elementName)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 getObjectArrayMaxLength(String elementName)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 createStandardFormat()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 */ getStandardFormatInstance()1252 public static IIOMetadataFormat getStandardFormatInstance() { 1253 createStandardFormat(); 1254 return standardFormat; 1255 } 1256 } 1257