1 /*
2  * Copyright (c) 2000, 2013, 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      */
IIOMetadataFormatImpl(String rootName, int childPolicy)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      */
IIOMetadataFormatImpl(String rootName, int minChildren, int maxChildren)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      */
setResourceBaseName(String resourceBaseName)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      */
getResourceBaseName()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      */
getElement(String elementName, boolean mustAppear)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 
getElement(String elementName)283     private Element getElement(String elementName) {
284         return getElement(elementName, true);
285     }
286 
287     // Utility method for locating an attribute
getAttribute(String elementName, String attrName)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      */
addElement(String elementName, String parentName, int childPolicy)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      */
addElement(String elementName, String parentName, int minChildren, int maxChildren)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      */
addChildElement(String elementName, String parentName)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      */
removeElement(String elementName)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      */
addAttribute(String elementName, String attrName, int dataType, boolean required, String defaultValue)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      */
addAttribute(String elementName, String attrName, int dataType, boolean required, String defaultValue, List<String> enumeratedValues)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      */
addAttribute(String elementName, String attrName, int dataType, boolean required, String defaultValue, String minValue, String maxValue, boolean minInclusive, boolean maxInclusive)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      */
addAttribute(String elementName, String attrName, int dataType, boolean required, int listMinLength, int listMaxLength)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      */
addBooleanAttribute(String elementName, String attrName, boolean hasDefaultValue, boolean defaultValue)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      */
removeAttribute(String elementName, String attrName)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      */
addObjectValue(String elementName, Class<T> classType, boolean required, T defaultValue)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      */
addObjectValue(String elementName, Class<T> classType, boolean required, T defaultValue, List<? extends T> enumeratedValues)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
addObjectValue(String elementName, Class<T> classType, T defaultValue, Comparable<? super T> minValue, Comparable<? super T> maxValue, boolean minInclusive, boolean maxInclusive)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      */
addObjectValue(String elementName, Class<?> classType, int arrayMinLength, int arrayMaxLength)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      */
removeObjectValue(String elementName)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 
getRootName()927     public String getRootName() {
928         return rootName;
929     }
930 
931     // Multiplicity
932 
canNodeAppear(String elementName, ImageTypeSpecifier imageType)933     public abstract boolean canNodeAppear(String elementName,
934                                           ImageTypeSpecifier imageType);
935 
getElementMinChildren(String elementName)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 
getElementMaxChildren(String elementName)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 
getResource(String key, Locale locale)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      */
getElementDescription(String elementName, Locale locale)1022     public String getElementDescription(String elementName,
1023                                         Locale locale) {
1024         Element element = getElement(elementName);
1025         return getResource(elementName, locale);
1026     }
1027 
1028     // Children
1029 
getChildPolicy(String elementName)1030     public int getChildPolicy(String elementName) {
1031         Element element = getElement(elementName);
1032         return element.childPolicy;
1033     }
1034 
getChildNames(String elementName)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 
getAttributeNames(String elementName)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 
getAttributeValueType(String elementName, String attrName)1053     public int getAttributeValueType(String elementName, String attrName) {
1054         Attribute attr = getAttribute(elementName, attrName);
1055         return attr.valueType;
1056     }
1057 
getAttributeDataType(String elementName, String attrName)1058     public int getAttributeDataType(String elementName, String attrName) {
1059         Attribute attr = getAttribute(elementName, attrName);
1060         return attr.dataType;
1061     }
1062 
isAttributeRequired(String elementName, String attrName)1063     public boolean isAttributeRequired(String elementName, String attrName) {
1064         Attribute attr = getAttribute(elementName, attrName);
1065         return attr.required;
1066     }
1067 
getAttributeDefaultValue(String elementName, String attrName)1068     public String getAttributeDefaultValue(String elementName,
1069                                            String attrName) {
1070         Attribute attr = getAttribute(elementName, attrName);
1071         return attr.defaultValue;
1072     }
1073 
getAttributeEnumerations(String elementName, String attrName)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 
getAttributeMinValue(String elementName, String attrName)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 
getAttributeMaxValue(String elementName, String attrName)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 
getAttributeListMinLength(String elementName, String attrName)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 
getAttributeListMaxLength(String elementName, String attrName)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      */
getAttributeDescription(String elementName, String attrName, Locale locale)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 
getObjectValue(String elementName)1182     private ObjectValue getObjectValue(String elementName) {
1183         Element element = getElement(elementName);
1184         ObjectValue objv = (ObjectValue)element.objectValue;
1185         if (objv == null) {
1186             throw new IllegalArgumentException("No object within element " +
1187                                                elementName + "!");
1188         }
1189         return objv;
1190     }
1191 
getObjectValueType(String elementName)1192     public int getObjectValueType(String elementName) {
1193         Element element = getElement(elementName);
1194         ObjectValue objv = (ObjectValue)element.objectValue;
1195         if (objv == null) {
1196             return VALUE_NONE;
1197         }
1198         return objv.valueType;
1199     }
1200 
getObjectClass(String elementName)1201     public Class<?> getObjectClass(String elementName) {
1202         ObjectValue objv = getObjectValue(elementName);
1203         return objv.classType;
1204     }
1205 
getObjectDefaultValue(String elementName)1206     public Object getObjectDefaultValue(String elementName) {
1207         ObjectValue objv = getObjectValue(elementName);
1208         return objv.defaultValue;
1209     }
1210 
getObjectEnumerations(String elementName)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 
getObjectMinValue(String elementName)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 
getObjectMaxValue(String elementName)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 
getObjectArrayMinLength(String elementName)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 
getObjectArrayMaxLength(String elementName)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 
createStandardFormat()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      */
getStandardFormatInstance()1269     public static IIOMetadataFormat getStandardFormatInstance() {
1270         createStandardFormat();
1271         return standardFormat;
1272     }
1273 }
1274