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</code> interface. In addition, a static 43 * instance representing the standard, plug-in neutral 44 * <code>javax_imageio_1.0</code> format is provided by the 45 * <code>getStandardFormatInstance</code> method. 46 * 47 * <p> In order to supply localized descriptions of elements and 48 * attributes, a <code>ResourceBundle</code> with a base name of 49 * <code>this.getClass().getName() + "Resources"</code> should be 50 * supplied via the usual mechanism used by 51 * <code>ResourceBundle.getBundle</code>. 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</code>, plus the string 55 * "Resources", plus the country, language, and variant codes 56 * separated by underscores). At run time, calls to 57 * <code>getElementDescription</code> or 58 * <code>getAttributeDescription</code> 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</code>'s 63 * <code>getString</code> 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</code> 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</code> and 72 * <code>getAttributeDescription</code>. 73 * 74 * @see ResourceBundle#getBundle(String,Locale) 75 * 76 */ 77 public abstract class IIOMetadataFormatImpl implements IIOMetadataFormat { 78 79 /** 80 * A <code>String</code> constant containing the standard format 81 * name, <code>"javax_imageio_1.0"</code>. 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</code> instance, 156 * with a given root element name and child policy (other than 157 * <code>CHILD_POLICY_REPEAT</code>). Additional elements, and 158 * their attributes and <code>Object</code> reference information 159 * may be added using the various <code>add</code> methods. 160 * 161 * @param rootName the name of the root element. 162 * @param childPolicy one of the <code>CHILD_POLICY_*</code> constants, 163 * other than <code>CHILD_POLICY_REPEAT</code>. 164 * 165 * @exception IllegalArgumentException if <code>rootName</code> is 166 * <code>null</code>. 167 * @exception IllegalArgumentException if <code>childPolicy</code> 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</code> instance, 192 * with a given root element name and a child policy of 193 * <code>CHILD_POLICY_REPEAT</code>. Additional elements, and 194 * their attributes and <code>Object</code> reference information 195 * may be added using the various <code>add</code> 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</code> is 202 * <code>null</code>. 203 * @exception IllegalArgumentException if <code>minChildren</code> 204 * is negative or larger than <code>maxChildren</code>. 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</code>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 <code>this.getClass().getName() + 236 * "Resources"</code>. 237 * 238 * @param resourceBaseName a <code>String</code> containing the new 239 * base name. 240 * 241 * @exception IllegalArgumentException if 242 * <code>resourceBaseName</code> is <code>null</code>. 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</code>s. 256 * 257 * @return a <code>String</code> 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</code>, throw an 269 * <code>IllegalArgumentException</code> if no such node exists; 270 * if <code>false</code>, 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</code>. 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_*</code> 309 * constants, other than <code>CHILD_POLICY_REPEAT</code>, 310 * indicating the child policy of the new element. 311 * 312 * @exception IllegalArgumentException if <code>parentName</code> 313 * is <code>null</code>, or is not a legal element name for this 314 * format. 315 * @exception IllegalArgumentException if <code>childPolicy</code> 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</code>. 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</code> 350 * is <code>null</code>, or is not a legal element name for this 351 * format. 352 * @exception IllegalArgumentException if <code>minChildren</code> 353 * is negative or larger than <code>maxChildren</code>. 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</code> 389 * is <code>null</code>, or is not a legal element name for this 390 * format. 391 * @exception IllegalArgumentException if <code>parentName</code> 392 * is <code>null</code>, 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_*</code> constants. 432 * @param required <code>true</code> if the attribute must be present. 433 * @param defaultValue the default value for the attribute, or 434 * <code>null</code>. 435 * 436 * @exception IllegalArgumentException if <code>elementName</code> 437 * is <code>null</code>, or is not a legal element name for this 438 * format. 439 * @exception IllegalArgumentException if <code>attrName</code> is 440 * <code>null</code>. 441 * @exception IllegalArgumentException if <code>dataType</code> 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_*</code> constants. 476 * @param required <code>true</code> if the attribute must be present. 477 * @param defaultValue the default value for the attribute, or 478 * <code>null</code>. 479 * @param enumeratedValues a <code>List</code> of 480 * <code>String</code>s containing the legal values for the 481 * attribute. 482 * 483 * @exception IllegalArgumentException if <code>elementName</code> 484 * is <code>null</code>, or is not a legal element name for this 485 * format. 486 * @exception IllegalArgumentException if <code>attrName</code> is 487 * <code>null</code>. 488 * @exception IllegalArgumentException if <code>dataType</code> is 489 * not one of the predefined constants. 490 * @exception IllegalArgumentException if 491 * <code>enumeratedValues</code> is <code>null</code>. 492 * @exception IllegalArgumentException if 493 * <code>enumeratedValues</code> does not contain at least one 494 * entry. 495 * @exception IllegalArgumentException if 496 * <code>enumeratedValues</code> contains an element that is not a 497 * <code>String</code> or is <code>null</code>. 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_*</code> constants. 551 * @param required <code>true</code> if the attribute must be present. 552 * @param defaultValue the default value for the attribute, or 553 * <code>null</code>. 554 * @param minValue the smallest (inclusive or exclusive depending 555 * on the value of <code>minInclusive</code>) legal value for the 556 * attribute, as a <code>String</code>. 557 * @param maxValue the largest (inclusive or exclusive depending 558 * on the value of <code>minInclusive</code>) legal value for the 559 * attribute, as a <code>String</code>. 560 * @param minInclusive <code>true</code> if <code>minValue</code> 561 * is inclusive. 562 * @param maxInclusive <code>true</code> if <code>maxValue</code> 563 * is inclusive. 564 * 565 * @exception IllegalArgumentException if <code>elementName</code> 566 * is <code>null</code>, or is not a legal element name for this 567 * format. 568 * @exception IllegalArgumentException if <code>attrName</code> is 569 * <code>null</code>. 570 * @exception IllegalArgumentException if <code>dataType</code> 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_*</code> constants. 617 * @param required <code>true</code> 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</code> 622 * is <code>null</code>, or is not a legal element name for this 623 * format. 624 * @exception IllegalArgumentException if <code>attrName</code> is 625 * <code>null</code>. 626 * @exception IllegalArgumentException if <code>dataType</code> is 627 * not one of the predefined constants. 628 * @exception IllegalArgumentException if 629 * <code>listMinLength</code> is negative or larger than 630 * <code>listMaxLength</code>. 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</code> and 664 * <code>FALSE</code>, with a datatype of 665 * <code>DATATYPE_BOOLEAN</code>. 666 * 667 * @param elementName the name of the element. 668 * @param attrName the name of the attribute being added. 669 * @param hasDefaultValue <code>true</code> if a default value 670 * should be present. 671 * @param defaultValue the default value for the attribute as a 672 * <code>boolean</code>, ignored if <code>hasDefaultValue</code> 673 * is <code>false</code>. 674 * 675 * @exception IllegalArgumentException if <code>elementName</code> 676 * is <code>null</code>, or is not a legal element name for this 677 * format. 678 * @exception IllegalArgumentException if <code>attrName</code> is 679 * <code>null</code>. 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</code> 710 * is <code>null</code>, 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</code> reference of a given class type 720 * to be stored in nodes implementing the named element. The 721 * value of the <code>Object</code> is unconstrained other than by 722 * its class type. 723 * 724 * <p> If an <code>Object</code> 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</code> variable indicating the 729 * legal class type for the object value. 730 * @param required <code>true</code> if an object value must be present. 731 * @param defaultValue the default value for the 732 * <code>Object</code> reference, or <code>null</code>. 733 * @param <T> the type of the object. 734 * 735 * @exception IllegalArgumentException if <code>elementName</code> 736 * is <code>null</code>, 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</code> reference of a given class type 754 * to be stored in nodes implementing the named element. The 755 * value of the <code>Object</code> must be one of the values 756 * given by <code>enumeratedValues</code>. 757 * 758 * <p> If an <code>Object</code> 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</code> variable indicating the 763 * legal class type for the object value. 764 * @param required <code>true</code> if an object value must be present. 765 * @param defaultValue the default value for the 766 * <code>Object</code> reference, or <code>null</code>. 767 * @param enumeratedValues a <code>List</code> of 768 * <code>Object</code>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</code> 773 * is <code>null</code>, or is not a legal element name for this format. 774 * @exception IllegalArgumentException if 775 * <code>enumeratedValues</code> is <code>null</code>. 776 * @exception IllegalArgumentException if 777 * <code>enumeratedValues</code> does not contain at least one 778 * entry. 779 * @exception IllegalArgumentException if 780 * <code>enumeratedValues</code> contains an element that is not 781 * an instance of the class type denoted by <code>classType</code> 782 * or is <code>null</code>. 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</code> reference of a given class type 819 * to be stored in nodes implementing the named element. The 820 * value of the <code>Object</code> must be within the range given 821 * by <code>minValue</code> and <code>maxValue</code>. 822 * Furthermore, the class type must implement the 823 * <code>Comparable</code> interface. 824 * 825 * <p> If an <code>Object</code> 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</code> 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</code>) legal value for the 834 * object value, as a <code>String</code>. 835 * @param maxValue the largest (inclusive or exclusive depending 836 * on the value of <code>minInclusive</code>) legal value for the 837 * object value, as a <code>String</code>. 838 * @param minInclusive <code>true</code> if <code>minValue</code> 839 * is inclusive. 840 * @param maxInclusive <code>true</code> if <code>maxValue</code> 841 * is inclusive. 842 * @param <T> the type of the object. 843 * 844 * @exception IllegalArgumentException if <code>elementName</code> 845 * is <code>null</code>, 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</code> reference of a given class type 876 * to be stored in nodes implementing the named element. The 877 * value of the <code>Object</code> must an array of objects of 878 * class type given by <code>classType</code>, with at least 879 * <code>arrayMinLength</code> and at most 880 * <code>arrayMaxLength</code> elements. 881 * 882 * <p> If an <code>Object</code> 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</code> 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</code> 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</code> 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</code> 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 * If an applet supplies an implementation of IIOMetadataFormat and 960 * resource bundles, then the resource bundle will need to be 961 * accessed via the applet class loader. So first try the context 962 * class loader to locate the resource bundle. 963 * If that throws MissingResourceException, then try the 964 * system class loader. 965 */ 966 ClassLoader loader = 967 java.security.AccessController.doPrivileged( 968 new java.security.PrivilegedAction<ClassLoader>() { 969 public ClassLoader run() { 970 return Thread.currentThread().getContextClassLoader(); 971 } 972 }); 973 974 ResourceBundle bundle = null; 975 try { 976 bundle = ResourceBundle.getBundle(resourceBaseName, 977 locale, loader); 978 } catch (MissingResourceException mre) { 979 try { 980 bundle = ResourceBundle.getBundle(resourceBaseName, locale); 981 } catch (MissingResourceException mre1) { 982 return null; 983 } 984 } 985 986 try { 987 return bundle.getString(key); 988 } catch (MissingResourceException e) { 989 return null; 990 } 991 } 992 993 /** 994 * Returns a <code>String</code> containing a description of the 995 * named element, or <code>null</code>. The description will be 996 * localized for the supplied <code>Locale</code> if possible. 997 * 998 * <p> The default implementation will first locate a 999 * <code>ResourceBundle</code> using the current resource base 1000 * name set by <code>setResourceBaseName</code> and the supplied 1001 * <code>Locale</code>, using the fallback mechanism described in 1002 * the comments for <code>ResourceBundle.getBundle</code>. If a 1003 * <code>ResourceBundle</code> is found, the element name will be 1004 * used as a key to its <code>getString</code> method, and the 1005 * result returned. If no <code>ResourceBundle</code> is found, 1006 * or no such key is present, <code>null</code> will be returned. 1007 * 1008 * <p> If <code>locale</code> is <code>null</code>, the current 1009 * default <code>Locale</code> returned by <code>Locale.getLocale</code> 1010 * will be used. 1011 * 1012 * @param elementName the name of the element. 1013 * @param locale the <code>Locale</code> for which localization 1014 * will be attempted. 1015 * 1016 * @return the element description. 1017 * 1018 * @exception IllegalArgumentException if <code>elementName</code> 1019 * is <code>null</code>, or is not a legal element name for this format. 1020 * 1021 * @see #setResourceBaseName 1022 */ 1023 public String getElementDescription(String elementName, 1024 Locale locale) { 1025 Element element = getElement(elementName); 1026 return getResource(elementName, locale); 1027 } 1028 1029 // Children 1030 1031 public int getChildPolicy(String elementName) { 1032 Element element = getElement(elementName); 1033 return element.childPolicy; 1034 } 1035 1036 public String[] getChildNames(String elementName) { 1037 Element element = getElement(elementName); 1038 if (element.childPolicy == CHILD_POLICY_EMPTY) { 1039 return null; 1040 } 1041 return element.childList.toArray(new String[0]); 1042 } 1043 1044 // Attributes 1045 1046 public String[] getAttributeNames(String elementName) { 1047 Element element = getElement(elementName); 1048 List<String> names = element.attrList; 1049 1050 String[] result = new String[names.size()]; 1051 return names.toArray(result); 1052 } 1053 1054 public int getAttributeValueType(String elementName, String attrName) { 1055 Attribute attr = getAttribute(elementName, attrName); 1056 return attr.valueType; 1057 } 1058 1059 public int getAttributeDataType(String elementName, String attrName) { 1060 Attribute attr = getAttribute(elementName, attrName); 1061 return attr.dataType; 1062 } 1063 1064 public boolean isAttributeRequired(String elementName, String attrName) { 1065 Attribute attr = getAttribute(elementName, attrName); 1066 return attr.required; 1067 } 1068 1069 public String getAttributeDefaultValue(String elementName, 1070 String attrName) { 1071 Attribute attr = getAttribute(elementName, attrName); 1072 return attr.defaultValue; 1073 } 1074 1075 public String[] getAttributeEnumerations(String elementName, 1076 String attrName) { 1077 Attribute attr = getAttribute(elementName, attrName); 1078 if (attr.valueType != VALUE_ENUMERATION) { 1079 throw new IllegalArgumentException 1080 ("Attribute not an enumeration!"); 1081 } 1082 1083 List<String> values = attr.enumeratedValues; 1084 String[] result = new String[values.size()]; 1085 return values.toArray(result); 1086 } 1087 1088 public String getAttributeMinValue(String elementName, String attrName) { 1089 Attribute attr = getAttribute(elementName, attrName); 1090 if (attr.valueType != VALUE_RANGE && 1091 attr.valueType != VALUE_RANGE_MIN_INCLUSIVE && 1092 attr.valueType != VALUE_RANGE_MAX_INCLUSIVE && 1093 attr.valueType != VALUE_RANGE_MIN_MAX_INCLUSIVE) { 1094 throw new IllegalArgumentException("Attribute not a range!"); 1095 } 1096 1097 return attr.minValue; 1098 } 1099 1100 public String getAttributeMaxValue(String elementName, String attrName) { 1101 Attribute attr = getAttribute(elementName, attrName); 1102 if (attr.valueType != VALUE_RANGE && 1103 attr.valueType != VALUE_RANGE_MIN_INCLUSIVE && 1104 attr.valueType != VALUE_RANGE_MAX_INCLUSIVE && 1105 attr.valueType != VALUE_RANGE_MIN_MAX_INCLUSIVE) { 1106 throw new IllegalArgumentException("Attribute not a range!"); 1107 } 1108 1109 return attr.maxValue; 1110 } 1111 1112 public int getAttributeListMinLength(String elementName, String attrName) { 1113 Attribute attr = getAttribute(elementName, attrName); 1114 if (attr.valueType != VALUE_LIST) { 1115 throw new IllegalArgumentException("Attribute not a list!"); 1116 } 1117 1118 return attr.listMinLength; 1119 } 1120 1121 public int getAttributeListMaxLength(String elementName, String attrName) { 1122 Attribute attr = getAttribute(elementName, attrName); 1123 if (attr.valueType != VALUE_LIST) { 1124 throw new IllegalArgumentException("Attribute not a list!"); 1125 } 1126 1127 return attr.listMaxLength; 1128 } 1129 1130 /** 1131 * Returns a <code>String</code> containing a description of the 1132 * named attribute, or <code>null</code>. The description will be 1133 * localized for the supplied <code>Locale</code> if possible. 1134 * 1135 * <p> The default implementation will first locate a 1136 * <code>ResourceBundle</code> using the current resource base 1137 * name set by <code>setResourceBaseName</code> and the supplied 1138 * <code>Locale</code>, using the fallback mechanism described in 1139 * the comments for <code>ResourceBundle.getBundle</code>. If a 1140 * <code>ResourceBundle</code> is found, the element name followed 1141 * by a "/" character followed by the attribute name 1142 * (<code>elementName + "/" + attrName</code>) will be used as a 1143 * key to its <code>getString</code> method, and the result 1144 * returned. If no <code>ResourceBundle</code> is found, or no 1145 * such key is present, <code>null</code> will be returned. 1146 * 1147 * <p> If <code>locale</code> is <code>null</code>, the current 1148 * default <code>Locale</code> returned by <code>Locale.getLocale</code> 1149 * will be used. 1150 * 1151 * @param elementName the name of the element. 1152 * @param attrName the name of the attribute. 1153 * @param locale the <code>Locale</code> for which localization 1154 * will be attempted, or <code>null</code>. 1155 * 1156 * @return the attribute description. 1157 * 1158 * @exception IllegalArgumentException if <code>elementName</code> 1159 * is <code>null</code>, or is not a legal element name for this format. 1160 * @exception IllegalArgumentException if <code>attrName</code> is 1161 * <code>null</code> or is not a legal attribute name for this 1162 * element. 1163 * 1164 * @see #setResourceBaseName 1165 */ 1166 public String getAttributeDescription(String elementName, 1167 String attrName, 1168 Locale locale) { 1169 Element element = getElement(elementName); 1170 if (attrName == null) { 1171 throw new IllegalArgumentException("attrName == null!"); 1172 } 1173 Attribute attr = element.attrMap.get(attrName); 1174 if (attr == null) { 1175 throw new IllegalArgumentException("No such attribute!"); 1176 } 1177 1178 String key = elementName + "/" + attrName; 1179 return getResource(key, locale); 1180 } 1181 1182 private ObjectValue<?> getObjectValue(String elementName) { 1183 Element element = getElement(elementName); 1184 ObjectValue<?> objv = element.objectValue; 1185 if (objv == null) { 1186 throw new IllegalArgumentException("No object within element " + 1187 elementName + "!"); 1188 } 1189 return objv; 1190 } 1191 1192 public int getObjectValueType(String elementName) { 1193 Element element = getElement(elementName); 1194 ObjectValue<?> objv = element.objectValue; 1195 if (objv == null) { 1196 return VALUE_NONE; 1197 } 1198 return objv.valueType; 1199 } 1200 1201 public Class<?> getObjectClass(String elementName) { 1202 ObjectValue<?> objv = getObjectValue(elementName); 1203 return objv.classType; 1204 } 1205 1206 public Object getObjectDefaultValue(String elementName) { 1207 ObjectValue<?> objv = getObjectValue(elementName); 1208 return objv.defaultValue; 1209 } 1210 1211 public Object[] getObjectEnumerations(String elementName) { 1212 ObjectValue<?> objv = getObjectValue(elementName); 1213 if (objv.valueType != VALUE_ENUMERATION) { 1214 throw new IllegalArgumentException("Not an enumeration!"); 1215 } 1216 List<?> vlist = objv.enumeratedValues; 1217 Object[] values = new Object[vlist.size()]; 1218 return vlist.toArray(values); 1219 } 1220 1221 public Comparable<?> getObjectMinValue(String elementName) { 1222 ObjectValue<?> objv = getObjectValue(elementName); 1223 if ((objv.valueType & VALUE_RANGE) != VALUE_RANGE) { 1224 throw new IllegalArgumentException("Not a range!"); 1225 } 1226 return objv.minValue; 1227 } 1228 1229 public Comparable<?> getObjectMaxValue(String elementName) { 1230 ObjectValue<?> objv = getObjectValue(elementName); 1231 if ((objv.valueType & VALUE_RANGE) != VALUE_RANGE) { 1232 throw new IllegalArgumentException("Not a range!"); 1233 } 1234 return objv.maxValue; 1235 } 1236 1237 public int getObjectArrayMinLength(String elementName) { 1238 ObjectValue<?> objv = getObjectValue(elementName); 1239 if (objv.valueType != VALUE_LIST) { 1240 throw new IllegalArgumentException("Not a list!"); 1241 } 1242 return objv.arrayMinLength; 1243 } 1244 1245 public int getObjectArrayMaxLength(String elementName) { 1246 ObjectValue<?> objv = getObjectValue(elementName); 1247 if (objv.valueType != VALUE_LIST) { 1248 throw new IllegalArgumentException("Not a list!"); 1249 } 1250 return objv.arrayMaxLength; 1251 } 1252 1253 // Standard format descriptor 1254 1255 private static synchronized void createStandardFormat() { 1256 if (standardFormat == null) { 1257 standardFormat = new StandardMetadataFormat(); 1258 } 1259 } 1260 1261 /** 1262 * Returns an <code>IIOMetadataFormat</code> object describing the 1263 * standard, plug-in neutral <code>javax.imageio_1.0</code> 1264 * metadata document format described in the comment of the 1265 * <code>javax.imageio.metadata</code> package. 1266 * 1267 * @return a predefined <code>IIOMetadataFormat</code> instance. 1268 */ 1269 public static IIOMetadataFormat getStandardFormatInstance() { 1270 createStandardFormat(); 1271 return standardFormat; 1272 } 1273 }