001/**
002 *
003 * Copyright 2003-2007 Jive Software, 2019-2024 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     */
355    public String getFieldName() {
356        return fieldName;
357    }
358
359    public FormFieldChildElement getFormFieldChildElement(QName qname) {
360        return formFieldChildElementsMap.getFirst(qname);
361    }
362
363    public List<FormFieldChildElement> getFormFieldChildElements(QName qname) {
364        return formFieldChildElementsMap.getAll(qname);
365    }
366
367    public List<FormFieldChildElement> getFormFieldChildElements() {
368        return formFieldChildElements;
369    }
370
371    @Override
372    public String getElementName() {
373        return ELEMENT;
374    }
375
376    @Override
377    public String getNamespace() {
378        return NAMESPACE;
379    }
380
381    @Override
382    public QName getQName() {
383        return QNAME;
384    }
385
386    protected transient List<XmlElement> extraXmlChildElements;
387
388    /**
389     * Populate {@link #extraXmlChildElements}. Note that this method may be overridden by subclasses.
390     */
391    protected void populateExtraXmlChildElements() {
392        List<Value> values = getRawValues();
393        // Note that we need to create a new ArrayList here, since subclasses may add to it by overriding
394        // populateExtraXmlChildElements.
395        extraXmlChildElements = new ArrayList<>(values.size());
396        extraXmlChildElements.addAll(values);
397    }
398
399    @Override
400    public final XmlStringBuilder toXML(XmlEnvironment enclosingNamespace) {
401        return toXML(enclosingNamespace, true);
402    }
403
404    public final XmlStringBuilder toXML(XmlEnvironment enclosingNamespace, boolean includeType) {
405        XmlStringBuilder buf = new XmlStringBuilder(this, enclosingNamespace);
406        // Add attributes
407        buf.optAttribute("label", getLabel());
408        buf.optAttribute("var", getFieldName());
409
410        if (includeType) {
411            // If no 'type' is specified, the default is "text-single";
412            buf.attribute("type", getType(), Type.text_single);
413        }
414
415        if (extraXmlChildElements == null) {
416            // If extraXmlChildElements is null, see if they should be populated.
417            populateExtraXmlChildElements();
418        }
419
420        if (formFieldChildElements.isEmpty()
421                        && (extraXmlChildElements == null || extraXmlChildElements.isEmpty())) {
422            buf.closeEmptyElement();
423        } else {
424            buf.rightAngleBracket();
425
426            buf.optAppend(extraXmlChildElements);
427            buf.append(formFieldChildElements);
428
429            buf.closeElement(this);
430        }
431        return buf;
432    }
433
434    @Override
435    public boolean equals(Object obj) {
436        if (obj == null)
437            return false;
438        if (obj == this)
439            return true;
440        if (!(obj instanceof FormField))
441            return false;
442
443        FormField other = (FormField) obj;
444
445        return toXML().toString().equals(other.toXML().toString());
446    }
447
448    @Override
449    public int hashCode() {
450        return toXML().toString().hashCode();
451    }
452
453    public static BooleanFormField.Builder booleanBuilder(String fieldName) {
454        return new BooleanFormField.Builder(fieldName);
455    }
456
457    public static TextSingleFormField.Builder fixedBuilder() {
458        return fixedBuilder(null);
459    }
460
461    public static TextSingleFormField.Builder fixedBuilder(String fieldName) {
462        return new TextSingleFormField.Builder(fieldName, Type.fixed);
463    }
464
465    public static TextSingleFormField.Builder hiddenBuilder(String fieldName) {
466        return new TextSingleFormField.Builder(fieldName, Type.hidden);
467    }
468
469    public static JidMultiFormField.Builder jidMultiBuilder(String fieldName) {
470        return new JidMultiFormField.Builder(fieldName);
471    }
472
473    public static JidSingleFormField.Builder jidSingleBuilder(String fieldName) {
474        return new JidSingleFormField.Builder(fieldName);
475    }
476
477    public static ListMultiFormField.Builder listMultiBuilder(String fieldName) {
478        return new ListMultiFormField.Builder(fieldName);
479    }
480
481    public static ListSingleFormField.Builder listSingleBuilder(String fieldName) {
482        return new ListSingleFormField.Builder(fieldName);
483    }
484
485    public static TextMultiFormField.Builder textMultiBuilder(String fieldName) {
486        return new TextMultiFormField.Builder(fieldName);
487    }
488
489    public static TextSingleFormField.Builder textPrivateBuilder(String fieldName) {
490        return new TextSingleFormField.Builder(fieldName, Type.text_private);
491    }
492
493    public static TextSingleFormField.Builder textSingleBuilder(String fieldName) {
494        return new TextSingleFormField.Builder(fieldName, Type.text_single);
495    }
496
497    public static TextSingleFormField.Builder builder(String fieldName) {
498        return textSingleBuilder(fieldName);
499    }
500
501    public static TextSingleFormField buildHiddenFormType(String formType) {
502        return hiddenBuilder(FORM_TYPE).setValue(formType).build();
503    }
504
505    public <F extends FormField> F ifPossibleAs(Class<F> formFieldClass) {
506        if (formFieldClass.isInstance(this)) {
507            return formFieldClass.cast(this);
508        }
509        return null;
510    }
511
512    public <F extends FormField> F ifPossibleAsOrThrow(Class<F> formFieldClass) {
513        F field = ifPossibleAs(formFieldClass);
514        if (field == null) {
515            throw new IllegalArgumentException();
516        }
517        return field;
518    }
519
520    public TextSingleFormField asHiddenFormTypeFieldIfPossible() {
521        TextSingleFormField textSingleFormField = ifPossibleAs(TextSingleFormField.class);
522        if (textSingleFormField == null) {
523            return null;
524        }
525        if (getType() != Type.hidden) {
526            return null;
527        }
528        if (!getFieldName().equals(FORM_TYPE)) {
529            return null;
530        }
531        return textSingleFormField;
532    }
533
534    public abstract static class Builder<F extends FormField, B extends Builder<?, ?>> {
535        private final String fieldName;
536        private final Type type;
537
538        private String label;
539
540        private List<FormFieldChildElement> formFieldChildElements;
541
542        private boolean disallowType;
543        private boolean disallowFurtherFormFieldChildElements;
544
545        protected Builder(String fieldName, Type type) {
546            if (StringUtils.isNullOrEmpty(fieldName) && type != Type.fixed) {
547                throw new IllegalArgumentException("Fields of type " + type + " must have a field name set");
548            }
549            this.fieldName = fieldName;
550            this.type = type;
551        }
552
553        protected Builder(FormField formField) {
554            // TODO: Is this still correct?
555            fieldName = formField.fieldName;
556            label = formField.label;
557            type = formField.type;
558            // Create a new modifiable list.
559            formFieldChildElements = CollectionUtil.newListWith(formField.formFieldChildElements);
560        }
561
562        /**
563         * Sets a description that provides extra clarification about the question. This information
564         * could be presented to the user either in tool-tip, help button, or as a section of text
565         * before the question.
566         * <p>
567         * If the question is of type FIXED then the description should remain empty.
568         * </p>
569         *
570         * @param description provides extra clarification about the question.
571         * @return a reference to this builder.
572         */
573        public B setDescription(String description) {
574            Description descriptionElement = new Description(description);
575            setOnlyElement(descriptionElement);
576            return getThis();
577        }
578
579        /**
580         * Sets the label of the question which should give enough information to the user to
581         * fill out the form.
582         *
583         * @param label the label of the question.
584         * @return a reference to this builder.
585         */
586        public B setLabel(String label) {
587            this.label = Objects.requireNonNull(label, "label must not be null");
588            return getThis();
589        }
590
591        /**
592         * Sets if the question must be answered in order to complete the questionnaire.
593         *
594         * @return a reference to this builder.
595         */
596        public B setRequired() {
597            return setRequired(true);
598        }
599
600        /**
601         * Sets if the question must be answered in order to complete the questionnaire.
602         *
603         * @param required if the question must be answered in order to complete the questionnaire.
604         * @return a reference to this builder.
605         */
606        public B setRequired(boolean required) {
607            if (required) {
608                setOnlyElement(Required.INSTANCE);
609            }
610            return getThis();
611        }
612
613        public B addFormFieldChildElements(Collection<? extends FormFieldChildElement> formFieldChildElements) {
614            for (FormFieldChildElement formFieldChildElement : formFieldChildElements) {
615                addFormFieldChildElement(formFieldChildElement);
616            }
617            return getThis();
618        }
619
620        @SuppressWarnings("ModifyCollectionInEnhancedForLoop")
621        public B addFormFieldChildElement(FormFieldChildElement newFormFieldChildElement) {
622            if (disallowFurtherFormFieldChildElements) {
623                throw new IllegalArgumentException();
624            }
625
626            if (newFormFieldChildElement.requiresNoTypeSet() && type != null) {
627                throw new IllegalArgumentException("Elements of type " + newFormFieldChildElement.getClass()
628                                + " can only be added to form fields where no type is set");
629            }
630
631            ensureThatFormFieldChildElementsIsSet();
632
633            if (!formFieldChildElements.isEmpty() && newFormFieldChildElement.isExclusiveElement()) {
634                throw new IllegalArgumentException("Elements of type " + newFormFieldChildElement.getClass()
635                                + " must be the only child elements of a form field.");
636            }
637
638            disallowType = disallowType || newFormFieldChildElement.requiresNoTypeSet();
639            disallowFurtherFormFieldChildElements = newFormFieldChildElement.isExclusiveElement();
640
641            formFieldChildElements.add(newFormFieldChildElement);
642
643            for (FormFieldChildElement formFieldChildElement : formFieldChildElements) {
644                try {
645                    formFieldChildElement.checkConsistency(this);
646                } catch (IllegalArgumentException e) {
647                    // Remove the newly added form field child element if there it causes inconsistency.
648                    formFieldChildElements.remove(newFormFieldChildElement);
649                    throw e;
650                }
651            }
652
653            return getThis();
654        }
655
656        protected abstract void resetInternal();
657
658        public B reset() {
659            resetInternal();
660
661            if (formFieldChildElements == null) {
662                return getThis();
663            }
664
665            // TODO: Use Java' stream API once Smack's minimum Android SDK level is 24 or higher.
666            Iterator<FormFieldChildElement> it = formFieldChildElements.iterator();
667            while (it.hasNext()) {
668                FormFieldChildElement formFieldChildElement = it.next();
669                if (formFieldChildElement instanceof Value) {
670                    it.remove();
671                }
672            }
673
674            disallowType = disallowFurtherFormFieldChildElements = false;
675
676            return getThis();
677        }
678
679        public abstract F build();
680
681        public Type getType() {
682            return type;
683        }
684
685        private void ensureThatFormFieldChildElementsIsSet() {
686            if (formFieldChildElements == null) {
687                formFieldChildElements = new ArrayList<>(4);
688            }
689        }
690
691        private <E extends FormFieldChildElement> void setOnlyElement(E element) {
692            Class<?> elementClass = element.getClass();
693            ensureThatFormFieldChildElementsIsSet();
694            for (int i = 0; i < formFieldChildElements.size(); i++) {
695                if (formFieldChildElements.get(i).getClass().equals(elementClass)) {
696                    formFieldChildElements.set(i, element);
697                    return;
698                }
699            }
700
701            addFormFieldChildElement(element);
702        }
703
704        public abstract B getThis();
705    }
706
707    /**
708     * Marker class for the standard, as per XEP-0004, child elements of form fields.
709     */
710    private abstract static class StandardFormFieldChildElement implements FormFieldChildElement {
711    }
712
713    /**
714     * Represents the available options of a {@link ListSingleFormField} and {@link ListMultiFormField}.
715     *
716     * @author Gaston Dombiak
717     */
718    public static final class Option implements XmlElement {
719
720        public static final String ELEMENT = "option";
721
722        public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
723
724        private final String label;
725
726        private final Value value;
727
728        public Option(String value) {
729            this(null, value);
730        }
731
732        public Option(String label, String value) {
733            this.label = label;
734            this.value = new Value(value);
735        }
736
737        public Option(String label, Value value) {
738            this.label = label;
739            this.value = value;
740        }
741
742        /**
743         * Returns the label that represents the option.
744         *
745         * @return the label that represents the option.
746         */
747        public String getLabel() {
748            return label;
749        }
750
751        /**
752         * Returns the value of the option.
753         *
754         * @return the value of the option.
755         */
756        public Value getValue() {
757            return value;
758        }
759
760        /**
761         * Returns the string representation of the value of the option.
762         *
763         * @return the value of the option.
764         */
765        public String getValueString() {
766            return value.value.toString();
767        }
768
769        @Override
770        public String toString() {
771            return getLabel();
772        }
773
774        @Override
775        public String getElementName() {
776            return ELEMENT;
777        }
778
779        @Override
780        public String getNamespace() {
781            return NAMESPACE;
782        }
783
784        @Override
785        public QName getQName() {
786            return QNAME;
787        }
788
789        @Override
790        public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
791            XmlStringBuilder xml = new XmlStringBuilder(this);
792            // Add attribute
793            xml.optAttribute("label", getLabel());
794            xml.rightAngleBracket();
795
796            // Add element
797            xml.element("value", getValueString());
798
799            xml.closeElement(this);
800            return xml;
801        }
802
803        @Override
804        public boolean equals(Object obj) {
805            return EqualsUtil.equals(this, obj, (e, o) -> {
806                e.append(value, o.value)
807                 .append(label, o.label);
808            });
809        }
810
811        private final HashCode.Cache hashCodeCache = new HashCode.Cache();
812
813        @Override
814        public int hashCode() {
815            return hashCodeCache.getHashCode(c ->
816                c.append(value)
817                 .append(label)
818            );
819        }
820
821    }
822
823    public static class Description extends StandardFormFieldChildElement {
824
825        public static final String ELEMENT = "desc";
826
827        public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
828
829        private final String description;
830
831        public Description(String description) {
832            this.description = description;
833        }
834
835        public String getDescription() {
836            return description;
837        }
838
839        @Override
840        public String getElementName() {
841            return ELEMENT;
842        }
843
844        @Override
845        public String getNamespace() {
846            return NAMESPACE;
847        }
848
849        @Override
850        public QName getQName() {
851            return QNAME;
852        }
853
854        @Override
855        public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
856            XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
857            xml.rightAngleBracket();
858            xml.escape(description);
859            xml.closeElement(this);
860            return xml;
861        }
862    }
863
864    public static final class Required extends StandardFormFieldChildElement {
865
866        public static final Required INSTANCE = new Required();
867
868        public static final String ELEMENT = "required";
869
870        public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
871
872        private Required() {
873        }
874
875        @Override
876        public String getElementName() {
877            return ELEMENT;
878        }
879
880        @Override
881        public String getNamespace() {
882            return NAMESPACE;
883        }
884
885        @Override
886        public QName getQName() {
887            return QNAME;
888        }
889
890        @Override
891        public boolean mustBeOnlyOfHisKind() {
892            return true;
893        }
894
895        @Override
896        public String toXML(XmlEnvironment xmlEnvironment) {
897            return '<' + ELEMENT + "/>";
898        }
899    }
900
901    public static class Value implements XmlElement {
902
903        public static final String ELEMENT = "value";
904
905        public static final QName QNAME = new QName(NAMESPACE, ELEMENT);
906
907        private final CharSequence value;
908
909        public Value(CharSequence value) {
910            this.value = value;
911        }
912
913        public CharSequence getValue() {
914            return value;
915        }
916
917        @Override
918        public String getElementName() {
919            return ELEMENT;
920        }
921
922        @Override
923        public String getNamespace() {
924            return NAMESPACE;
925        }
926
927        @Override
928        public QName getQName() {
929            return QNAME;
930        }
931
932        @Override
933        public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
934            XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
935            xml.rightAngleBracket();
936            xml.escape(value);
937            return xml.closeElement(this);
938        }
939
940        @Override
941        public boolean equals(Object other) {
942            return EqualsUtil.equals(this, other, (e, o) -> e.append(this.value, o.value));
943        }
944
945        @Override
946        public int hashCode() {
947            return value.hashCode();
948        }
949    }
950}