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 synchronized static 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 }