1 /*
2  * Copyright (c) 2000, 2014, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package javax.imageio.metadata;
27 
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.HashMap;
31 import java.util.Iterator;
32 import java.util.List;
33 import java.util.Locale;
34 import java.util.Map;
35 import java.util.MissingResourceException;
36 import java.util.ResourceBundle;
37 import javax.imageio.ImageTypeSpecifier;
38 import com.sun.imageio.plugins.common.StandardMetadataFormat;
39 
40 /**
41  * A concrete class providing a reusable implementation of the
42  * {@code IIOMetadataFormat} interface.  In addition, a static
43  * instance representing the standard, plug-in neutral
44  * {@code javax_imageio_1.0} format is provided by the
45  * {@code getStandardFormatInstance} method.
46  *
47  * <p> In order to supply localized descriptions of elements and
48  * attributes, a {@code ResourceBundle} with a base name of
49  * {@code this.getClass().getName() + "Resources"} should be
50  * supplied via the usual mechanism used by
51  * {@code ResourceBundle.getBundle}.  Briefly, the subclasser
52  * supplies one or more additional classes according to a naming
53  * convention (by default, the fully-qualified name of the subclass
54  * extending {@code IIMetadataFormatImpl}, plus the string
55  * "Resources", plus the country, language, and variant codes
56  * separated by underscores).  At run time, calls to
57  * {@code getElementDescription} or
58  * {@code getAttributeDescription} will attempt to load such
59  * classes dynamically according to the supplied locale, and will use
60  * either the element name, or the element name followed by a '/'
61  * character followed by the attribute name as a key.  This key will
62  * be supplied to the {@code ResourceBundle}'s
63  * {@code getString} method, and the resulting localized
64  * description of the node or attribute is returned.
65  *
66  * <p> The subclass may supply a different base name for the resource
67  * bundles using the {@code setResourceBaseName} method.
68  *
69  * <p> A subclass may choose its own localization mechanism, if so
70  * desired, by overriding the supplied implementations of
71  * {@code getElementDescription} and
72  * {@code getAttributeDescription}.
73  *
74  * @see ResourceBundle#getBundle(String,Locale)
75  *
76  */
77 public abstract class IIOMetadataFormatImpl implements IIOMetadataFormat {
78 
79     /**
80      * A {@code String} constant containing the standard format
81      * name, {@code "javax_imageio_1.0"}.
82      */
83     public static final String standardMetadataFormatName =
84         "javax_imageio_1.0";
85 
86     private static IIOMetadataFormat standardFormat = null;
87 
88     private String resourceBaseName = this.getClass().getName() + "Resources";
89 
90     private String rootName;
91 
92     // Element name (String) -> Element
93     private HashMap<String, Element> elementMap = new HashMap<>();
94 
95     class Element {
96         String elementName;
97 
98         int childPolicy;
99         int minChildren = 0;
100         int maxChildren = 0;
101 
102         // Child names (Strings)
103         List<String> childList = new ArrayList<>();
104 
105         // Parent names (Strings)
106         List<String> parentList = new ArrayList<>();
107 
108         // List of attribute names in the order they were added
109         List<String> attrList = new ArrayList<>();
110         // Attr name (String) -> Attribute
111         Map<String, Attribute> attrMap = new HashMap<>();
112 
113         ObjectValue<?> objectValue;
114     }
115 
116     class Attribute {
117         String attrName;
118 
119         int valueType = VALUE_ARBITRARY;
120         int dataType;
121         boolean required;
122         String defaultValue = null;
123 
124         // enumeration
125         List<String> enumeratedValues;
126 
127         // range
128         String minValue;
129         String maxValue;
130 
131         // list
132         int listMinLength;
133         int listMaxLength;
134     }
135 
136     class ObjectValue<T> {
137         int valueType = VALUE_NONE;
138         // ? extends T So that ObjectValue<Object> can take Class<?>
139         Class<? extends T> classType = null;
140         T defaultValue = null;
141 
142         // Meaningful only if valueType == VALUE_ENUMERATION
143         List<? extends T> enumeratedValues = null;
144 
145         // Meaningful only if valueType == VALUE_RANGE
146         Comparable<? super T> minValue = null;
147         Comparable<? super T> maxValue = null;
148 
149         // Meaningful only if valueType == VALUE_LIST
150         int arrayMinLength = 0;
151         int arrayMaxLength = 0;
152     }
153 
154     /**
155      * Constructs a blank {@code IIOMetadataFormatImpl} instance,
156      * with a given root element name and child policy (other than
157      * {@code CHILD_POLICY_REPEAT}).  Additional elements, and
158      * their attributes and {@code Object} reference information
159      * may be added using the various {@code add} methods.
160      *
161      * @param rootName the name of the root element.
162      * @param childPolicy one of the {@code CHILD_POLICY_*} constants,
163      * other than {@code CHILD_POLICY_REPEAT}.
164      *
165      * @exception IllegalArgumentException if {@code rootName} is
166      * {@code null}.
167      * @exception IllegalArgumentException if {@code childPolicy} is
168      * not one of the predefined constants.
169      */
IIOMetadataFormatImpl(String rootName, int childPolicy)170     public IIOMetadataFormatImpl(String rootName,
171                                  int childPolicy) {
172         if (rootName == null) {
173             throw new IllegalArgumentException("rootName == null!");
174         }
175         if (childPolicy < CHILD_POLICY_EMPTY ||
176             childPolicy > CHILD_POLICY_MAX ||
177             childPolicy == CHILD_POLICY_REPEAT) {
178             throw new IllegalArgumentException("Invalid value for childPolicy!");
179         }
180 
181         this.rootName = rootName;
182 
183         Element root = new Element();
184         root.elementName = rootName;
185         root.childPolicy = childPolicy;
186 
187         elementMap.put(rootName, root);
188     }
189 
190     /**
191      * Constructs a blank {@code IIOMetadataFormatImpl} instance,
192      * with a given root element name and a child policy of
193      * {@code CHILD_POLICY_REPEAT}.  Additional elements, and
194      * their attributes and {@code Object} reference information
195      * may be added using the various {@code add} methods.
196      *
197      * @param rootName the name of the root element.
198      * @param minChildren the minimum number of children of the node.
199      * @param maxChildren the maximum number of children of the node.
200      *
201      * @exception IllegalArgumentException if {@code rootName} is
202      * {@code null}.
203      * @exception IllegalArgumentException if {@code minChildren}
204      * is negative or larger than {@code maxChildren}.
205      */
IIOMetadataFormatImpl(String rootName, int minChildren, int maxChildren)206     public IIOMetadataFormatImpl(String rootName,
207                                  int minChildren,
208                                  int maxChildren) {
209         if (rootName == null) {
210             throw new IllegalArgumentException("rootName == null!");
211         }
212         if (minChildren < 0) {
213             throw new IllegalArgumentException("minChildren < 0!");
214         }
215         if (minChildren > maxChildren) {
216             throw new IllegalArgumentException("minChildren > maxChildren!");
217         }
218 
219         Element root = new Element();
220         root.elementName = rootName;
221         root.childPolicy = CHILD_POLICY_REPEAT;
222         root.minChildren = minChildren;
223         root.maxChildren = maxChildren;
224 
225         this.rootName = rootName;
226         elementMap.put(rootName, root);
227     }
228 
229     /**
230      * Sets a new base name for locating {@code ResourceBundle}s
231      * containing descriptions of elements and attributes for this
232      * format.
233      *
234      * <p> Prior to the first time this method is called, the base
235      * name will be equal to
236      * {@code this.getClass().getName() + "Resources"}.
237      *
238      * @param resourceBaseName a {@code String} containing the new
239      * base name.
240      *
241      * @exception IllegalArgumentException if
242      * {@code resourceBaseName} is {@code null}.
243      *
244      * @see #getResourceBaseName
245      */
setResourceBaseName(String resourceBaseName)246     protected void setResourceBaseName(String resourceBaseName) {
247         if (resourceBaseName == null) {
248             throw new IllegalArgumentException("resourceBaseName == null!");
249         }
250         this.resourceBaseName = resourceBaseName;
251     }
252 
253     /**
254      * Returns the currently set base name for locating
255      * {@code ResourceBundle}s.
256      *
257      * @return a {@code String} containing the base name.
258      *
259      * @see #setResourceBaseName
260      */
getResourceBaseName()261     protected String getResourceBaseName() {
262         return resourceBaseName;
263     }
264 
265     /**
266      * Utility method for locating an element.
267      *
268      * @param mustAppear if {@code true}, throw an
269      * {@code IllegalArgumentException} if no such node exists;
270      * if {@code false}, just return null.
271      */
getElement(String elementName, boolean mustAppear)272     private Element getElement(String elementName, boolean mustAppear) {
273         if (mustAppear && (elementName == null)) {
274             throw new IllegalArgumentException("element name is null!");
275         }
276         Element element = elementMap.get(elementName);
277         if (mustAppear && (element == null)) {
278             throw new IllegalArgumentException("No such element: " +
279                                                elementName);
280         }
281         return element;
282     }
283 
getElement(String elementName)284     private Element getElement(String elementName) {
285         return getElement(elementName, true);
286     }
287 
288     // Utility method for locating an attribute
getAttribute(String elementName, String attrName)289     private Attribute getAttribute(String elementName, String attrName) {
290         Element element = getElement(elementName);
291         Attribute attr = element.attrMap.get(attrName);
292         if (attr == null) {
293             throw new IllegalArgumentException("No such attribute \"" +
294                                                attrName + "\"!");
295         }
296         return attr;
297     }
298 
299     // Setup
300 
301     /**
302      * Adds a new element type to this metadata document format with a
303      * child policy other than {@code CHILD_POLICY_REPEAT}.
304      *
305      * @param elementName the name of the new element.
306      * @param parentName the name of the element that will be the
307      * parent of the new element.
308      * @param childPolicy one of the {@code CHILD_POLICY_*}
309      * constants, other than {@code CHILD_POLICY_REPEAT},
310      * indicating the child policy of the new element.
311      *
312      * @exception IllegalArgumentException if {@code parentName}
313      * is {@code null}, or is not a legal element name for this
314      * format.
315      * @exception IllegalArgumentException if {@code childPolicy}
316      * is not one of the predefined constants.
317      */
addElement(String elementName, String parentName, int childPolicy)318     protected void addElement(String elementName,
319                               String parentName,
320                               int childPolicy) {
321         Element parent = getElement(parentName);
322         if (childPolicy < CHILD_POLICY_EMPTY ||
323             childPolicy > CHILD_POLICY_MAX ||
324             childPolicy == CHILD_POLICY_REPEAT) {
325             throw new IllegalArgumentException
326                 ("Invalid value for childPolicy!");
327         }
328 
329         Element element = new Element();
330         element.elementName = elementName;
331         element.childPolicy = childPolicy;
332 
333         parent.childList.add(elementName);
334         element.parentList.add(parentName);
335 
336         elementMap.put(elementName, element);
337     }
338 
339     /**
340      * Adds a new element type to this metadata document format with a
341      * child policy of {@code CHILD_POLICY_REPEAT}.
342      *
343      * @param elementName the name of the new element.
344      * @param parentName the name of the element that will be the
345      * parent of the new element.
346      * @param minChildren the minimum number of children of the node.
347      * @param maxChildren the maximum number of children of the node.
348      *
349      * @exception IllegalArgumentException if {@code parentName}
350      * is {@code null}, or is not a legal element name for this
351      * format.
352      * @exception IllegalArgumentException if {@code minChildren}
353      * is negative or larger than {@code maxChildren}.
354      */
addElement(String elementName, String parentName, int minChildren, int maxChildren)355     protected void addElement(String elementName,
356                               String parentName,
357                               int minChildren,
358                               int maxChildren) {
359         Element parent = getElement(parentName);
360         if (minChildren < 0) {
361             throw new IllegalArgumentException("minChildren < 0!");
362         }
363         if (minChildren > maxChildren) {
364             throw new IllegalArgumentException("minChildren > maxChildren!");
365         }
366 
367         Element element = new Element();
368         element.elementName = elementName;
369         element.childPolicy = CHILD_POLICY_REPEAT;
370         element.minChildren = minChildren;
371         element.maxChildren = maxChildren;
372 
373         parent.childList.add(elementName);
374         element.parentList.add(parentName);
375 
376         elementMap.put(elementName, element);
377     }
378 
379     /**
380      * Adds an existing element to the list of legal children for a
381      * given parent node type.
382      *
383      * @param parentName the name of the element that will be the
384      * new parent of the element.
385      * @param elementName the name of the element to be added as a
386      * child.
387      *
388      * @exception IllegalArgumentException if {@code elementName}
389      * is {@code null}, or is not a legal element name for this
390      * format.
391      * @exception IllegalArgumentException if {@code parentName}
392      * is {@code null}, or is not a legal element name for this
393      * format.
394      */
addChildElement(String elementName, String parentName)395     protected void addChildElement(String elementName, String parentName) {
396         Element parent = getElement(parentName);
397         Element element = getElement(elementName);
398         parent.childList.add(elementName);
399         element.parentList.add(parentName);
400     }
401 
402     /**
403      * Removes an element from the format.  If no element with the
404      * given name was present, nothing happens and no exception is
405      * thrown.
406      *
407      * @param elementName the name of the element to be removed.
408      */
removeElement(String elementName)409     protected void removeElement(String elementName) {
410         Element element = getElement(elementName, false);
411         if (element != null) {
412             Iterator<String> iter = element.parentList.iterator();
413             while (iter.hasNext()) {
414                 String parentName = iter.next();
415                 Element parent = getElement(parentName, false);
416                 if (parent != null) {
417                     parent.childList.remove(elementName);
418                 }
419             }
420             elementMap.remove(elementName);
421         }
422     }
423 
424     /**
425      * Adds a new attribute to a previously defined element that may
426      * be set to an arbitrary value.
427      *
428      * @param elementName the name of the element.
429      * @param attrName the name of the attribute being added.
430      * @param dataType the data type (string format) of the attribute,
431      * one of the {@code DATATYPE_*} constants.
432      * @param required {@code true} if the attribute must be present.
433      * @param defaultValue the default value for the attribute, or
434      * {@code null}.
435      *
436      * @exception IllegalArgumentException if {@code elementName}
437      * is {@code null}, or is not a legal element name for this
438      * format.
439      * @exception IllegalArgumentException if {@code attrName} is
440      * {@code null}.
441      * @exception IllegalArgumentException if {@code dataType} is
442      * not one of the predefined constants.
443      */
addAttribute(String elementName, String attrName, int dataType, boolean required, String defaultValue)444     protected void addAttribute(String elementName,
445                                 String attrName,
446                                 int dataType,
447                                 boolean required,
448                                 String defaultValue) {
449         Element element = getElement(elementName);
450         if (attrName == null) {
451             throw new IllegalArgumentException("attrName == null!");
452         }
453         if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) {
454             throw new IllegalArgumentException("Invalid value for dataType!");
455         }
456 
457         Attribute attr = new Attribute();
458         attr.attrName = attrName;
459         attr.valueType = VALUE_ARBITRARY;
460         attr.dataType = dataType;
461         attr.required = required;
462         attr.defaultValue = defaultValue;
463 
464         element.attrList.add(attrName);
465         element.attrMap.put(attrName, attr);
466     }
467 
468     /**
469      * Adds a new attribute to a previously defined element that will
470      * be defined by a set of enumerated values.
471      *
472      * @param elementName the name of the element.
473      * @param attrName the name of the attribute being added.
474      * @param dataType the data type (string format) of the attribute,
475      * one of the {@code DATATYPE_*} constants.
476      * @param required {@code true} if the attribute must be present.
477      * @param defaultValue the default value for the attribute, or
478      * {@code null}.
479      * @param enumeratedValues a {@code List} of
480      * {@code String}s containing the legal values for the
481      * attribute.
482      *
483      * @exception IllegalArgumentException if {@code elementName}
484      * is {@code null}, or is not a legal element name for this
485      * format.
486      * @exception IllegalArgumentException if {@code attrName} is
487      * {@code null}.
488      * @exception IllegalArgumentException if {@code dataType} is
489      * not one of the predefined constants.
490      * @exception IllegalArgumentException if
491      * {@code enumeratedValues} is {@code null}.
492      * @exception IllegalArgumentException if
493      * {@code enumeratedValues} does not contain at least one
494      * entry.
495      * @exception IllegalArgumentException if
496      * {@code enumeratedValues} contains an element that is not a
497      * {@code String} or is {@code null}.
498      */
addAttribute(String elementName, String attrName, int dataType, boolean required, String defaultValue, List<String> enumeratedValues)499     protected void addAttribute(String elementName,
500                                 String attrName,
501                                 int dataType,
502                                 boolean required,
503                                 String defaultValue,
504                                 List<String> enumeratedValues) {
505         Element element = getElement(elementName);
506         if (attrName == null) {
507             throw new IllegalArgumentException("attrName == null!");
508         }
509         if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) {
510             throw new IllegalArgumentException("Invalid value for dataType!");
511         }
512         if (enumeratedValues == null) {
513             throw new IllegalArgumentException("enumeratedValues == null!");
514         }
515         if (enumeratedValues.size() == 0) {
516             throw new IllegalArgumentException("enumeratedValues is empty!");
517         }
518         Iterator<String> iter = enumeratedValues.iterator();
519         while (iter.hasNext()) {
520             Object o = iter.next();
521             if (o == null) {
522                 throw new IllegalArgumentException
523                     ("enumeratedValues contains a null!");
524             }
525             if (!(o instanceof String)) {
526                 throw new IllegalArgumentException
527                     ("enumeratedValues contains a non-String value!");
528             }
529         }
530 
531         Attribute attr = new Attribute();
532         attr.attrName = attrName;
533         attr.valueType = VALUE_ENUMERATION;
534         attr.dataType = dataType;
535         attr.required = required;
536         attr.defaultValue = defaultValue;
537         attr.enumeratedValues = enumeratedValues;
538 
539         element.attrList.add(attrName);
540         element.attrMap.put(attrName, attr);
541     }
542 
543     /**
544      * Adds a new attribute to a previously defined element that will
545      * be defined by a range of values.
546      *
547      * @param elementName the name of the element.
548      * @param attrName the name of the attribute being added.
549      * @param dataType the data type (string format) of the attribute,
550      * one of the {@code DATATYPE_*} constants.
551      * @param required {@code true} if the attribute must be present.
552      * @param defaultValue the default value for the attribute, or
553      * {@code null}.
554      * @param minValue the smallest (inclusive or exclusive depending
555      * on the value of {@code minInclusive}) legal value for the
556      * attribute, as a {@code String}.
557      * @param maxValue the largest (inclusive or exclusive depending
558      * on the value of {@code minInclusive}) legal value for the
559      * attribute, as a {@code String}.
560      * @param minInclusive {@code true} if {@code minValue}
561      * is inclusive.
562      * @param maxInclusive {@code true} if {@code maxValue}
563      * is inclusive.
564      *
565      * @exception IllegalArgumentException if {@code elementName}
566      * is {@code null}, or is not a legal element name for this
567      * format.
568      * @exception IllegalArgumentException if {@code attrName} is
569      * {@code null}.
570      * @exception IllegalArgumentException if {@code dataType} is
571      * not one of the predefined constants.
572      */
addAttribute(String elementName, String attrName, int dataType, boolean required, String defaultValue, String minValue, String maxValue, boolean minInclusive, boolean maxInclusive)573     protected void addAttribute(String elementName,
574                                 String attrName,
575                                 int dataType,
576                                 boolean required,
577                                 String defaultValue,
578                                 String minValue,
579                                 String maxValue,
580                                 boolean minInclusive,
581                                 boolean maxInclusive) {
582         Element element = getElement(elementName);
583         if (attrName == null) {
584             throw new IllegalArgumentException("attrName == null!");
585         }
586         if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) {
587             throw new IllegalArgumentException("Invalid value for dataType!");
588         }
589 
590         Attribute attr = new Attribute();
591         attr.attrName = attrName;
592         attr.valueType = VALUE_RANGE;
593         if (minInclusive) {
594             attr.valueType |= VALUE_RANGE_MIN_INCLUSIVE_MASK;
595         }
596         if (maxInclusive) {
597             attr.valueType |= VALUE_RANGE_MAX_INCLUSIVE_MASK;
598         }
599         attr.dataType = dataType;
600         attr.required = required;
601         attr.defaultValue = defaultValue;
602         attr.minValue = minValue;
603         attr.maxValue = maxValue;
604 
605         element.attrList.add(attrName);
606         element.attrMap.put(attrName, attr);
607     }
608 
609     /**
610      * Adds a new attribute to a previously defined element that will
611      * be defined by a list of values.
612      *
613      * @param elementName the name of the element.
614      * @param attrName the name of the attribute being added.
615      * @param dataType the data type (string format) of the attribute,
616      * one of the {@code DATATYPE_*} constants.
617      * @param required {@code true} if the attribute must be present.
618      * @param listMinLength the smallest legal number of list items.
619      * @param listMaxLength the largest legal number of list items.
620      *
621      * @exception IllegalArgumentException if {@code elementName}
622      * is {@code null}, or is not a legal element name for this
623      * format.
624      * @exception IllegalArgumentException if {@code attrName} is
625      * {@code null}.
626      * @exception IllegalArgumentException if {@code dataType} is
627      * not one of the predefined constants.
628      * @exception IllegalArgumentException if
629      * {@code listMinLength} is negative or larger than
630      * {@code listMaxLength}.
631      */
addAttribute(String elementName, String attrName, int dataType, boolean required, int listMinLength, int listMaxLength)632     protected void addAttribute(String elementName,
633                                 String attrName,
634                                 int dataType,
635                                 boolean required,
636                                 int listMinLength,
637                                 int listMaxLength) {
638         Element element = getElement(elementName);
639         if (attrName == null) {
640             throw new IllegalArgumentException("attrName == null!");
641         }
642         if (dataType < DATATYPE_STRING || dataType > DATATYPE_DOUBLE) {
643             throw new IllegalArgumentException("Invalid value for dataType!");
644         }
645         if (listMinLength < 0 || listMinLength > listMaxLength) {
646             throw new IllegalArgumentException("Invalid list bounds!");
647         }
648 
649         Attribute attr = new Attribute();
650         attr.attrName = attrName;
651         attr.valueType = VALUE_LIST;
652         attr.dataType = dataType;
653         attr.required = required;
654         attr.listMinLength = listMinLength;
655         attr.listMaxLength = listMaxLength;
656 
657         element.attrList.add(attrName);
658         element.attrMap.put(attrName, attr);
659     }
660 
661     /**
662      * Adds a new attribute to a previously defined element that will
663      * be defined by the enumerated values {@code TRUE} and
664      * {@code FALSE}, with a datatype of
665      * {@code DATATYPE_BOOLEAN}.
666      *
667      * @param elementName the name of the element.
668      * @param attrName the name of the attribute being added.
669      * @param hasDefaultValue {@code true} if a default value
670      * should be present.
671      * @param defaultValue the default value for the attribute as a
672      * {@code boolean}, ignored if {@code hasDefaultValue}
673      * is {@code false}.
674      *
675      * @exception IllegalArgumentException if {@code elementName}
676      * is {@code null}, or is not a legal element name for this
677      * format.
678      * @exception IllegalArgumentException if {@code attrName} is
679      * {@code null}.
680      */
addBooleanAttribute(String elementName, String attrName, boolean hasDefaultValue, boolean defaultValue)681     protected void addBooleanAttribute(String elementName,
682                                        String attrName,
683                                        boolean hasDefaultValue,
684                                        boolean defaultValue) {
685         List<String> values = new ArrayList<>();
686         values.add("TRUE");
687         values.add("FALSE");
688 
689         String dval = null;
690         if (hasDefaultValue) {
691             dval = defaultValue ? "TRUE" : "FALSE";
692         }
693         addAttribute(elementName,
694                      attrName,
695                      DATATYPE_BOOLEAN,
696                      true,
697                      dval,
698                      values);
699     }
700 
701     /**
702      * Removes an attribute from a previously defined element.  If no
703      * attribute with the given name was present in the given element,
704      * nothing happens and no exception is thrown.
705      *
706      * @param elementName the name of the element.
707      * @param attrName the name of the attribute being removed.
708      *
709      * @exception IllegalArgumentException if {@code elementName}
710      * is {@code null}, or is not a legal element name for this format.
711      */
removeAttribute(String elementName, String attrName)712     protected void removeAttribute(String elementName, String attrName) {
713         Element element = getElement(elementName);
714         element.attrList.remove(attrName);
715         element.attrMap.remove(attrName);
716     }
717 
718     /**
719      * Allows an {@code Object} reference of a given class type
720      * to be stored in nodes implementing the named element.  The
721      * value of the {@code Object} is unconstrained other than by
722      * its class type.
723      *
724      * <p> If an {@code Object} reference was previously allowed,
725      * the previous settings are overwritten.
726      *
727      * @param elementName the name of the element.
728      * @param classType a {@code Class} variable indicating the
729      * legal class type for the object value.
730      * @param required {@code true} if an object value must be present.
731      * @param defaultValue the default value for the
732      * {@code Object} reference, or {@code null}.
733      * @param <T> the type of the object.
734      *
735      * @exception IllegalArgumentException if {@code elementName}
736      * is {@code null}, or is not a legal element name for this format.
737      */
addObjectValue(String elementName, Class<T> classType, boolean required, T defaultValue)738     protected <T> void addObjectValue(String elementName,
739                                       Class<T> classType,
740                                       boolean required,
741                                       T defaultValue)
742     {
743         Element element = getElement(elementName);
744         ObjectValue<T> obj = new ObjectValue<>();
745         obj.valueType = VALUE_ARBITRARY;
746         obj.classType = classType;
747         obj.defaultValue = defaultValue;
748 
749         element.objectValue = obj;
750     }
751 
752     /**
753      * Allows an {@code Object} reference of a given class type
754      * to be stored in nodes implementing the named element.  The
755      * value of the {@code Object} must be one of the values
756      * given by {@code enumeratedValues}.
757      *
758      * <p> If an {@code Object} reference was previously allowed,
759      * the previous settings are overwritten.
760      *
761      * @param elementName the name of the element.
762      * @param classType a {@code Class} variable indicating the
763      * legal class type for the object value.
764      * @param required {@code true} if an object value must be present.
765      * @param defaultValue the default value for the
766      * {@code Object} reference, or {@code null}.
767      * @param enumeratedValues a {@code List} of
768      * {@code Object}s containing the legal values for the
769      * object reference.
770      * @param <T> the type of the object.
771      *
772      * @exception IllegalArgumentException if {@code elementName}
773      * is {@code null}, or is not a legal element name for this format.
774      * @exception IllegalArgumentException if
775      * {@code enumeratedValues} is {@code null}.
776      * @exception IllegalArgumentException if
777      * {@code enumeratedValues} does not contain at least one
778      * entry.
779      * @exception IllegalArgumentException if
780      * {@code enumeratedValues} contains an element that is not
781      * an instance of the class type denoted by {@code classType}
782      * or is {@code null}.
783      */
addObjectValue(String elementName, Class<T> classType, boolean required, T defaultValue, List<? extends T> enumeratedValues)784     protected <T> void addObjectValue(String elementName,
785                                       Class<T> classType,
786                                       boolean required,
787                                       T defaultValue,
788                                       List<? extends T> enumeratedValues)
789     {
790         Element element = getElement(elementName);
791         if (enumeratedValues == null) {
792             throw new IllegalArgumentException("enumeratedValues == null!");
793         }
794         if (enumeratedValues.size() == 0) {
795             throw new IllegalArgumentException("enumeratedValues is empty!");
796         }
797         Iterator<? extends T> iter = enumeratedValues.iterator();
798         while (iter.hasNext()) {
799             Object o = iter.next();
800             if (o == null) {
801                 throw new IllegalArgumentException("enumeratedValues contains a null!");
802             }
803             if (!classType.isInstance(o)) {
804                 throw new IllegalArgumentException("enumeratedValues contains a value not of class classType!");
805             }
806         }
807 
808         ObjectValue<T> obj = new ObjectValue<>();
809         obj.valueType = VALUE_ENUMERATION;
810         obj.classType = classType;
811         obj.defaultValue = defaultValue;
812         obj.enumeratedValues = enumeratedValues;
813 
814         element.objectValue = obj;
815     }
816 
817     /**
818      * Allows an {@code Object} reference of a given class type
819      * to be stored in nodes implementing the named element.  The
820      * value of the {@code Object} must be within the range given
821      * by {@code minValue} and {@code maxValue}.
822      * Furthermore, the class type must implement the
823      * {@code Comparable} interface.
824      *
825      * <p> If an {@code Object} reference was previously allowed,
826      * the previous settings are overwritten.
827      *
828      * @param elementName the name of the element.
829      * @param classType a {@code Class} variable indicating the
830      * legal class type for the object value.
831      * @param defaultValue the default value for the
832      * @param minValue the smallest (inclusive or exclusive depending
833      * on the value of {@code minInclusive}) legal value for the
834      * object value, as a {@code String}.
835      * @param maxValue the largest (inclusive or exclusive depending
836      * on the value of {@code minInclusive}) legal value for the
837      * object value, as a {@code String}.
838      * @param minInclusive {@code true} if {@code minValue}
839      * is inclusive.
840      * @param maxInclusive {@code true} if {@code maxValue}
841      * is inclusive.
842      * @param <T> the type of the object.
843      *
844      * @exception IllegalArgumentException if {@code elementName}
845      * is {@code null}, or is not a legal element name for this
846      * format.
847      */
848     protected <T extends Object & Comparable<? super T>> void
addObjectValue(String elementName, Class<T> classType, T defaultValue, Comparable<? super T> minValue, Comparable<? super T> maxValue, boolean minInclusive, boolean maxInclusive)849         addObjectValue(String elementName,
850                        Class<T> classType,
851                        T defaultValue,
852                        Comparable<? super T> minValue,
853                        Comparable<? super T> maxValue,
854                        boolean minInclusive,
855                        boolean maxInclusive)
856     {
857         Element element = getElement(elementName);
858         ObjectValue<T> obj = new ObjectValue<>();
859         obj.valueType = VALUE_RANGE;
860         if (minInclusive) {
861             obj.valueType |= VALUE_RANGE_MIN_INCLUSIVE_MASK;
862         }
863         if (maxInclusive) {
864             obj.valueType |= VALUE_RANGE_MAX_INCLUSIVE_MASK;
865         }
866         obj.classType = classType;
867         obj.defaultValue = defaultValue;
868         obj.minValue = minValue;
869         obj.maxValue = maxValue;
870 
871         element.objectValue = obj;
872     }
873 
874     /**
875      * Allows an {@code Object} reference of a given class type
876      * to be stored in nodes implementing the named element.  The
877      * value of the {@code Object} must an array of objects of
878      * class type given by {@code classType}, with at least
879      * {@code arrayMinLength} and at most
880      * {@code arrayMaxLength} elements.
881      *
882      * <p> If an {@code Object} reference was previously allowed,
883      * the previous settings are overwritten.
884      *
885      * @param elementName the name of the element.
886      * @param classType a {@code Class} variable indicating the
887      * legal class type for the object value.
888      * @param arrayMinLength the smallest legal length for the array.
889      * @param arrayMaxLength the largest legal length for the array.
890      *
891      * @exception IllegalArgumentException if {@code elementName} is
892      * not a legal element name for this format.
893      */
addObjectValue(String elementName, Class<?> classType, int arrayMinLength, int arrayMaxLength)894     protected void addObjectValue(String elementName,
895                                   Class<?> classType,
896                                   int arrayMinLength,
897                                   int arrayMaxLength) {
898         Element element = getElement(elementName);
899         ObjectValue<Object> obj = new ObjectValue<>();
900         obj.valueType = VALUE_LIST;
901         obj.classType = classType;
902         obj.arrayMinLength = arrayMinLength;
903         obj.arrayMaxLength = arrayMaxLength;
904 
905         element.objectValue = obj;
906     }
907 
908     /**
909      * Disallows an {@code Object} reference from being stored in
910      * nodes implementing the named element.
911      *
912      * @param elementName the name of the element.
913      *
914      * @exception IllegalArgumentException if {@code elementName} is
915      * not a legal element name for this format.
916      */
removeObjectValue(String elementName)917     protected void removeObjectValue(String elementName) {
918         Element element = getElement(elementName);
919         element.objectValue = null;
920     }
921 
922     // Utility method
923 
924     // Methods from IIOMetadataFormat
925 
926     // Root
927 
getRootName()928     public String getRootName() {
929         return rootName;
930     }
931 
932     // Multiplicity
933 
canNodeAppear(String elementName, ImageTypeSpecifier imageType)934     public abstract boolean canNodeAppear(String elementName,
935                                           ImageTypeSpecifier imageType);
936 
getElementMinChildren(String elementName)937     public int getElementMinChildren(String elementName) {
938         Element element = getElement(elementName);
939         if (element.childPolicy != CHILD_POLICY_REPEAT) {
940             throw new IllegalArgumentException("Child policy not CHILD_POLICY_REPEAT!");
941         }
942         return element.minChildren;
943     }
944 
getElementMaxChildren(String elementName)945     public int getElementMaxChildren(String elementName) {
946         Element element = getElement(elementName);
947         if (element.childPolicy != CHILD_POLICY_REPEAT) {
948             throw new IllegalArgumentException("Child policy not CHILD_POLICY_REPEAT!");
949         }
950         return element.maxChildren;
951     }
952 
getResource(String key, Locale locale)953     private String getResource(String key, Locale locale) {
954         if (locale == null) {
955             locale = Locale.getDefault();
956         }
957 
958         /**
959          * Per the class documentation, resource bundles, including localized ones
960          * are intended to be delivered by the subclasser - ie supplier of the
961          * metadataformat. For the standard format and all standard plugins that
962          * is the JDK. For 3rd party plugins that they will supply their own.
963          * This includes plugins bundled with applets/applications.
964          * In all cases this means it is sufficient to search for those resource
965          * in the module that is providing the MetadataFormatImpl subclass.
966          */
967         try {
968             ResourceBundle bundle = ResourceBundle.getBundle(resourceBaseName, locale,
969                                                             this.getClass().getModule());
970             return bundle.getString(key);
971         } catch (MissingResourceException e) {
972             return null;
973         }
974     }
975 
976     /**
977      * Returns a {@code String} containing a description of the
978      * named element, or {@code null}.  The description will be
979      * localized for the supplied {@code Locale} if possible.
980      *
981      * <p> The default implementation will first locate a
982      * {@code ResourceBundle} using the current resource base
983      * name set by {@code setResourceBaseName} and the supplied
984      * {@code Locale}, using the fallback mechanism described in
985      * the comments for {@code ResourceBundle.getBundle}.  If a
986      * {@code ResourceBundle} is found, the element name will be
987      * used as a key to its {@code getString} method, and the
988      * result returned.  If no {@code ResourceBundle} is found,
989      * or no such key is present, {@code null} will be returned.
990      *
991      * <p> If {@code locale} is {@code null}, the current
992      * default {@code Locale} returned by {@code Locale.getLocale}
993      * will be used.
994      *
995      * @param elementName the name of the element.
996      * @param locale the {@code Locale} for which localization
997      * will be attempted.
998      *
999      * @return the element description.
1000      *
1001      * @exception IllegalArgumentException if {@code elementName}
1002      * is {@code null}, or is not a legal element name for this format.
1003      *
1004      * @see #setResourceBaseName
1005      */
getElementDescription(String elementName, Locale locale)1006     public String getElementDescription(String elementName,
1007                                         Locale locale) {
1008         Element element = getElement(elementName);
1009         return getResource(elementName, locale);
1010     }
1011 
1012     // Children
1013 
getChildPolicy(String elementName)1014     public int getChildPolicy(String elementName) {
1015         Element element = getElement(elementName);
1016         return element.childPolicy;
1017     }
1018 
getChildNames(String elementName)1019     public String[] getChildNames(String elementName) {
1020         Element element = getElement(elementName);
1021         if (element.childPolicy == CHILD_POLICY_EMPTY) {
1022             return null;
1023         }
1024         return element.childList.toArray(new String[0]);
1025     }
1026 
1027     // Attributes
1028 
getAttributeNames(String elementName)1029     public String[] getAttributeNames(String elementName) {
1030         Element element = getElement(elementName);
1031         List<String> names = element.attrList;
1032 
1033         String[] result = new String[names.size()];
1034         return names.toArray(result);
1035     }
1036 
getAttributeValueType(String elementName, String attrName)1037     public int getAttributeValueType(String elementName, String attrName) {
1038         Attribute attr = getAttribute(elementName, attrName);
1039         return attr.valueType;
1040     }
1041 
getAttributeDataType(String elementName, String attrName)1042     public int getAttributeDataType(String elementName, String attrName) {
1043         Attribute attr = getAttribute(elementName, attrName);
1044         return attr.dataType;
1045     }
1046 
isAttributeRequired(String elementName, String attrName)1047     public boolean isAttributeRequired(String elementName, String attrName) {
1048         Attribute attr = getAttribute(elementName, attrName);
1049         return attr.required;
1050     }
1051 
getAttributeDefaultValue(String elementName, String attrName)1052     public String getAttributeDefaultValue(String elementName,
1053                                            String attrName) {
1054         Attribute attr = getAttribute(elementName, attrName);
1055         return attr.defaultValue;
1056     }
1057 
getAttributeEnumerations(String elementName, String attrName)1058     public String[] getAttributeEnumerations(String elementName,
1059                                              String attrName) {
1060         Attribute attr = getAttribute(elementName, attrName);
1061         if (attr.valueType != VALUE_ENUMERATION) {
1062             throw new IllegalArgumentException
1063                 ("Attribute not an enumeration!");
1064         }
1065 
1066         List<String> values = attr.enumeratedValues;
1067         String[] result = new String[values.size()];
1068         return values.toArray(result);
1069     }
1070 
getAttributeMinValue(String elementName, String attrName)1071     public String getAttributeMinValue(String elementName, String attrName) {
1072         Attribute attr = getAttribute(elementName, attrName);
1073         if (attr.valueType != VALUE_RANGE &&
1074             attr.valueType != VALUE_RANGE_MIN_INCLUSIVE &&
1075             attr.valueType != VALUE_RANGE_MAX_INCLUSIVE &&
1076             attr.valueType != VALUE_RANGE_MIN_MAX_INCLUSIVE) {
1077             throw new IllegalArgumentException("Attribute not a range!");
1078         }
1079 
1080         return attr.minValue;
1081     }
1082 
getAttributeMaxValue(String elementName, String attrName)1083     public String getAttributeMaxValue(String elementName, String attrName) {
1084         Attribute attr = getAttribute(elementName, attrName);
1085         if (attr.valueType != VALUE_RANGE &&
1086             attr.valueType != VALUE_RANGE_MIN_INCLUSIVE &&
1087             attr.valueType != VALUE_RANGE_MAX_INCLUSIVE &&
1088             attr.valueType != VALUE_RANGE_MIN_MAX_INCLUSIVE) {
1089             throw new IllegalArgumentException("Attribute not a range!");
1090         }
1091 
1092         return attr.maxValue;
1093     }
1094 
getAttributeListMinLength(String elementName, String attrName)1095     public int getAttributeListMinLength(String elementName, String attrName) {
1096         Attribute attr = getAttribute(elementName, attrName);
1097         if (attr.valueType != VALUE_LIST) {
1098             throw new IllegalArgumentException("Attribute not a list!");
1099         }
1100 
1101         return attr.listMinLength;
1102     }
1103 
getAttributeListMaxLength(String elementName, String attrName)1104     public int getAttributeListMaxLength(String elementName, String attrName) {
1105         Attribute attr = getAttribute(elementName, attrName);
1106         if (attr.valueType != VALUE_LIST) {
1107             throw new IllegalArgumentException("Attribute not a list!");
1108         }
1109 
1110         return attr.listMaxLength;
1111     }
1112 
1113     /**
1114      * Returns a {@code String} containing a description of the
1115      * named attribute, or {@code null}.  The description will be
1116      * localized for the supplied {@code Locale} if possible.
1117      *
1118      * <p> The default implementation will first locate a
1119      * {@code ResourceBundle} using the current resource base
1120      * name set by {@code setResourceBaseName} and the supplied
1121      * {@code Locale}, using the fallback mechanism described in
1122      * the comments for {@code ResourceBundle.getBundle}.  If a
1123      * {@code ResourceBundle} is found, the element name followed
1124      * by a "/" character followed by the attribute name
1125      * ({@code elementName + "/" + attrName}) will be used as a
1126      * key to its {@code getString} method, and the result
1127      * returned.  If no {@code ResourceBundle} is found, or no
1128      * such key is present, {@code null} will be returned.
1129      *
1130      * <p> If {@code locale} is {@code null}, the current
1131      * default {@code Locale} returned by {@code Locale.getLocale}
1132      * will be used.
1133      *
1134      * @param elementName the name of the element.
1135      * @param attrName the name of the attribute.
1136      * @param locale the {@code Locale} for which localization
1137      * will be attempted, or {@code null}.
1138      *
1139      * @return the attribute description.
1140      *
1141      * @exception IllegalArgumentException if {@code elementName}
1142      * is {@code null}, or is not a legal element name for this format.
1143      * @exception IllegalArgumentException if {@code attrName} is
1144      * {@code null} or is not a legal attribute name for this
1145      * element.
1146      *
1147      * @see #setResourceBaseName
1148      */
getAttributeDescription(String elementName, String attrName, Locale locale)1149     public String getAttributeDescription(String elementName,
1150                                           String attrName,
1151                                           Locale locale) {
1152         Element element = getElement(elementName);
1153         if (attrName == null) {
1154             throw new IllegalArgumentException("attrName == null!");
1155         }
1156         Attribute attr = element.attrMap.get(attrName);
1157         if (attr == null) {
1158             throw new IllegalArgumentException("No such attribute!");
1159         }
1160 
1161         String key = elementName + "/" + attrName;
1162         return getResource(key, locale);
1163     }
1164 
getObjectValue(String elementName)1165     private ObjectValue<?> getObjectValue(String elementName) {
1166         Element element = getElement(elementName);
1167         ObjectValue<?> objv = element.objectValue;
1168         if (objv == null) {
1169             throw new IllegalArgumentException("No object within element " +
1170                                                elementName + "!");
1171         }
1172         return objv;
1173     }
1174 
getObjectValueType(String elementName)1175     public int getObjectValueType(String elementName) {
1176         Element element = getElement(elementName);
1177         ObjectValue<?> objv = element.objectValue;
1178         if (objv == null) {
1179             return VALUE_NONE;
1180         }
1181         return objv.valueType;
1182     }
1183 
getObjectClass(String elementName)1184     public Class<?> getObjectClass(String elementName) {
1185         ObjectValue<?> objv = getObjectValue(elementName);
1186         return objv.classType;
1187     }
1188 
getObjectDefaultValue(String elementName)1189     public Object getObjectDefaultValue(String elementName) {
1190         ObjectValue<?> objv = getObjectValue(elementName);
1191         return objv.defaultValue;
1192     }
1193 
getObjectEnumerations(String elementName)1194     public Object[] getObjectEnumerations(String elementName) {
1195         ObjectValue<?> objv = getObjectValue(elementName);
1196         if (objv.valueType != VALUE_ENUMERATION) {
1197             throw new IllegalArgumentException("Not an enumeration!");
1198         }
1199         List<?> vlist = objv.enumeratedValues;
1200         Object[] values = new Object[vlist.size()];
1201         return vlist.toArray(values);
1202     }
1203 
getObjectMinValue(String elementName)1204     public Comparable<?> getObjectMinValue(String elementName) {
1205         ObjectValue<?> objv = getObjectValue(elementName);
1206         if ((objv.valueType & VALUE_RANGE) != VALUE_RANGE) {
1207             throw new IllegalArgumentException("Not a range!");
1208         }
1209         return objv.minValue;
1210     }
1211 
getObjectMaxValue(String elementName)1212     public Comparable<?> getObjectMaxValue(String elementName) {
1213         ObjectValue<?> objv = getObjectValue(elementName);
1214         if ((objv.valueType & VALUE_RANGE) != VALUE_RANGE) {
1215             throw new IllegalArgumentException("Not a range!");
1216         }
1217         return objv.maxValue;
1218     }
1219 
getObjectArrayMinLength(String elementName)1220     public int getObjectArrayMinLength(String elementName) {
1221         ObjectValue<?> objv = getObjectValue(elementName);
1222         if (objv.valueType != VALUE_LIST) {
1223             throw new IllegalArgumentException("Not a list!");
1224         }
1225         return objv.arrayMinLength;
1226     }
1227 
getObjectArrayMaxLength(String elementName)1228     public int getObjectArrayMaxLength(String elementName) {
1229         ObjectValue<?> objv = getObjectValue(elementName);
1230         if (objv.valueType != VALUE_LIST) {
1231             throw new IllegalArgumentException("Not a list!");
1232         }
1233         return objv.arrayMaxLength;
1234     }
1235 
1236     // Standard format descriptor
1237 
createStandardFormat()1238     private static synchronized void createStandardFormat() {
1239         if (standardFormat == null) {
1240             standardFormat = new StandardMetadataFormat();
1241         }
1242     }
1243 
1244     /**
1245      * Returns an {@code IIOMetadataFormat} object describing the
1246      * standard, plug-in neutral {@code javax.imageio_1.0}
1247      * metadata document format described in the comment of the
1248      * {@code javax.imageio.metadata} package.
1249      *
1250      * @return a predefined {@code IIOMetadataFormat} instance.
1251      */
getStandardFormatInstance()1252     public static IIOMetadataFormat getStandardFormatInstance() {
1253         createStandardFormat();
1254         return standardFormat;
1255     }
1256 }
1257