001/**
002 *
003 * Copyright 2003-2007 Jive Software, 2019-2020 Florian Schmaus.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017
018package org.jivesoftware.smackx.xdata;
019
020import java.text.ParseException;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.Date;
025import java.util.Iterator;
026import java.util.List;
027
028import javax.xml.namespace.QName;
029
030import org.jivesoftware.smack.packet.FullyQualifiedElement;
031import org.jivesoftware.smack.packet.XmlEnvironment;
032import org.jivesoftware.smack.util.CollectionUtil;
033import org.jivesoftware.smack.util.EqualsUtil;
034import org.jivesoftware.smack.util.HashCode;
035import org.jivesoftware.smack.util.MultiMap;
036import org.jivesoftware.smack.util.StringUtils;
037import org.jivesoftware.smack.util.XmlStringBuilder;
038
039import org.jivesoftware.smackx.xdata.packet.DataForm;
040
041import org.jxmpp.util.XmppDateTime;
042
043/**
044 * Represents a field of a form. The field could be used to represent a question to complete,
045 * a completed question or a data returned from a search. The exact interpretation of the field
046 * depends on the context where the field is used.
047 * <p>
048 * Fields have a name, which is stored in the 'var' attribute of the field's XML representation.
049 * Field instances of all types, except of type "fixed" must have a name.
050 * </p>
051 *
052 * @author Gaston Dombiak
053 */
054public abstract class FormField implements FullyQualifiedElement {
055
056    public static final String ELEMENT = "field";
057
058    public static final String NAMESPACE = DataForm.NAMESPACE;
059
060    public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
061
062    /**
063     * The constant String "FORM_TYPE".
064     */
065    public static final String FORM_TYPE = "FORM_TYPE";
066
067    /**
068     * Form Field Types as defined in XEP-4 § 3.3.
069     *
070     * @see <a href="http://xmpp.org/extensions/xep-0004.html#protocol-fieldtypes">XEP-4 § 3.3 Field Types</a>
071     */
072    public enum Type {
073
074        /**
075         * Boolean type. Can be 0 or 1, true or false, yes or no. Default value is 0.
076         * <p>
077         * Note that in XEP-4 this type is called 'boolean', but since that String is a restricted keyword in Java, it
078         * is named 'bool' in Smack.
079         * </p>
080         */
081        bool,
082
083        /**
084         * Fixed for putting in text to show sections, or just advertise your web site in the middle of the form.
085         */
086        fixed,
087
088        /**
089         * Is not given to the user at all, but returned with the questionnaire.
090         */
091        hidden,
092
093        /**
094         * multiple entries for JIDs.
095         */
096        jid_multi,
097
098        /**
099         * Jabber ID - choosing a JID from your roster, and entering one based on the rules for a JID.
100         */
101        jid_single,
102
103        /**
104         * Given a list of choices, pick one or more.
105         */
106        list_multi,
107
108        /**
109         * Given a list of choices, pick one.
110         */
111        list_single,
112
113        /**
114         * Multiple lines of text entry.
115         */
116        text_multi,
117
118        /**
119         * Instead of showing the user what they typed, you show ***** to protect it.
120         */
121        text_private,
122
123        /**
124         * Single line or word of text.
125         */
126        text_single,
127        ;
128
129        @Override
130        public String toString() {
131            switch (this) {
132            case bool:
133                return "boolean";
134            default:
135                return this.name().replace('_', '-');
136            }
137        }
138
139        /**
140         * Get a form field type from the given string. If <code>string</code> is null, then null will be returned.
141         *
142         * @param string the string to transform or null.
143         * @return the type or null.
144         */
145        public static Type fromString(String string) {
146            if (string == null) {
147                return null;
148            }
149            switch (string) {
150            case "boolean":
151                return bool;
152            default:
153                string = string.replace('-', '_');
154                return Type.valueOf(string);
155            }
156        }
157    }
158
159    /**
160     * The field's name. Put as value in the 'var' attribute of &lt;field/&gt;.
161     */
162    private final String fieldName;
163
164    private final String label;
165
166    private final Type type;
167
168    private final List<FormFieldChildElement> formFieldChildElements;
169
170    private final MultiMap<QName, FormFieldChildElement> formFieldChildElementsMap;
171
172    /*
173     * The following four fields are cache values which are represented as child elements of </form> and hence also
174     * appear in formFieldChildElements.
175     */
176    private final String description;
177    private final boolean required;
178
179    private MultiMap<QName, FormFieldChildElement> createChildElementsMap() {
180        MultiMap<QName, FormFieldChildElement> formFieldChildElementsMap = new MultiMap<>(formFieldChildElements.size());
181        for (FormFieldChildElement formFieldChildElement : formFieldChildElements) {
182            formFieldChildElementsMap.put(formFieldChildElement.getQName(), formFieldChildElement);
183        }
184        return formFieldChildElementsMap.asUnmodifiableMultiMap();
185    }
186
187    protected FormField(Builder<?, ?> builder) {
188        fieldName = builder.fieldName;
189        label = builder.label;
190        type = builder.type;
191        if (builder.formFieldChildElements != null) {
192            formFieldChildElements = Collections.unmodifiableList(builder.formFieldChildElements);
193        } else {
194            formFieldChildElements = Collections.emptyList();
195        }
196
197        if (fieldName == null && type != Type.fixed) {
198            throw new IllegalArgumentException("The variable can only be null if the form is of type fixed");
199        }
200
201        String description = null;
202        boolean requiredElementAsChild = false;
203        ArrayList<CharSequence> values = new ArrayList<>(formFieldChildElements.size());
204        for (FormFieldChildElement formFieldChildElement : formFieldChildElements) {
205            if (formFieldChildElement instanceof Description) {
206                description = ((Description) formFieldChildElement).getDescription();
207            } else if (formFieldChildElement instanceof Required) {
208                requiredElementAsChild = true;
209            }
210        }
211        values.trimToSize();
212        this.description = description;
213
214        required = requiredElementAsChild;
215
216        formFieldChildElementsMap = createChildElementsMap();
217    }
218
219    /**
220     * Returns a description that provides extra clarification about the question. This information
221     * could be presented to the user either in tool-tip, help button, or as a section of text
222     * before the question.
223     * <p>
224     * If the question is of type FIXED then the description should remain empty.
225     * </p>
226     *
227     * @return description that provides extra clarification about the question.
228     */
229    public String getDescription() {
230        return description;
231    }
232
233    /**
234     * Returns the label of the question which should give enough information to the user to
235     * fill out the form.
236     *
237     * @return label of the question.
238     */
239    public String getLabel() {
240        return label;
241    }
242
243    /**
244     * Returns true if the question must be answered in order to complete the questionnaire.
245     *
246     * @return true if the question must be answered in order to complete the questionnaire.
247     */
248    public boolean isRequired() {
249        return required;
250    }
251
252    /**
253     * Returns an indicative of the format for the data to answer.
254     *
255     * @return format for the data to answer.
256     * @see Type
257     */
258    public Type getType() {
259        if (type == null) {
260            return Type.text_single;
261        }
262        return type;
263    }
264
265    /**
266     * Returns a List of the default values of the question if the question is part
267     * of a form to fill out. Otherwise, returns a List of the answered values of
268     * the question.
269     *
270     * @return a List of the default values or answered values of the question.
271     */
272    public abstract List<? extends CharSequence> getValues();
273
274    public boolean hasValueSet() {
275        List<?> values = getValues();
276        return !values.isEmpty();
277    }
278
279    /**
280     * Returns the values a String. Note that you should use {@link #getValues()} whenever possible instead of this
281     * method.
282     *
283     * @return a list of Strings representing the values
284     * @see #getValues()
285     * @since 4.3
286     */
287    public List<String> getValuesAsString() {
288        List<? extends CharSequence> valuesAsCharSequence = getValues();
289        List<String> res = new ArrayList<>(valuesAsCharSequence.size());
290        for (CharSequence value : valuesAsCharSequence) {
291            res.add(value.toString());
292        }
293        return res;
294    }
295
296    /**
297     * Returns the first value of this form field or {@code null}.
298     *
299     * @return the first value or {@code null}
300     * @since 4.3
301     */
302    public String getFirstValue() {
303        List<? extends CharSequence> values = getValues();
304        if (values.isEmpty()) {
305            return null;
306        }
307
308        return values.get(0).toString();
309    }
310
311    /**
312     * Parses the first value of this form field as XEP-0082 date/time format and returns a date instance or {@code null}.
313     *
314     * @return a Date instance representing the date/time information of the first value of this field.
315     * @throws ParseException if parsing fails.
316     * @since 4.3.0
317     */
318    public Date getFirstValueAsDate() throws ParseException {
319        String valueString = getFirstValue();
320        if (valueString == null) {
321            return null;
322        }
323        return XmppDateTime.parseXEP0082Date(valueString);
324    }
325
326    /**
327     * Returns the field's name, also known as the variable name in case this is an filled out answer form.
328     * <p>
329     * According to XEP-4 § 3.2 the variable name (the 'var' attribute)
330     * "uniquely identifies the field in the context of the form" (if the field is not of type 'fixed', in which case
331     * the field "MAY possess a 'var' attribute")
332     * </p>
333     *
334     * @return the field's name.
335     * @deprecated use {@link #getFieldName()} instead.
336     */
337    // TODO: Remove in Smack 4.5
338    @Deprecated
339    public String getVariable() {
340        return getFieldName();
341    }
342
343    /**
344     * Returns the field's name, also known as the variable name in case this is an filled out answer form.
345     * <p>
346     * According to XEP-4 § 3.2 the variable name (the 'var' attribute)
347     * "uniquely identifies the field in the context of the form" (if the field is not of type 'fixed', in which case
348     * the field "MAY possess a 'var' attribute")
349     * </p>
350     *
351     * @return the field's name.
352     */
353    public String getFieldName() {
354        return fieldName;
355    }
356
357    public FormFieldChildElement getFormFieldChildElement(QName qname) {
358        return formFieldChildElementsMap.getFirst(qname);
359    }
360
361    public List<FormFieldChildElement> getFormFieldChildElements(QName qname) {
362        return formFieldChildElementsMap.getAll(qname);
363    }
364
365    public List<FormFieldChildElement> getFormFieldChildElements() {
366        return formFieldChildElements;
367    }
368
369    @Override
370    public String getElementName() {
371        return ELEMENT;
372    }
373
374    @Override
375    public String getNamespace() {
376        return NAMESPACE;
377    }
378
379    @Override
380    public QName getQName() {
381        return QNAME;
382    }
383
384    protected transient List<FullyQualifiedElement> extraXmlChildElements;
385
386    protected void populateExtraXmlChildElements() {
387        List<? extends CharSequence> values = getValues();
388        extraXmlChildElements = new ArrayList<>(values.size());
389        for (CharSequence value : values) {
390            extraXmlChildElements.add(new Value(value));
391        }
392    }
393
394    @Override
395    public final XmlStringBuilder toXML(XmlEnvironment enclosingNamespace) {
396        return toXML(enclosingNamespace, true);
397    }
398
399    public final XmlStringBuilder toXML(XmlEnvironment enclosingNamespace, boolean includeType) {
400        XmlStringBuilder buf = new XmlStringBuilder(this, enclosingNamespace);
401        // Add attributes
402        buf.optAttribute("label", getLabel());
403        buf.optAttribute("var", getFieldName());
404
405        if (includeType) {
406            // If no 'type' is specified, the default is "text-single";
407            buf.attribute("type", getType(), Type.text_single);
408        }
409
410        if (extraXmlChildElements == null) {
411            // If extraXmlChildElements is null, see if they should be populated.
412            populateExtraXmlChildElements();
413        }
414
415        if (formFieldChildElements.isEmpty() && extraXmlChildElements == null) {
416            buf.closeEmptyElement();
417        } else {
418            buf.rightAngleBracket();
419
420            buf.optAppend(extraXmlChildElements);
421            buf.append(formFieldChildElements);
422
423            buf.closeElement(this);
424        }
425        return buf;
426    }
427
428    @Override
429    public boolean equals(Object obj) {
430        if (obj == null)
431            return false;
432        if (obj == this)
433            return true;
434        if (!(obj instanceof FormField))
435            return false;
436
437        FormField other = (FormField) obj;
438
439        return toXML().toString().equals(other.toXML().toString());
440    }
441
442    @Override
443    public int hashCode() {
444        return toXML().toString().hashCode();
445    }
446
447    public static BooleanFormField.Builder booleanBuilder(String fieldName) {
448        return new BooleanFormField.Builder(fieldName);
449    }
450
451    public static TextSingleFormField.Builder fixedBuilder() {
452        return fixedBuilder(null);
453    }
454
455    public static TextSingleFormField.Builder fixedBuilder(String fieldName) {
456        return new TextSingleFormField.Builder(fieldName, Type.fixed);
457    }
458
459    public static TextSingleFormField.Builder hiddenBuilder(String fieldName) {
460        return new TextSingleFormField.Builder(fieldName, Type.hidden);
461    }
462
463    public static JidMultiFormField.Builder jidMultiBuilder(String fieldName) {
464        return new JidMultiFormField.Builder(fieldName);
465    }
466
467    public static JidSingleFormField.Builder jidSingleBuilder(String fieldName) {
468        return new JidSingleFormField.Builder(fieldName);
469    }
470
471    public static ListMultiFormField.Builder listMultiBuilder(String fieldName) {
472        return new ListMultiFormField.Builder(fieldName);
473    }
474
475    public static ListSingleFormField.Builder listSingleBuilder(String fieldName) {
476        return new ListSingleFormField.Builder(fieldName);
477    }
478
479    public static TextMultiFormField.Builder textMultiBuilder(String fieldName) {
480        return new TextMultiFormField.Builder(fieldName);
481    }
482
483    public static TextSingleFormField.Builder textPrivateBuilder(String fieldName) {
484        return new TextSingleFormField.Builder(fieldName, Type.text_private);
485    }
486
487    public static TextSingleFormField.Builder textSingleBuilder(String fieldName) {
488        return new TextSingleFormField.Builder(fieldName, Type.text_single);
489    }
490
491    public static TextSingleFormField.Builder builder(String fieldName) {
492        return textSingleBuilder(fieldName);
493    }
494
495    public static TextSingleFormField buildHiddenFormType(String formType) {
496        return hiddenBuilder(FORM_TYPE).setValue(formType).build();
497    }
498
499    public <F extends FormField> F ifPossibleAs(Class<F> formFieldClass) {
500        if (formFieldClass.isInstance(this)) {
501            return formFieldClass.cast(this);
502        }
503        return null;
504    }
505
506    public <F extends FormField> F ifPossibleAsOrThrow(Class<F> formFieldClass) {
507        F field = ifPossibleAs(formFieldClass);
508        if (field == null) {
509            throw new IllegalArgumentException();
510        }
511        return field;
512    }
513
514    public TextSingleFormField asHiddenFormTypeFieldIfPossible() {
515        TextSingleFormField textSingleFormField = ifPossibleAs(TextSingleFormField.class);
516        if (textSingleFormField == null) {
517            return null;
518        }
519        if (getType() != Type.hidden) {
520            return null;
521        }
522        if (!getFieldName().equals(FORM_TYPE)) {
523            return null;
524        }
525        return textSingleFormField;
526    }
527
528    public abstract static class Builder<F extends FormField, B extends Builder<?, ?>> {
529        private final String fieldName;
530        private final Type type;
531
532        private String label;
533
534        private List<FormFieldChildElement> formFieldChildElements;
535
536        private boolean disallowType;
537        private boolean disallowFurtherFormFieldChildElements;
538
539        protected Builder(String fieldName, Type type) {
540            if (StringUtils.isNullOrEmpty(fieldName) && type != Type.fixed) {
541                throw new IllegalArgumentException("Fields of type " + type + " must have a field name set");
542            }
543            this.fieldName = fieldName;
544            this.type = type;
545        }
546
547        protected Builder(FormField formField) {
548            // TODO: Is this still correct?
549            fieldName = formField.fieldName;
550            label = formField.label;
551            type = formField.type;
552            // Create a new modifiable list.
553            formFieldChildElements = CollectionUtil.newListWith(formField.formFieldChildElements);
554        }
555
556        /**
557         * Sets a description that provides extra clarification about the question. This information
558         * could be presented to the user either in tool-tip, help button, or as a section of text
559         * before the question.
560         * <p>
561         * If the question is of type FIXED then the description should remain empty.
562         * </p>
563         *
564         * @param description provides extra clarification about the question.
565         * @return a reference to this builder.
566         */
567        public B setDescription(String description) {
568            Description descriptionElement = new Description(description);
569            setOnlyElement(descriptionElement);
570            return getThis();
571        }
572
573        /**
574         * Sets the label of the question which should give enough information to the user to
575         * fill out the form.
576         *
577         * @param label the label of the question.
578         * @return a reference to this builder.
579         */
580        public B setLabel(String label) {
581            this.label = StringUtils.requireNotNullNorEmpty(label, "label must not be null or empty");
582            return getThis();
583        }
584
585        /**
586         * Sets if the question must be answered in order to complete the questionnaire.
587         *
588         * @return a reference to this builder.
589         */
590        public B setRequired() {
591            return setRequired(true);
592        }
593
594        /**
595         * Sets if the question must be answered in order to complete the questionnaire.
596         *
597         * @param required if the question must be answered in order to complete the questionnaire.
598         * @return a reference to this builder.
599         */
600        public B setRequired(boolean required) {
601            if (required) {
602                setOnlyElement(Required.INSTANCE);
603            }
604            return getThis();
605        }
606
607        public B addFormFieldChildElements(Collection<? extends FormFieldChildElement> formFieldChildElements) {
608            for (FormFieldChildElement formFieldChildElement : formFieldChildElements) {
609                addFormFieldChildElement(formFieldChildElement);
610            }
611            return getThis();
612        }
613
614        @SuppressWarnings("ModifyCollectionInEnhancedForLoop")
615        public B addFormFieldChildElement(FormFieldChildElement newFormFieldChildElement) {
616            if (disallowFurtherFormFieldChildElements) {
617                throw new IllegalArgumentException();
618            }
619
620            if (newFormFieldChildElement.requiresNoTypeSet() && type != null) {
621                throw new IllegalArgumentException("Elements of type " + newFormFieldChildElement.getClass()
622                                + " can only be added to form fields where no type is set");
623            }
624
625            ensureThatFormFieldChildElementsIsSet();
626
627            if (!formFieldChildElements.isEmpty() && newFormFieldChildElement.isExclusiveElement()) {
628                throw new IllegalArgumentException("Elements of type " + newFormFieldChildElement.getClass()
629                                + " must be the only child elements of a form field.");
630            }
631
632            disallowType = disallowType || newFormFieldChildElement.requiresNoTypeSet();
633            disallowFurtherFormFieldChildElements = newFormFieldChildElement.isExclusiveElement();
634
635            formFieldChildElements.add(newFormFieldChildElement);
636
637            for (FormFieldChildElement formFieldChildElement : formFieldChildElements) {
638                try {
639                    formFieldChildElement.checkConsistency(this);
640                } catch (IllegalArgumentException e) {
641                    // Remove the newly added form field child element if there it causes inconsistency.
642                    formFieldChildElements.remove(newFormFieldChildElement);
643                    throw e;
644                }
645            }
646
647            return getThis();
648        }
649
650        protected abstract void resetInternal();
651
652        public B reset() {
653            resetInternal();
654
655            if (formFieldChildElements == null) {
656                return getThis();
657            }
658
659            // TODO: Use Java' stream API once Smack's minimum Android SDK level is 24 or higher.
660            Iterator<FormFieldChildElement> it = formFieldChildElements.iterator();
661            while (it.hasNext()) {
662                FormFieldChildElement formFieldChildElement = it.next();
663                if (formFieldChildElement instanceof Value) {
664                    it.remove();
665                }
666            }
667
668            disallowType = disallowFurtherFormFieldChildElements = false;
669
670            return getThis();
671        }
672
673        public abstract F build();
674
675        public Type getType() {
676            return type;
677        }
678
679        private void ensureThatFormFieldChildElementsIsSet() {
680            if (formFieldChildElements == null) {
681                formFieldChildElements = new ArrayList<>(4);
682            }
683        }
684
685        private <E extends FormFieldChildElement> void setOnlyElement(E element) {
686            Class<?> elementClass = element.getClass();
687            ensureThatFormFieldChildElementsIsSet();
688            for (int i = 0; i < formFieldChildElements.size(); i++) {
689                if (formFieldChildElements.get(i).getClass().equals(elementClass)) {
690                    formFieldChildElements.set(i, element);
691                    return;
692                }
693            }
694
695            addFormFieldChildElement(element);
696        }
697
698        public abstract B getThis();
699    }
700
701    /**
702     * Marker class for the standard, as per XEP-0004, child elements of form fields.
703     */
704    private abstract static class StandardFormFieldChildElement implements FormFieldChildElement {
705    }
706
707    /**
708     * Represents the available options of a {@link ListSingleFormField} and {@link ListMultiFormField}.
709     *
710     * @author Gaston Dombiak
711     */
712    public static final class Option implements FullyQualifiedElement {
713
714        public static final String ELEMENT = "option";
715
716        public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
717
718        private final String label;
719
720        private final Value value;
721
722        public Option(String value) {
723            this(null, value);
724        }
725
726        public Option(String label, String value) {
727            this.label = label;
728            this.value = new Value(value);
729        }
730
731        public Option(String label, Value value) {
732            this.label = label;
733            this.value = value;
734        }
735
736        /**
737         * Returns the label that represents the option.
738         *
739         * @return the label that represents the option.
740         */
741        public String getLabel() {
742            return label;
743        }
744
745        /**
746         * Returns the value of the option.
747         *
748         * @return the value of the option.
749         */
750        public Value getValue() {
751            return value;
752        }
753
754        /**
755         * Returns the string representation of the value of the option.
756         *
757         * @return the value of the option.
758         */
759        public String getValueString() {
760            return value.value.toString();
761        }
762
763        @Override
764        public String toString() {
765            return getLabel();
766        }
767
768        @Override
769        public String getElementName() {
770            return ELEMENT;
771        }
772
773        @Override
774        public String getNamespace() {
775            return NAMESPACE;
776        }
777
778        @Override
779        public QName getQName() {
780            return QNAME;
781        }
782
783        @Override
784        public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
785            XmlStringBuilder xml = new XmlStringBuilder(this);
786            // Add attribute
787            xml.optAttribute("label", getLabel());
788            xml.rightAngleBracket();
789
790            // Add element
791            xml.element("value", getValueString());
792
793            xml.closeElement(this);
794            return xml;
795        }
796
797        @Override
798        public boolean equals(Object obj) {
799            return EqualsUtil.equals(this, obj, (e, o) -> {
800                e.append(value, o.value)
801                 .append(label, o.label);
802            });
803        }
804
805        private final HashCode.Cache hashCodeCache = new HashCode.Cache();
806
807        @Override
808        public int hashCode() {
809            return hashCodeCache.getHashCode(c ->
810                c.append(value)
811                 .append(label)
812            );
813        }
814
815    }
816
817    public static class Description extends StandardFormFieldChildElement {
818
819        public static final String ELEMENT = "desc";
820
821        public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
822
823        private final String description;
824
825        public Description(String description) {
826            this.description = description;
827        }
828
829        public String getDescription() {
830            return description;
831        }
832
833        @Override
834        public String getElementName() {
835            return ELEMENT;
836        }
837
838        @Override
839        public String getNamespace() {
840            return NAMESPACE;
841        }
842
843        @Override
844        public QName getQName() {
845            return QNAME;
846        }
847
848        @Override
849        public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
850            XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
851            xml.rightAngleBracket();
852            xml.escape(description);
853            xml.closeElement(this);
854            return xml;
855        }
856    }
857
858    public static final class Required extends StandardFormFieldChildElement {
859
860        public static final Required INSTANCE = new Required();
861
862        public static final String ELEMENT = "required";
863
864        public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
865
866        private Required() {
867        }
868
869        @Override
870        public String getElementName() {
871            return ELEMENT;
872        }
873
874        @Override
875        public String getNamespace() {
876            return NAMESPACE;
877        }
878
879        @Override
880        public QName getQName() {
881            return QNAME;
882        }
883
884        @Override
885        public boolean mustBeOnlyOfHisKind() {
886            return true;
887        }
888
889        @Override
890        public String toXML(XmlEnvironment xmlEnvironment) {
891            return '<' + ELEMENT + "/>";
892        }
893    }
894
895    public static class Value implements FullyQualifiedElement {
896
897        public static final String ELEMENT = "value";
898
899        public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
900
901        private final CharSequence value;
902
903        public Value(CharSequence value) {
904            this.value = value;
905        }
906
907        public CharSequence getValue() {
908            return value;
909        }
910
911        @Override
912        public String getElementName() {
913            return ELEMENT;
914        }
915
916        @Override
917        public String getNamespace() {
918            return NAMESPACE;
919        }
920
921        @Override
922        public QName getQName() {
923            return QNAME;
924        }
925
926        @Override
927        public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
928            XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
929            xml.rightAngleBracket();
930            xml.escape(value);
931            return xml.closeElement(this);
932        }
933
934        @Override
935        public boolean equals(Object other) {
936            return EqualsUtil.equals(this, other, (e, o) -> e.append(this.value, o.value));
937        }
938
939        @Override
940        public int hashCode() {
941            return value.hashCode();
942        }
943    }
944}