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
26package javax.imageio.metadata;
27
28import java.util.ArrayList;
29import java.util.Collection;
30import java.util.HashMap;
31import java.util.Iterator;
32import java.util.List;
33import java.util.Locale;
34import java.util.Map;
35import java.util.MissingResourceException;
36import java.util.ResourceBundle;
37import javax.imageio.ImageTypeSpecifier;
38import 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 */
77public 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     */
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     */
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     */
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     */
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     */
272    private Element getElement(String elementName, boolean mustAppear) {
273        if (mustAppear && (elementName == null)) {
274            throw new IllegalArgumentException("element name is null!");
275        }
276        Element element = elementMap.get(elementName);
277        if (mustAppear && (element == null)) {
278            throw new IllegalArgumentException("No such element: " +
279                                               elementName);
280        }
281        return element;
282    }
283
284    private Element getElement(String elementName) {
285        return getElement(elementName, true);
286    }
287
288    // Utility method for locating an attribute
289    private Attribute getAttribute(String elementName, String attrName) {
290        Element element = getElement(elementName);
291        Attribute attr = element.attrMap.get(attrName);
292        if (attr == null) {
293            throw new IllegalArgumentException("No such attribute \"" +
294                                               attrName + "\"!");
295        }
296        return attr;
297    }
298
299    // Setup
300
301    /**
302     * Adds a new element type to this metadata document format with a
303     * child policy other than {@code CHILD_POLICY_REPEAT}.
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     */
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     */
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     */
395    protected void addChildElement(String elementName, String parentName) {
396        Element parent = getElement(parentName);
397        Element element = getElement(elementName);
398        parent.childList.add(elementName);
399        element.parentList.add(parentName);
400    }
401
402    /**
403     * Removes an element from the format.  If no element with the
404     * given name was present, nothing happens and no exception is
405     * thrown.
406     *
407     * @param elementName the name of the element to be removed.
408     */
409    protected void removeElement(String elementName) {
410        Element element = getElement(elementName, false);
411        if (element != null) {
412            Iterator<String> iter = element.parentList.iterator();
413            while (iter.hasNext()) {
414                String parentName = iter.next();
415                Element parent = getElement(parentName, false);
416                if (parent != null) {
417                    parent.childList.remove(elementName);
418                }
419            }
420            elementMap.remove(elementName);
421        }
422    }
423
424    /**
425     * Adds a new attribute to a previously defined element that may
426     * be set to an arbitrary value.
427     *
428     * @param elementName the name of the element.
429     * @param attrName the name of the attribute being added.
430     * @param dataType the data type (string format) of the attribute,
431     * one of the {@code DATATYPE_*} 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     */
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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     */
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     */
917    protected void removeObjectValue(String elementName) {
918        Element element = getElement(elementName);
919        element.objectValue = null;
920    }
921
922    // Utility method
923
924    // Methods from IIOMetadataFormat
925
926    // Root
927
928    public String getRootName() {
929        return rootName;
930    }
931
932    // Multiplicity
933
934    public abstract boolean canNodeAppear(String elementName,
935                                          ImageTypeSpecifier imageType);
936
937    public int getElementMinChildren(String elementName) {
938        Element element = getElement(elementName);
939        if (element.childPolicy != CHILD_POLICY_REPEAT) {
940            throw new IllegalArgumentException("Child policy not CHILD_POLICY_REPEAT!");
941        }
942        return element.minChildren;
943    }
944
945    public int getElementMaxChildren(String elementName) {
946        Element element = getElement(elementName);
947        if (element.childPolicy != CHILD_POLICY_REPEAT) {
948            throw new IllegalArgumentException("Child policy not CHILD_POLICY_REPEAT!");
949        }
950        return element.maxChildren;
951    }
952
953    private String getResource(String key, Locale locale) {
954        if (locale == null) {
955            locale = Locale.getDefault();
956        }
957
958        /**
959         * 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     */
1006    public String getElementDescription(String elementName,
1007                                        Locale locale) {
1008        Element element = getElement(elementName);
1009        return getResource(elementName, locale);
1010    }
1011
1012    // Children
1013
1014    public int getChildPolicy(String elementName) {
1015        Element element = getElement(elementName);
1016        return element.childPolicy;
1017    }
1018
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
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
1037    public int getAttributeValueType(String elementName, String attrName) {
1038        Attribute attr = getAttribute(elementName, attrName);
1039        return attr.valueType;
1040    }
1041
1042    public int getAttributeDataType(String elementName, String attrName) {
1043        Attribute attr = getAttribute(elementName, attrName);
1044        return attr.dataType;
1045    }
1046
1047    public boolean isAttributeRequired(String elementName, String attrName) {
1048        Attribute attr = getAttribute(elementName, attrName);
1049        return attr.required;
1050    }
1051
1052    public String getAttributeDefaultValue(String elementName,
1053                                           String attrName) {
1054        Attribute attr = getAttribute(elementName, attrName);
1055        return attr.defaultValue;
1056    }
1057
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
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
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
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
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     */
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
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
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
1184    public Class<?> getObjectClass(String elementName) {
1185        ObjectValue<?> objv = getObjectValue(elementName);
1186        return objv.classType;
1187    }
1188
1189    public Object getObjectDefaultValue(String elementName) {
1190        ObjectValue<?> objv = getObjectValue(elementName);
1191        return objv.defaultValue;
1192    }
1193
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
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
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
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
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
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     */
1252    public static IIOMetadataFormat getStandardFormatInstance() {
1253        createStandardFormat();
1254        return standardFormat;
1255    }
1256}
1257