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