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 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 childList = new ArrayList();
 104 
 105         // Parent names (Strings)
 106         List parentList = new ArrayList();
 107 
 108         // List of attribute names in the order they were added
 109         List attrList = new ArrayList();
 110         // Attr name (String) -> Attribute
 111         Map 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 enumeratedValues;
 126 
 127         // range
 128         String minValue;
 129         String maxValue;
 130 
 131         // list
 132         int listMinLength;
 133         int listMaxLength;
 134     }
 135 
 136     class ObjectValue {
 137         int valueType = VALUE_NONE;
 138         Class classType = null;
 139         Object defaultValue = null;
 140 
 141         // Meaningful only if valueType == VALUE_ENUMERATION
 142         List enumeratedValues = null;
 143 
 144         // Meaningful only if valueType == VALUE_RANGE
 145         Comparable minValue = null;
 146         Comparable maxValue = null;
 147 
 148         // Meaningful only if valueType == VALUE_LIST
 149         int arrayMinLength = 0;
 150         int arrayMaxLength = 0;
 151     }
 152 
 153     /**
 154      * Constructs a blank <code>IIOMetadataFormatImpl</code> instance,
 155      * with a given root element name and child policy (other than
 156      * <code>CHILD_POLICY_REPEAT</code>).  Additional elements, and
 157      * their attributes and <code>Object</code> reference information
 158      * may be added using the various <code>add</code> methods.
 159      *
 160      * @param rootName the name of the root element.
 161      * @param childPolicy one of the <code>CHILD_POLICY_*</code> constants,
 162      * other than <code>CHILD_POLICY_REPEAT</code>.
 163      *
 164      * @exception IllegalArgumentException if <code>rootName</code> is
 165      * <code>null</code>.
 166      * @exception IllegalArgumentException if <code>childPolicy</code> is
 167      * not one of the predefined constants.
 168      */
 169     public IIOMetadataFormatImpl(String rootName,
 170                                  int childPolicy) {
 171         if (rootName == null) {
 172             throw new IllegalArgumentException("rootName == null!");
 173         }
 174         if (childPolicy < CHILD_POLICY_EMPTY ||
 175             childPolicy > CHILD_POLICY_MAX ||
 176             childPolicy == CHILD_POLICY_REPEAT) {
 177             throw new IllegalArgumentException("Invalid value for childPolicy!");
 178         }
 179 
 180         this.rootName = rootName;
 181 
 182         Element root = new Element();
 183         root.elementName = rootName;
 184         root.childPolicy = childPolicy;
 185 
 186         elementMap.put(rootName, root);
 187     }
 188 
 189     /**
 190      * Constructs a blank <code>IIOMetadataFormatImpl</code> instance,
 191      * with a given root element name and a child policy of
 192      * <code>CHILD_POLICY_REPEAT</code>.  Additional elements, and
 193      * their attributes and <code>Object</code> reference information
 194      * may be added using the various <code>add</code> methods.
 195      *
 196      * @param rootName the name of the root element.
 197      * @param minChildren the minimum number of children of the node.
 198      * @param maxChildren the maximum number of children of the node.
 199      *
 200      * @exception IllegalArgumentException if <code>rootName</code> is
 201      * <code>null</code>.
 202      * @exception IllegalArgumentException if <code>minChildren</code>
 203      * is negative or larger than <code>maxChildren</code>.
 204      */
 205     public IIOMetadataFormatImpl(String rootName,
 206                                  int minChildren,
 207                                  int maxChildren) {
 208         if (rootName == null) {
 209             throw new IllegalArgumentException("rootName == null!");
 210         }
 211         if (minChildren < 0) {
 212             throw new IllegalArgumentException("minChildren < 0!");
 213         }
 214         if (minChildren > maxChildren) {
 215             throw new IllegalArgumentException("minChildren > maxChildren!");
 216         }
 217 
 218         Element root = new Element();
 219         root.elementName = rootName;
 220         root.childPolicy = CHILD_POLICY_REPEAT;
 221         root.minChildren = minChildren;
 222         root.maxChildren = maxChildren;
 223 
 224         this.rootName = rootName;
 225         elementMap.put(rootName, root);
 226     }
 227 
 228     /**
 229      * Sets a new base name for locating <code>ResourceBundle</code>s
 230      * containing descriptions of elements and attributes for this
 231      * format.
 232      *
 233      * <p> Prior to the first time this method is called, the base
 234      * name will be equal to <code>this.getClass().getName() +
 235      * "Resources"</code>.
 236      *
 237      * @param resourceBaseName a <code>String</code> containing the new
 238      * base name.
 239      *
 240      * @exception IllegalArgumentException if
 241      * <code>resourceBaseName</code> is <code>null</code>.
 242      *
 243      * @see #getResourceBaseName
 244      */
 245     protected void setResourceBaseName(String resourceBaseName) {
 246         if (resourceBaseName == null) {
 247             throw new IllegalArgumentException("resourceBaseName == null!");
 248         }
 249         this.resourceBaseName = resourceBaseName;
 250     }
 251 
 252     /**
 253      * Returns the currently set base name for locating
 254      * <code>ResourceBundle</code>s.
 255      *
 256      * @return a <code>String</code> containing the base name.
 257      *
 258      * @see #setResourceBaseName
 259      */
 260     protected String getResourceBaseName() {
 261         return resourceBaseName;
 262     }
 263 
 264     /**
 265      * Utility method for locating an element.
 266      *
 267      * @param mustAppear if <code>true</code>, throw an
 268      * <code>IllegalArgumentException</code> if no such node exists;
 269      * if <code>false</code>, just return null.
 270      */
 271     private Element getElement(String elementName, boolean mustAppear) {
 272         if (mustAppear && (elementName == null)) {
 273             throw new IllegalArgumentException("element name is null!");
 274         }
 275         Element element = (Element)elementMap.get(elementName);
 276         if (mustAppear && (element == null)) {
 277             throw new IllegalArgumentException("No such element: " +
 278                                                elementName);
 279         }
 280         return element;
 281     }
 282 
 283     private Element getElement(String elementName) {
 284         return getElement(elementName, true);
 285     }
 286 
 287     // Utility method for locating an attribute
 288     private Attribute getAttribute(String elementName, String attrName) {
 289         Element element = getElement(elementName);
 290         Attribute attr = (Attribute)element.attrMap.get(attrName);
 291         if (attr == null) {
 292             throw new IllegalArgumentException("No such attribute \"" +
 293                                                attrName + "\"!");
 294         }
 295         return attr;
 296     }
 297 
 298     // Setup
 299 
 300     /**
 301      * Adds a new element type to this metadata document format with a
 302      * child policy other than <code>CHILD_POLICY_REPEAT</code>.
 303      *
 304      * @param elementName the name of the new element.
 305      * @param parentName the name of the element that will be the
 306      * parent of the new element.
 307      * @param childPolicy one of the <code>CHILD_POLICY_*</code>
 308      * constants, other than <code>CHILD_POLICY_REPEAT</code>,
 309      * indicating the child policy of the new element.
 310      *
 311      * @exception IllegalArgumentException if <code>parentName</code>
 312      * is <code>null</code>, or is not a legal element name for this
 313      * format.
 314      * @exception IllegalArgumentException if <code>childPolicy</code>
 315      * is not one of the predefined constants.
 316      */
 317     protected void addElement(String elementName,
 318                               String parentName,
 319                               int childPolicy) {
 320         Element parent = getElement(parentName);
 321         if (childPolicy < CHILD_POLICY_EMPTY ||
 322             childPolicy > CHILD_POLICY_MAX ||
 323             childPolicy == CHILD_POLICY_REPEAT) {
 324             throw new IllegalArgumentException
 325                 ("Invalid value for childPolicy!");
 326         }
 327 
 328         Element element = new Element();
 329         element.elementName = elementName;
 330         element.childPolicy = childPolicy;
 331 
 332         parent.childList.add(elementName);
 333         element.parentList.add(parentName);
 334 
 335         elementMap.put(elementName, element);
 336     }
 337 
 338     /**
 339      * Adds a new element type to this metadata document format with a
 340      * child policy of <code>CHILD_POLICY_REPEAT</code>.
 341      *
 342      * @param elementName the name of the new element.
 343      * @param parentName the name of the element that will be the
 344      * parent of the new element.
 345      * @param minChildren the minimum number of children of the node.
 346      * @param maxChildren the maximum number of children of the node.
 347      *
 348      * @exception IllegalArgumentException if <code>parentName</code>
 349      * is <code>null</code>, or is not a legal element name for this
 350      * format.
 351      * @exception IllegalArgumentException if <code>minChildren</code>
 352      * is negative or larger than <code>maxChildren</code>.
 353      */
 354     protected void addElement(String elementName,
 355                               String parentName,
 356                               int minChildren,
 357                               int maxChildren) {
 358         Element parent = getElement(parentName);
 359         if (minChildren < 0) {
 360             throw new IllegalArgumentException("minChildren < 0!");
 361         }
 362         if (minChildren > maxChildren) {
 363             throw new IllegalArgumentException("minChildren > maxChildren!");
 364         }
 365 
 366         Element element = new Element();
 367         element.elementName = elementName;
 368         element.childPolicy = CHILD_POLICY_REPEAT;
 369         element.minChildren = minChildren;
 370         element.maxChildren = maxChildren;
 371 
 372         parent.childList.add(elementName);
 373         element.parentList.add(parentName);
 374 
 375         elementMap.put(elementName, element);
 376     }
 377 
 378     /**
 379      * Adds an existing element to the list of legal children for a
 380      * given parent node type.
 381      *
 382      * @param parentName the name of the element that will be the
 383      * new parent of the element.
 384      * @param elementName the name of the element to be added as a
 385      * child.
 386      *
 387      * @exception IllegalArgumentException if <code>elementName</code>
 388      * is <code>null</code>, or is not a legal element name for this
 389      * format.
 390      * @exception IllegalArgumentException if <code>parentName</code>
 391      * is <code>null</code>, or is not a legal element name for this
 392      * format.
 393      */
 394     protected void addChildElement(String elementName, String parentName) {
 395         Element parent = getElement(parentName);
 396         Element element = getElement(elementName);
 397         parent.childList.add(elementName);
 398         element.parentList.add(parentName);
 399     }
 400 
 401     /**
 402      * Removes an element from the format.  If no element with the
 403      * given name was present, nothing happens and no exception is
 404      * thrown.
 405      *
 406      * @param elementName the name of the element to be removed.
 407      */
 408     protected void removeElement(String elementName) {
 409         Element element = getElement(elementName, false);
 410         if (element != null) {
 411             Iterator iter = element.parentList.iterator();
 412             while (iter.hasNext()) {
 413                 String parentName = (String)iter.next();
 414                 Element parent = getElement(parentName, false);
 415                 if (parent != null) {
 416                     parent.childList.remove(elementName);
 417                 }
 418             }
 419             elementMap.remove(elementName);
 420         }
 421     }
 422 
 423     /**
 424      * Adds a new attribute to a previously defined element that may
 425      * be set to an arbitrary value.
 426      *
 427      * @param elementName the name of the element.
 428      * @param attrName the name of the attribute being added.
 429      * @param dataType the data type (string format) of the attribute,
 430      * one of the <code>DATATYPE_*</code> constants.
 431      * @param required <code>true</code> if the attribute must be present.
 432      * @param defaultValue the default value for the attribute, or
 433      * <code>null</code>.
 434      *
 435      * @exception IllegalArgumentException if <code>elementName</code>
 436      * is <code>null</code>, or is not a legal element name for this
 437      * format.
 438      * @exception IllegalArgumentException if <code>attrName</code> is
 439      * <code>null</code>.
 440      * @exception IllegalArgumentException if <code>dataType</code> is
 441      * not one of the predefined constants.
 442      */
 443     protected void addAttribute(String elementName,
 444                                 String attrName,
 445                                 int dataType,
 446                                 boolean required,
 447                                 String defaultValue) {
 448         Element element = getElement(elementName);
 449         if (attrName == null) {
 450             throw new IllegalArgumentException("attrName == null!");
 451         }
 452         if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) {
 453             throw new IllegalArgumentException("Invalid value for dataType!");
 454         }
 455 
 456         Attribute attr = new Attribute();
 457         attr.attrName = attrName;
 458         attr.valueType = VALUE_ARBITRARY;
 459         attr.dataType = dataType;
 460         attr.required = required;
 461         attr.defaultValue = defaultValue;
 462 
 463         element.attrList.add(attrName);
 464         element.attrMap.put(attrName, attr);
 465     }
 466 
 467     /**
 468      * Adds a new attribute to a previously defined element that will
 469      * be defined by a set of enumerated values.
 470      *
 471      * @param elementName the name of the element.
 472      * @param attrName the name of the attribute being added.
 473      * @param dataType the data type (string format) of the attribute,
 474      * one of the <code>DATATYPE_*</code> constants.
 475      * @param required <code>true</code> if the attribute must be present.
 476      * @param defaultValue the default value for the attribute, or
 477      * <code>null</code>.
 478      * @param enumeratedValues a <code>List</code> of
 479      * <code>String</code>s containing the legal values for the
 480      * attribute.
 481      *
 482      * @exception IllegalArgumentException if <code>elementName</code>
 483      * is <code>null</code>, or is not a legal element name for this
 484      * format.
 485      * @exception IllegalArgumentException if <code>attrName</code> is
 486      * <code>null</code>.
 487      * @exception IllegalArgumentException if <code>dataType</code> is
 488      * not one of the predefined constants.
 489      * @exception IllegalArgumentException if
 490      * <code>enumeratedValues</code> is <code>null</code>.
 491      * @exception IllegalArgumentException if
 492      * <code>enumeratedValues</code> does not contain at least one
 493      * entry.
 494      * @exception IllegalArgumentException if
 495      * <code>enumeratedValues</code> contains an element that is not a
 496      * <code>String</code> or is <code>null</code>.
 497      */
 498     protected void addAttribute(String elementName,
 499                                 String attrName,
 500                                 int dataType,
 501                                 boolean required,
 502                                 String defaultValue,
 503                                 List<String> enumeratedValues) {
 504         Element element = getElement(elementName);
 505         if (attrName == null) {
 506             throw new IllegalArgumentException("attrName == null!");
 507         }
 508         if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) {
 509             throw new IllegalArgumentException("Invalid value for dataType!");
 510         }
 511         if (enumeratedValues == null) {
 512             throw new IllegalArgumentException("enumeratedValues == null!");
 513         }
 514         if (enumeratedValues.size() == 0) {
 515             throw new IllegalArgumentException("enumeratedValues is empty!");
 516         }
 517         Iterator iter = enumeratedValues.iterator();
 518         while (iter.hasNext()) {
 519             Object o = iter.next();
 520             if (o == null) {
 521                 throw new IllegalArgumentException
 522                     ("enumeratedValues contains a null!");
 523             }
 524             if (!(o instanceof String)) {
 525                 throw new IllegalArgumentException
 526                     ("enumeratedValues contains a non-String value!");
 527             }
 528         }
 529 
 530         Attribute attr = new Attribute();
 531         attr.attrName = attrName;
 532         attr.valueType = VALUE_ENUMERATION;
 533         attr.dataType = dataType;
 534         attr.required = required;
 535         attr.defaultValue = defaultValue;
 536         attr.enumeratedValues = enumeratedValues;
 537 
 538         element.attrList.add(attrName);
 539         element.attrMap.put(attrName, attr);
 540     }
 541 
 542     /**
 543      * Adds a new attribute to a previously defined element that will
 544      * be defined by a range of values.
 545      *
 546      * @param elementName the name of the element.
 547      * @param attrName the name of the attribute being added.
 548      * @param dataType the data type (string format) of the attribute,
 549      * one of the <code>DATATYPE_*</code> constants.
 550      * @param required <code>true</code> if the attribute must be present.
 551      * @param defaultValue the default value for the attribute, or
 552      * <code>null</code>.
 553      * @param minValue the smallest (inclusive or exclusive depending
 554      * on the value of <code>minInclusive</code>) legal value for the
 555      * attribute, as a <code>String</code>.
 556      * @param maxValue the largest (inclusive or exclusive depending
 557      * on the value of <code>minInclusive</code>) legal value for the
 558      * attribute, as a <code>String</code>.
 559      * @param minInclusive <code>true</code> if <code>minValue</code>
 560      * is inclusive.
 561      * @param maxInclusive <code>true</code> if <code>maxValue</code>
 562      * is inclusive.
 563      *
 564      * @exception IllegalArgumentException if <code>elementName</code>
 565      * is <code>null</code>, or is not a legal element name for this
 566      * format.
 567      * @exception IllegalArgumentException if <code>attrName</code> is
 568      * <code>null</code>.
 569      * @exception IllegalArgumentException if <code>dataType</code> is
 570      * not one of the predefined constants.
 571      */
 572     protected void addAttribute(String elementName,
 573                                 String attrName,
 574                                 int dataType,
 575                                 boolean required,
 576                                 String defaultValue,
 577                                 String minValue,
 578                                 String maxValue,
 579                                 boolean minInclusive,
 580                                 boolean maxInclusive) {
 581         Element element = getElement(elementName);
 582         if (attrName == null) {
 583             throw new IllegalArgumentException("attrName == null!");
 584         }
 585         if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) {
 586             throw new IllegalArgumentException("Invalid value for dataType!");
 587         }
 588 
 589         Attribute attr = new Attribute();
 590         attr.attrName = attrName;
 591         attr.valueType = VALUE_RANGE;
 592         if (minInclusive) {
 593             attr.valueType |= VALUE_RANGE_MIN_INCLUSIVE_MASK;
 594         }
 595         if (maxInclusive) {
 596             attr.valueType |= VALUE_RANGE_MAX_INCLUSIVE_MASK;
 597         }
 598         attr.dataType = dataType;
 599         attr.required = required;
 600         attr.defaultValue = defaultValue;
 601         attr.minValue = minValue;
 602         attr.maxValue = maxValue;
 603 
 604         element.attrList.add(attrName);
 605         element.attrMap.put(attrName, attr);
 606     }
 607 
 608     /**
 609      * Adds a new attribute to a previously defined element that will
 610      * be defined by a list of values.
 611      *
 612      * @param elementName the name of the element.
 613      * @param attrName the name of the attribute being added.
 614      * @param dataType the data type (string format) of the attribute,
 615      * one of the <code>DATATYPE_*</code> constants.
 616      * @param required <code>true</code> if the attribute must be present.
 617      * @param listMinLength the smallest legal number of list items.
 618      * @param listMaxLength the largest legal number of list items.
 619      *
 620      * @exception IllegalArgumentException if <code>elementName</code>
 621      * is <code>null</code>, or is not a legal element name for this
 622      * format.
 623      * @exception IllegalArgumentException if <code>attrName</code> is
 624      * <code>null</code>.
 625      * @exception IllegalArgumentException if <code>dataType</code> is
 626      * not one of the predefined constants.
 627      * @exception IllegalArgumentException if
 628      * <code>listMinLength</code> is negative or larger than
 629      * <code>listMaxLength</code>.
 630      */
 631     protected void addAttribute(String elementName,
 632                                 String attrName,
 633                                 int dataType,
 634                                 boolean required,
 635                                 int listMinLength,
 636                                 int listMaxLength) {
 637         Element element = getElement(elementName);
 638         if (attrName == null) {
 639             throw new IllegalArgumentException("attrName == null!");
 640         }
 641         if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) {
 642             throw new IllegalArgumentException("Invalid value for dataType!");
 643         }
 644         if (listMinLength < 0 || listMinLength > listMaxLength) {
 645             throw new IllegalArgumentException("Invalid list bounds!");
 646         }
 647 
 648         Attribute attr = new Attribute();
 649         attr.attrName = attrName;
 650         attr.valueType = VALUE_LIST;
 651         attr.dataType = dataType;
 652         attr.required = required;
 653         attr.listMinLength = listMinLength;
 654         attr.listMaxLength = listMaxLength;
 655 
 656         element.attrList.add(attrName);
 657         element.attrMap.put(attrName, attr);
 658     }
 659 
 660     /**
 661      * Adds a new attribute to a previously defined element that will
 662      * be defined by the enumerated values <code>TRUE</code> and
 663      * <code>FALSE</code>, with a datatype of
 664      * <code>DATATYPE_BOOLEAN</code>.
 665      *
 666      * @param elementName the name of the element.
 667      * @param attrName the name of the attribute being added.
 668      * @param hasDefaultValue <code>true</code> if a default value
 669      * should be present.
 670      * @param defaultValue the default value for the attribute as a
 671      * <code>boolean</code>, ignored if <code>hasDefaultValue</code>
 672      * is <code>false</code>.
 673      *
 674      * @exception IllegalArgumentException if <code>elementName</code>
 675      * is <code>null</code>, or is not a legal element name for this
 676      * format.
 677      * @exception IllegalArgumentException if <code>attrName</code> is
 678      * <code>null</code>.
 679      */
 680     protected void addBooleanAttribute(String elementName,
 681                                        String attrName,
 682                                        boolean hasDefaultValue,
 683                                        boolean defaultValue) {
 684         List values = new ArrayList();
 685         values.add("TRUE");
 686         values.add("FALSE");
 687 
 688         String dval = null;
 689         if (hasDefaultValue) {
 690             dval = defaultValue ? "TRUE" : "FALSE";
 691         }
 692         addAttribute(elementName,
 693                      attrName,
 694                      DATATYPE_BOOLEAN,
 695                      true,
 696                      dval,
 697                      values);
 698     }
 699 
 700     /**
 701      * Removes an attribute from a previously defined element.  If no
 702      * attribute with the given name was present in the given element,
 703      * nothing happens and no exception is thrown.
 704      *
 705      * @param elementName the name of the element.
 706      * @param attrName the name of the attribute being removed.
 707      *
 708      * @exception IllegalArgumentException if <code>elementName</code>
 709      * is <code>null</code>, or is not a legal element name for this format.
 710      */
 711     protected void removeAttribute(String elementName, String attrName) {
 712         Element element = getElement(elementName);
 713         element.attrList.remove(attrName);
 714         element.attrMap.remove(attrName);
 715     }
 716 
 717     /**
 718      * Allows an <code>Object</code> reference of a given class type
 719      * to be stored in nodes implementing the named element.  The
 720      * value of the <code>Object</code> is unconstrained other than by
 721      * its class type.
 722      *
 723      * <p> If an <code>Object</code> reference was previously allowed,
 724      * the previous settings are overwritten.
 725      *
 726      * @param elementName the name of the element.
 727      * @param classType a <code>Class</code> variable indicating the
 728      * legal class type for the object value.
 729      * @param required <code>true</code> if an object value must be present.
 730      * @param defaultValue the default value for the
 731      * <code>Object</code> reference, or <code>null</code>.
 732      * @param <T> the type of the object.
 733      *
 734      * @exception IllegalArgumentException if <code>elementName</code>
 735      * is <code>null</code>, or is not a legal element name for this format.
 736      */
 737     protected <T> void addObjectValue(String elementName,
 738                                       Class<T> classType,
 739                                       boolean required,
 740                                       T defaultValue)
 741     {
 742         Element element = getElement(elementName);
 743         ObjectValue obj = new ObjectValue();
 744         obj.valueType = VALUE_ARBITRARY;
 745         obj.classType = classType;
 746         obj.defaultValue = defaultValue;
 747 
 748         element.objectValue = obj;
 749     }
 750 
 751     /**
 752      * Allows an <code>Object</code> reference of a given class type
 753      * to be stored in nodes implementing the named element.  The
 754      * value of the <code>Object</code> must be one of the values
 755      * given by <code>enumeratedValues</code>.
 756      *
 757      * <p> If an <code>Object</code> reference was previously allowed,
 758      * the previous settings are overwritten.
 759      *
 760      * @param elementName the name of the element.
 761      * @param classType a <code>Class</code> variable indicating the
 762      * legal class type for the object value.
 763      * @param required <code>true</code> if an object value must be present.
 764      * @param defaultValue the default value for the
 765      * <code>Object</code> reference, or <code>null</code>.
 766      * @param enumeratedValues a <code>List</code> of
 767      * <code>Object</code>s containing the legal values for the
 768      * object reference.
 769      * @param <T> the type of the object.
 770      *
 771      * @exception IllegalArgumentException if <code>elementName</code>
 772      * is <code>null</code>, or is not a legal element name for this format.
 773      * @exception IllegalArgumentException if
 774      * <code>enumeratedValues</code> is <code>null</code>.
 775      * @exception IllegalArgumentException if
 776      * <code>enumeratedValues</code> does not contain at least one
 777      * entry.
 778      * @exception IllegalArgumentException if
 779      * <code>enumeratedValues</code> contains an element that is not
 780      * an instance of the class type denoted by <code>classType</code>
 781      * or is <code>null</code>.
 782      */
 783     protected <T> void addObjectValue(String elementName,
 784                                       Class<T> classType,
 785                                       boolean required,
 786                                       T defaultValue,
 787                                       List<? extends T> enumeratedValues)
 788     {
 789         Element element = getElement(elementName);
 790         if (enumeratedValues == null) {
 791             throw new IllegalArgumentException("enumeratedValues == null!");
 792         }
 793         if (enumeratedValues.size() == 0) {
 794             throw new IllegalArgumentException("enumeratedValues is empty!");
 795         }
 796         Iterator iter = enumeratedValues.iterator();
 797         while (iter.hasNext()) {
 798             Object o = iter.next();
 799             if (o == null) {
 800                 throw new IllegalArgumentException("enumeratedValues contains a null!");
 801             }
 802             if (!classType.isInstance(o)) {
 803                 throw new IllegalArgumentException("enumeratedValues contains a value not of class classType!");
 804             }
 805         }
 806 
 807         ObjectValue obj = new ObjectValue();
 808         obj.valueType = VALUE_ENUMERATION;
 809         obj.classType = classType;
 810         obj.defaultValue = defaultValue;
 811         obj.enumeratedValues = enumeratedValues;
 812 
 813         element.objectValue = obj;
 814     }
 815 
 816     /**
 817      * Allows an <code>Object</code> reference of a given class type
 818      * to be stored in nodes implementing the named element.  The
 819      * value of the <code>Object</code> must be within the range given
 820      * by <code>minValue</code> and <code>maxValue</code>.
 821      * Furthermore, the class type must implement the
 822      * <code>Comparable</code> interface.
 823      *
 824      * <p> If an <code>Object</code> reference was previously allowed,
 825      * the previous settings are overwritten.
 826      *
 827      * @param elementName the name of the element.
 828      * @param classType a <code>Class</code> variable indicating the
 829      * legal class type for the object value.
 830      * @param defaultValue the default value for the
 831      * @param minValue the smallest (inclusive or exclusive depending
 832      * on the value of <code>minInclusive</code>) legal value for the
 833      * object value, as a <code>String</code>.
 834      * @param maxValue the largest (inclusive or exclusive depending
 835      * on the value of <code>minInclusive</code>) legal value for the
 836      * object value, as a <code>String</code>.
 837      * @param minInclusive <code>true</code> if <code>minValue</code>
 838      * is inclusive.
 839      * @param maxInclusive <code>true</code> if <code>maxValue</code>
 840      * is inclusive.
 841      * @param <T> the type of the object.
 842      *
 843      * @exception IllegalArgumentException if <code>elementName</code>
 844      * is <code>null</code>, or is not a legal element name for this
 845      * format.
 846      */
 847     protected <T extends Object & Comparable<? super T>> void
 848         addObjectValue(String elementName,
 849                        Class<T> classType,
 850                        T defaultValue,
 851                        Comparable<? super T> minValue,
 852                        Comparable<? super T> maxValue,
 853                        boolean minInclusive,
 854                        boolean maxInclusive)
 855     {
 856         Element element = getElement(elementName);
 857         ObjectValue obj = new ObjectValue();
 858         obj.valueType = VALUE_RANGE;
 859         if (minInclusive) {
 860             obj.valueType |= VALUE_RANGE_MIN_INCLUSIVE_MASK;
 861         }
 862         if (maxInclusive) {
 863             obj.valueType |= VALUE_RANGE_MAX_INCLUSIVE_MASK;
 864         }
 865         obj.classType = classType;
 866         obj.defaultValue = defaultValue;
 867         obj.minValue = minValue;
 868         obj.maxValue = maxValue;
 869 
 870         element.objectValue = obj;
 871     }
 872 
 873     /**
 874      * Allows an <code>Object</code> reference of a given class type
 875      * to be stored in nodes implementing the named element.  The
 876      * value of the <code>Object</code> must an array of objects of
 877      * class type given by <code>classType</code>, with at least
 878      * <code>arrayMinLength</code> and at most
 879      * <code>arrayMaxLength</code> elements.
 880      *
 881      * <p> If an <code>Object</code> reference was previously allowed,
 882      * the previous settings are overwritten.
 883      *
 884      * @param elementName the name of the element.
 885      * @param classType a <code>Class</code> variable indicating the
 886      * legal class type for the object value.
 887      * @param arrayMinLength the smallest legal length for the array.
 888      * @param arrayMaxLength the largest legal length for the array.
 889      *
 890      * @exception IllegalArgumentException if <code>elementName</code> is
 891      * not a legal element name for this format.
 892      */
 893     protected void addObjectValue(String elementName,
 894                                   Class<?> classType,
 895                                   int arrayMinLength,
 896                                   int arrayMaxLength) {
 897         Element element = getElement(elementName);
 898         ObjectValue obj = new ObjectValue();
 899         obj.valueType = VALUE_LIST;
 900         obj.classType = classType;
 901         obj.arrayMinLength = arrayMinLength;
 902         obj.arrayMaxLength = arrayMaxLength;
 903 
 904         element.objectValue = obj;
 905     }
 906 
 907     /**
 908      * Disallows an <code>Object</code> reference from being stored in
 909      * nodes implementing the named element.
 910      *
 911      * @param elementName the name of the element.
 912      *
 913      * @exception IllegalArgumentException if <code>elementName</code> is
 914      * not a legal element name for this format.
 915      */
 916     protected void removeObjectValue(String elementName) {
 917         Element element = getElement(elementName);
 918         element.objectValue = null;
 919     }
 920 
 921     // Utility method
 922 
 923     // Methods from IIOMetadataFormat
 924 
 925     // Root
 926 
 927     public String getRootName() {
 928         return rootName;
 929     }
 930 
 931     // Multiplicity
 932 
 933     public abstract boolean canNodeAppear(String elementName,
 934                                           ImageTypeSpecifier imageType);
 935 
 936     public int getElementMinChildren(String elementName) {
 937         Element element = getElement(elementName);
 938         if (element.childPolicy != CHILD_POLICY_REPEAT) {
 939             throw new IllegalArgumentException("Child policy not CHILD_POLICY_REPEAT!");
 940         }
 941         return element.minChildren;
 942     }
 943 
 944     public int getElementMaxChildren(String elementName) {
 945         Element element = getElement(elementName);
 946         if (element.childPolicy != CHILD_POLICY_REPEAT) {
 947             throw new IllegalArgumentException("Child policy not CHILD_POLICY_REPEAT!");
 948         }
 949         return element.maxChildren;
 950     }
 951 
 952     private String getResource(String key, Locale locale) {
 953         if (locale == null) {
 954             locale = Locale.getDefault();
 955         }
 956 
 957         /**
 958          * If an applet supplies an implementation of IIOMetadataFormat and
 959          * resource bundles, then the resource bundle will need to be
 960          * accessed via the applet class loader. So first try the context
 961          * class loader to locate the resource bundle.
 962          * If that throws MissingResourceException, then try the
 963          * system class loader.
 964          */
 965         ClassLoader loader = (ClassLoader)
 966             java.security.AccessController.doPrivileged(
 967                 new java.security.PrivilegedAction() {
 968                    public Object run() {
 969                        return Thread.currentThread().getContextClassLoader();
 970                    }
 971             });
 972 
 973         ResourceBundle bundle = null;
 974         try {
 975             bundle = ResourceBundle.getBundle(resourceBaseName,
 976                                               locale, loader);
 977         } catch (MissingResourceException mre) {
 978             try {
 979                 bundle = ResourceBundle.getBundle(resourceBaseName, locale);
 980             } catch (MissingResourceException mre1) {
 981                 return null;
 982             }
 983         }
 984 
 985         try {
 986             return bundle.getString(key);
 987         } catch (MissingResourceException e) {
 988             return null;
 989         }
 990     }
 991 
 992     /**
 993      * Returns a <code>String</code> containing a description of the
 994      * named element, or <code>null</code>.  The description will be
 995      * localized for the supplied <code>Locale</code> if possible.
 996      *
 997      * <p> The default implementation will first locate a
 998      * <code>ResourceBundle</code> using the current resource base
 999      * name set by <code>setResourceBaseName</code> and the supplied
1000      * <code>Locale</code>, using the fallback mechanism described in
1001      * the comments for <code>ResourceBundle.getBundle</code>.  If a
1002      * <code>ResourceBundle</code> is found, the element name will be
1003      * used as a key to its <code>getString</code> method, and the
1004      * result returned.  If no <code>ResourceBundle</code> is found,
1005      * or no such key is present, <code>null</code> will be returned.
1006      *
1007      * <p> If <code>locale</code> is <code>null</code>, the current
1008      * default <code>Locale</code> returned by <code>Locale.getLocale</code>
1009      * will be used.
1010      *
1011      * @param elementName the name of the element.
1012      * @param locale the <code>Locale</code> for which localization
1013      * will be attempted.
1014      *
1015      * @return the element description.
1016      *
1017      * @exception IllegalArgumentException if <code>elementName</code>
1018      * is <code>null</code>, or is not a legal element name for this format.
1019      *
1020      * @see #setResourceBaseName
1021      */
1022     public String getElementDescription(String elementName,
1023                                         Locale locale) {
1024         Element element = getElement(elementName);
1025         return getResource(elementName, locale);
1026     }
1027 
1028     // Children
1029 
1030     public int getChildPolicy(String elementName) {
1031         Element element = getElement(elementName);
1032         return element.childPolicy;
1033     }
1034 
1035     public String[] getChildNames(String elementName) {
1036         Element element = getElement(elementName);
1037         if (element.childPolicy == CHILD_POLICY_EMPTY) {
1038             return null;
1039         }
1040         return (String[])element.childList.toArray(new String[0]);
1041     }
1042 
1043     // Attributes
1044 
1045     public String[] getAttributeNames(String elementName) {
1046         Element element = getElement(elementName);
1047         List names = element.attrList;
1048 
1049         String[] result = new String[names.size()];
1050         return (String[])names.toArray(result);
1051     }
1052 
1053     public int getAttributeValueType(String elementName, String attrName) {
1054         Attribute attr = getAttribute(elementName, attrName);
1055         return attr.valueType;
1056     }
1057 
1058     public int getAttributeDataType(String elementName, String attrName) {
1059         Attribute attr = getAttribute(elementName, attrName);
1060         return attr.dataType;
1061     }
1062 
1063     public boolean isAttributeRequired(String elementName, String attrName) {
1064         Attribute attr = getAttribute(elementName, attrName);
1065         return attr.required;
1066     }
1067 
1068     public String getAttributeDefaultValue(String elementName,
1069                                            String attrName) {
1070         Attribute attr = getAttribute(elementName, attrName);
1071         return attr.defaultValue;
1072     }
1073 
1074     public String[] getAttributeEnumerations(String elementName,
1075                                              String attrName) {
1076         Attribute attr = getAttribute(elementName, attrName);
1077         if (attr.valueType != VALUE_ENUMERATION) {
1078             throw new IllegalArgumentException
1079                 ("Attribute not an enumeration!");
1080         }
1081 
1082         List values = attr.enumeratedValues;
1083         Iterator iter = values.iterator();
1084         String[] result = new String[values.size()];
1085         return (String[])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 = (Attribute)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 }