FormField.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software, 2019-2024 Florian Schmaus.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.jivesoftware.smackx.xdata;

  18. import java.text.ParseException;
  19. import java.util.ArrayList;
  20. import java.util.Collection;
  21. import java.util.Collections;
  22. import java.util.Date;
  23. import java.util.Iterator;
  24. import java.util.List;

  25. import javax.xml.namespace.QName;

  26. import org.jivesoftware.smack.packet.XmlElement;
  27. import org.jivesoftware.smack.packet.XmlEnvironment;
  28. import org.jivesoftware.smack.util.CollectionUtil;
  29. import org.jivesoftware.smack.util.EqualsUtil;
  30. import org.jivesoftware.smack.util.HashCode;
  31. import org.jivesoftware.smack.util.MultiMap;
  32. import org.jivesoftware.smack.util.Objects;
  33. import org.jivesoftware.smack.util.StringUtils;
  34. import org.jivesoftware.smack.util.XmlStringBuilder;

  35. import org.jivesoftware.smackx.xdata.packet.DataForm;

  36. import org.jxmpp.util.XmppDateTime;

  37. /**
  38.  * Represents a field of a form. The field could be used to represent a question to complete,
  39.  * a completed question or a data returned from a search. The exact interpretation of the field
  40.  * depends on the context where the field is used.
  41.  * <p>
  42.  * Fields have a name, which is stored in the 'var' attribute of the field's XML representation.
  43.  * Field instances of all types, except of type "fixed" must have a name.
  44.  * </p>
  45.  *
  46.  * @author Gaston Dombiak
  47.  */
  48. public abstract class FormField implements XmlElement {

  49.     public static final String ELEMENT = "field";

  50.     public static final String NAMESPACE = DataForm.NAMESPACE;

  51.     public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

  52.     /**
  53.      * The constant String "FORM_TYPE".
  54.      */
  55.     public static final String FORM_TYPE = "FORM_TYPE";

  56.     /**
  57.      * Form Field Types as defined in XEP-4 § 3.3.
  58.      *
  59.      * @see <a href="http://xmpp.org/extensions/xep-0004.html#protocol-fieldtypes">XEP-4 § 3.3 Field Types</a>
  60.      */
  61.     public enum Type {

  62.         /**
  63.          * Boolean type. Can be 0 or 1, true or false, yes or no. Default value is 0.
  64.          * <p>
  65.          * Note that in XEP-4 this type is called 'boolean', but since that String is a restricted keyword in Java, it
  66.          * is named 'bool' in Smack.
  67.          * </p>
  68.          */
  69.         bool,

  70.         /**
  71.          * Fixed for putting in text to show sections, or just advertise your web site in the middle of the form.
  72.          */
  73.         fixed,

  74.         /**
  75.          * Is not given to the user at all, but returned with the questionnaire.
  76.          */
  77.         hidden,

  78.         /**
  79.          * multiple entries for JIDs.
  80.          */
  81.         jid_multi,

  82.         /**
  83.          * Jabber ID - choosing a JID from your roster, and entering one based on the rules for a JID.
  84.          */
  85.         jid_single,

  86.         /**
  87.          * Given a list of choices, pick one or more.
  88.          */
  89.         list_multi,

  90.         /**
  91.          * Given a list of choices, pick one.
  92.          */
  93.         list_single,

  94.         /**
  95.          * Multiple lines of text entry.
  96.          */
  97.         text_multi,

  98.         /**
  99.          * Instead of showing the user what they typed, you show ***** to protect it.
  100.          */
  101.         text_private,

  102.         /**
  103.          * Single line or word of text.
  104.          */
  105.         text_single,
  106.         ;

  107.         @Override
  108.         public String toString() {
  109.             switch (this) {
  110.             case bool:
  111.                 return "boolean";
  112.             default:
  113.                 return this.name().replace('_', '-');
  114.             }
  115.         }

  116.         /**
  117.          * Get a form field type from the given string. If <code>string</code> is null, then null will be returned.
  118.          *
  119.          * @param string the string to transform or null.
  120.          * @return the type or null.
  121.          */
  122.         public static Type fromString(String string) {
  123.             if (string == null) {
  124.                 return null;
  125.             }
  126.             switch (string) {
  127.             case "boolean":
  128.                 return bool;
  129.             default:
  130.                 string = string.replace('-', '_');
  131.                 return Type.valueOf(string);
  132.             }
  133.         }
  134.     }

  135.     /**
  136.      * The field's name. Put as value in the 'var' attribute of &lt;field/&gt;.
  137.      */
  138.     private final String fieldName;

  139.     private final String label;

  140.     private final Type type;

  141.     private final List<FormFieldChildElement> formFieldChildElements;

  142.     private final MultiMap<QName, FormFieldChildElement> formFieldChildElementsMap;

  143.     /*
  144.      * The following four fields are cache values which are represented as child elements of </form> and hence also
  145.      * appear in formFieldChildElements.
  146.      */
  147.     private final String description;
  148.     private final boolean required;

  149.     private MultiMap<QName, FormFieldChildElement> createChildElementsMap() {
  150.         MultiMap<QName, FormFieldChildElement> formFieldChildElementsMap = new MultiMap<>(formFieldChildElements.size());
  151.         for (FormFieldChildElement formFieldChildElement : formFieldChildElements) {
  152.             formFieldChildElementsMap.put(formFieldChildElement.getQName(), formFieldChildElement);
  153.         }
  154.         return formFieldChildElementsMap.asUnmodifiableMultiMap();
  155.     }

  156.     protected FormField(Builder<?, ?> builder) {
  157.         fieldName = builder.fieldName;
  158.         label = builder.label;
  159.         type = builder.type;
  160.         if (builder.formFieldChildElements != null) {
  161.             formFieldChildElements = Collections.unmodifiableList(builder.formFieldChildElements);
  162.         } else {
  163.             formFieldChildElements = Collections.emptyList();
  164.         }

  165.         if (fieldName == null && type != Type.fixed) {
  166.             throw new IllegalArgumentException("The variable can only be null if the form is of type fixed");
  167.         }

  168.         String description = null;
  169.         boolean requiredElementAsChild = false;
  170.         ArrayList<CharSequence> values = new ArrayList<>(formFieldChildElements.size());
  171.         for (FormFieldChildElement formFieldChildElement : formFieldChildElements) {
  172.             if (formFieldChildElement instanceof Description) {
  173.                 description = ((Description) formFieldChildElement).getDescription();
  174.             } else if (formFieldChildElement instanceof Required) {
  175.                 requiredElementAsChild = true;
  176.             }
  177.         }
  178.         values.trimToSize();
  179.         this.description = description;

  180.         required = requiredElementAsChild;

  181.         formFieldChildElementsMap = createChildElementsMap();
  182.     }

  183.     /**
  184.      * Returns a description that provides extra clarification about the question. This information
  185.      * could be presented to the user either in tool-tip, help button, or as a section of text
  186.      * before the question.
  187.      * <p>
  188.      * If the question is of type FIXED then the description should remain empty.
  189.      * </p>
  190.      *
  191.      * @return description that provides extra clarification about the question.
  192.      */
  193.     public String getDescription() {
  194.         return description;
  195.     }

  196.     /**
  197.      * Returns the label of the question which should give enough information to the user to
  198.      * fill out the form.
  199.      *
  200.      * @return label of the question.
  201.      */
  202.     public String getLabel() {
  203.         return label;
  204.     }

  205.     /**
  206.      * Returns true if the question must be answered in order to complete the questionnaire.
  207.      *
  208.      * @return true if the question must be answered in order to complete the questionnaire.
  209.      */
  210.     public boolean isRequired() {
  211.         return required;
  212.     }

  213.     /**
  214.      * Returns an indicative of the format for the data to answer.
  215.      *
  216.      * @return format for the data to answer.
  217.      * @see Type
  218.      */
  219.     public Type getType() {
  220.         if (type == null) {
  221.             return Type.text_single;
  222.         }
  223.         return type;
  224.     }

  225.     /**
  226.      * Returns a List of the default values of the question if the question is part
  227.      * of a form to fill out. Otherwise, returns a List of the answered values of
  228.      * the question.
  229.      *
  230.      * @return a List of the default values or answered values of the question.
  231.      */
  232.     public List<? extends CharSequence> getValues() {
  233.         return getRawValueCharSequences();
  234.     }

  235.     public abstract List<Value> getRawValues();

  236.     private transient List<CharSequence> rawValueCharSequences;

  237.     public final List<CharSequence> getRawValueCharSequences() {
  238.         if (rawValueCharSequences == null) {
  239.             List<Value> rawValues = getRawValues();
  240.             rawValueCharSequences = new ArrayList<>(rawValues.size());
  241.             for (Value value : rawValues) {
  242.                 rawValueCharSequences.add(value.value);
  243.             }
  244.         }

  245.         return rawValueCharSequences;
  246.     }

  247.     public boolean hasValueSet() {
  248.         List<?> values = getValues();
  249.         return !values.isEmpty();
  250.     }

  251.     /**
  252.      * Returns the values a String. Note that you should use {@link #getValues()} whenever possible instead of this
  253.      * method.
  254.      *
  255.      * @return a list of Strings representing the values
  256.      * @see #getValues()
  257.      * @since 4.3
  258.      */
  259.     public List<String> getValuesAsString() {
  260.         List<? extends CharSequence> valuesAsCharSequence = getValues();
  261.         List<String> res = new ArrayList<>(valuesAsCharSequence.size());
  262.         for (CharSequence value : valuesAsCharSequence) {
  263.             res.add(value.toString());
  264.         }
  265.         return res;
  266.     }

  267.     /**
  268.      * Returns the first value of this form field or {@code null}.
  269.      *
  270.      * @return the first value or {@code null}
  271.      * @since 4.3
  272.      */
  273.     public String getFirstValue() {
  274.         List<? extends CharSequence> values = getValues();
  275.         if (values.isEmpty()) {
  276.             return null;
  277.         }

  278.         return values.get(0).toString();
  279.     }

  280.     /**
  281.      * Parses the first value of this form field as XEP-0082 date/time format and returns a date instance or {@code null}.
  282.      *
  283.      * @return a Date instance representing the date/time information of the first value of this field.
  284.      * @throws ParseException if parsing fails.
  285.      * @since 4.3.0
  286.      */
  287.     public Date getFirstValueAsDate() throws ParseException {
  288.         String valueString = getFirstValue();
  289.         if (valueString == null) {
  290.             return null;
  291.         }
  292.         return XmppDateTime.parseXEP0082Date(valueString);
  293.     }

  294.     /**
  295.      * Returns the field's name, also known as the variable name in case this is an filled out answer form.
  296.      * <p>
  297.      * According to XEP-4 § 3.2 the variable name (the 'var' attribute)
  298.      * "uniquely identifies the field in the context of the form" (if the field is not of type 'fixed', in which case
  299.      * the field "MAY possess a 'var' attribute")
  300.      * </p>
  301.      *
  302.      * @return the field's name.
  303.      */
  304.     public String getFieldName() {
  305.         return fieldName;
  306.     }

  307.     public FormFieldChildElement getFormFieldChildElement(QName qname) {
  308.         return formFieldChildElementsMap.getFirst(qname);
  309.     }

  310.     public List<FormFieldChildElement> getFormFieldChildElements(QName qname) {
  311.         return formFieldChildElementsMap.getAll(qname);
  312.     }

  313.     public List<FormFieldChildElement> getFormFieldChildElements() {
  314.         return formFieldChildElements;
  315.     }

  316.     @Override
  317.     public String getElementName() {
  318.         return ELEMENT;
  319.     }

  320.     @Override
  321.     public String getNamespace() {
  322.         return NAMESPACE;
  323.     }

  324.     @Override
  325.     public QName getQName() {
  326.         return QNAME;
  327.     }

  328.     protected transient List<XmlElement> extraXmlChildElements;

  329.     /**
  330.      * Populate @{link {@link #extraXmlChildElements}}. Note that this method may be overridden by subclasses.
  331.      */
  332.     protected void populateExtraXmlChildElements() {
  333.         List<Value> values = getRawValues();
  334.         // Note that we need to create a new ArrayList here, since subclasses may add to it by overriding
  335.         // populateExtraXmlChildElements.
  336.         extraXmlChildElements = new ArrayList<>(values.size());
  337.         extraXmlChildElements.addAll(values);
  338.     }

  339.     @Override
  340.     public final XmlStringBuilder toXML(XmlEnvironment enclosingNamespace) {
  341.         return toXML(enclosingNamespace, true);
  342.     }

  343.     public final XmlStringBuilder toXML(XmlEnvironment enclosingNamespace, boolean includeType) {
  344.         XmlStringBuilder buf = new XmlStringBuilder(this, enclosingNamespace);
  345.         // Add attributes
  346.         buf.optAttribute("label", getLabel());
  347.         buf.optAttribute("var", getFieldName());

  348.         if (includeType) {
  349.             // If no 'type' is specified, the default is "text-single";
  350.             buf.attribute("type", getType(), Type.text_single);
  351.         }

  352.         if (extraXmlChildElements == null) {
  353.             // If extraXmlChildElements is null, see if they should be populated.
  354.             populateExtraXmlChildElements();
  355.         }

  356.         if (formFieldChildElements.isEmpty()
  357.                         && (extraXmlChildElements == null || extraXmlChildElements.isEmpty())) {
  358.             buf.closeEmptyElement();
  359.         } else {
  360.             buf.rightAngleBracket();

  361.             buf.optAppend(extraXmlChildElements);
  362.             buf.append(formFieldChildElements);

  363.             buf.closeElement(this);
  364.         }
  365.         return buf;
  366.     }

  367.     @Override
  368.     public boolean equals(Object obj) {
  369.         if (obj == null)
  370.             return false;
  371.         if (obj == this)
  372.             return true;
  373.         if (!(obj instanceof FormField))
  374.             return false;

  375.         FormField other = (FormField) obj;

  376.         return toXML().toString().equals(other.toXML().toString());
  377.     }

  378.     @Override
  379.     public int hashCode() {
  380.         return toXML().toString().hashCode();
  381.     }

  382.     public static BooleanFormField.Builder booleanBuilder(String fieldName) {
  383.         return new BooleanFormField.Builder(fieldName);
  384.     }

  385.     public static TextSingleFormField.Builder fixedBuilder() {
  386.         return fixedBuilder(null);
  387.     }

  388.     public static TextSingleFormField.Builder fixedBuilder(String fieldName) {
  389.         return new TextSingleFormField.Builder(fieldName, Type.fixed);
  390.     }

  391.     public static TextSingleFormField.Builder hiddenBuilder(String fieldName) {
  392.         return new TextSingleFormField.Builder(fieldName, Type.hidden);
  393.     }

  394.     public static JidMultiFormField.Builder jidMultiBuilder(String fieldName) {
  395.         return new JidMultiFormField.Builder(fieldName);
  396.     }

  397.     public static JidSingleFormField.Builder jidSingleBuilder(String fieldName) {
  398.         return new JidSingleFormField.Builder(fieldName);
  399.     }

  400.     public static ListMultiFormField.Builder listMultiBuilder(String fieldName) {
  401.         return new ListMultiFormField.Builder(fieldName);
  402.     }

  403.     public static ListSingleFormField.Builder listSingleBuilder(String fieldName) {
  404.         return new ListSingleFormField.Builder(fieldName);
  405.     }

  406.     public static TextMultiFormField.Builder textMultiBuilder(String fieldName) {
  407.         return new TextMultiFormField.Builder(fieldName);
  408.     }

  409.     public static TextSingleFormField.Builder textPrivateBuilder(String fieldName) {
  410.         return new TextSingleFormField.Builder(fieldName, Type.text_private);
  411.     }

  412.     public static TextSingleFormField.Builder textSingleBuilder(String fieldName) {
  413.         return new TextSingleFormField.Builder(fieldName, Type.text_single);
  414.     }

  415.     public static TextSingleFormField.Builder builder(String fieldName) {
  416.         return textSingleBuilder(fieldName);
  417.     }

  418.     public static TextSingleFormField buildHiddenFormType(String formType) {
  419.         return hiddenBuilder(FORM_TYPE).setValue(formType).build();
  420.     }

  421.     public <F extends FormField> F ifPossibleAs(Class<F> formFieldClass) {
  422.         if (formFieldClass.isInstance(this)) {
  423.             return formFieldClass.cast(this);
  424.         }
  425.         return null;
  426.     }

  427.     public <F extends FormField> F ifPossibleAsOrThrow(Class<F> formFieldClass) {
  428.         F field = ifPossibleAs(formFieldClass);
  429.         if (field == null) {
  430.             throw new IllegalArgumentException();
  431.         }
  432.         return field;
  433.     }

  434.     public TextSingleFormField asHiddenFormTypeFieldIfPossible() {
  435.         TextSingleFormField textSingleFormField = ifPossibleAs(TextSingleFormField.class);
  436.         if (textSingleFormField == null) {
  437.             return null;
  438.         }
  439.         if (getType() != Type.hidden) {
  440.             return null;
  441.         }
  442.         if (!getFieldName().equals(FORM_TYPE)) {
  443.             return null;
  444.         }
  445.         return textSingleFormField;
  446.     }

  447.     public abstract static class Builder<F extends FormField, B extends Builder<?, ?>> {
  448.         private final String fieldName;
  449.         private final Type type;

  450.         private String label;

  451.         private List<FormFieldChildElement> formFieldChildElements;

  452.         private boolean disallowType;
  453.         private boolean disallowFurtherFormFieldChildElements;

  454.         protected Builder(String fieldName, Type type) {
  455.             if (StringUtils.isNullOrEmpty(fieldName) && type != Type.fixed) {
  456.                 throw new IllegalArgumentException("Fields of type " + type + " must have a field name set");
  457.             }
  458.             this.fieldName = fieldName;
  459.             this.type = type;
  460.         }

  461.         protected Builder(FormField formField) {
  462.             // TODO: Is this still correct?
  463.             fieldName = formField.fieldName;
  464.             label = formField.label;
  465.             type = formField.type;
  466.             // Create a new modifiable list.
  467.             formFieldChildElements = CollectionUtil.newListWith(formField.formFieldChildElements);
  468.         }

  469.         /**
  470.          * Sets a description that provides extra clarification about the question. This information
  471.          * could be presented to the user either in tool-tip, help button, or as a section of text
  472.          * before the question.
  473.          * <p>
  474.          * If the question is of type FIXED then the description should remain empty.
  475.          * </p>
  476.          *
  477.          * @param description provides extra clarification about the question.
  478.          * @return a reference to this builder.
  479.          */
  480.         public B setDescription(String description) {
  481.             Description descriptionElement = new Description(description);
  482.             setOnlyElement(descriptionElement);
  483.             return getThis();
  484.         }

  485.         /**
  486.          * Sets the label of the question which should give enough information to the user to
  487.          * fill out the form.
  488.          *
  489.          * @param label the label of the question.
  490.          * @return a reference to this builder.
  491.          */
  492.         public B setLabel(String label) {
  493.             this.label = Objects.requireNonNull(label, "label must not be null");
  494.             return getThis();
  495.         }

  496.         /**
  497.          * Sets if the question must be answered in order to complete the questionnaire.
  498.          *
  499.          * @return a reference to this builder.
  500.          */
  501.         public B setRequired() {
  502.             return setRequired(true);
  503.         }

  504.         /**
  505.          * Sets if the question must be answered in order to complete the questionnaire.
  506.          *
  507.          * @param required if the question must be answered in order to complete the questionnaire.
  508.          * @return a reference to this builder.
  509.          */
  510.         public B setRequired(boolean required) {
  511.             if (required) {
  512.                 setOnlyElement(Required.INSTANCE);
  513.             }
  514.             return getThis();
  515.         }

  516.         public B addFormFieldChildElements(Collection<? extends FormFieldChildElement> formFieldChildElements) {
  517.             for (FormFieldChildElement formFieldChildElement : formFieldChildElements) {
  518.                 addFormFieldChildElement(formFieldChildElement);
  519.             }
  520.             return getThis();
  521.         }

  522.         @SuppressWarnings("ModifyCollectionInEnhancedForLoop")
  523.         public B addFormFieldChildElement(FormFieldChildElement newFormFieldChildElement) {
  524.             if (disallowFurtherFormFieldChildElements) {
  525.                 throw new IllegalArgumentException();
  526.             }

  527.             if (newFormFieldChildElement.requiresNoTypeSet() && type != null) {
  528.                 throw new IllegalArgumentException("Elements of type " + newFormFieldChildElement.getClass()
  529.                                 + " can only be added to form fields where no type is set");
  530.             }

  531.             ensureThatFormFieldChildElementsIsSet();

  532.             if (!formFieldChildElements.isEmpty() && newFormFieldChildElement.isExclusiveElement()) {
  533.                 throw new IllegalArgumentException("Elements of type " + newFormFieldChildElement.getClass()
  534.                                 + " must be the only child elements of a form field.");
  535.             }

  536.             disallowType = disallowType || newFormFieldChildElement.requiresNoTypeSet();
  537.             disallowFurtherFormFieldChildElements = newFormFieldChildElement.isExclusiveElement();

  538.             formFieldChildElements.add(newFormFieldChildElement);

  539.             for (FormFieldChildElement formFieldChildElement : formFieldChildElements) {
  540.                 try {
  541.                     formFieldChildElement.checkConsistency(this);
  542.                 } catch (IllegalArgumentException e) {
  543.                     // Remove the newly added form field child element if there it causes inconsistency.
  544.                     formFieldChildElements.remove(newFormFieldChildElement);
  545.                     throw e;
  546.                 }
  547.             }

  548.             return getThis();
  549.         }

  550.         protected abstract void resetInternal();

  551.         public B reset() {
  552.             resetInternal();

  553.             if (formFieldChildElements == null) {
  554.                 return getThis();
  555.             }

  556.             // TODO: Use Java' stream API once Smack's minimum Android SDK level is 24 or higher.
  557.             Iterator<FormFieldChildElement> it = formFieldChildElements.iterator();
  558.             while (it.hasNext()) {
  559.                 FormFieldChildElement formFieldChildElement = it.next();
  560.                 if (formFieldChildElement instanceof Value) {
  561.                     it.remove();
  562.                 }
  563.             }

  564.             disallowType = disallowFurtherFormFieldChildElements = false;

  565.             return getThis();
  566.         }

  567.         public abstract F build();

  568.         public Type getType() {
  569.             return type;
  570.         }

  571.         private void ensureThatFormFieldChildElementsIsSet() {
  572.             if (formFieldChildElements == null) {
  573.                 formFieldChildElements = new ArrayList<>(4);
  574.             }
  575.         }

  576.         private <E extends FormFieldChildElement> void setOnlyElement(E element) {
  577.             Class<?> elementClass = element.getClass();
  578.             ensureThatFormFieldChildElementsIsSet();
  579.             for (int i = 0; i < formFieldChildElements.size(); i++) {
  580.                 if (formFieldChildElements.get(i).getClass().equals(elementClass)) {
  581.                     formFieldChildElements.set(i, element);
  582.                     return;
  583.                 }
  584.             }

  585.             addFormFieldChildElement(element);
  586.         }

  587.         public abstract B getThis();
  588.     }

  589.     /**
  590.      * Marker class for the standard, as per XEP-0004, child elements of form fields.
  591.      */
  592.     private abstract static class StandardFormFieldChildElement implements FormFieldChildElement {
  593.     }

  594.     /**
  595.      * Represents the available options of a {@link ListSingleFormField} and {@link ListMultiFormField}.
  596.      *
  597.      * @author Gaston Dombiak
  598.      */
  599.     public static final class Option implements XmlElement {

  600.         public static final String ELEMENT = "option";

  601.         public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

  602.         private final String label;

  603.         private final Value value;

  604.         public Option(String value) {
  605.             this(null, value);
  606.         }

  607.         public Option(String label, String value) {
  608.             this.label = label;
  609.             this.value = new Value(value);
  610.         }

  611.         public Option(String label, Value value) {
  612.             this.label = label;
  613.             this.value = value;
  614.         }

  615.         /**
  616.          * Returns the label that represents the option.
  617.          *
  618.          * @return the label that represents the option.
  619.          */
  620.         public String getLabel() {
  621.             return label;
  622.         }

  623.         /**
  624.          * Returns the value of the option.
  625.          *
  626.          * @return the value of the option.
  627.          */
  628.         public Value getValue() {
  629.             return value;
  630.         }

  631.         /**
  632.          * Returns the string representation of the value of the option.
  633.          *
  634.          * @return the value of the option.
  635.          */
  636.         public String getValueString() {
  637.             return value.value.toString();
  638.         }

  639.         @Override
  640.         public String toString() {
  641.             return getLabel();
  642.         }

  643.         @Override
  644.         public String getElementName() {
  645.             return ELEMENT;
  646.         }

  647.         @Override
  648.         public String getNamespace() {
  649.             return NAMESPACE;
  650.         }

  651.         @Override
  652.         public QName getQName() {
  653.             return QNAME;
  654.         }

  655.         @Override
  656.         public XmlStringBuilder toXML(org.jivesoftware.smack.packet.XmlEnvironment enclosingNamespace) {
  657.             XmlStringBuilder xml = new XmlStringBuilder(this);
  658.             // Add attribute
  659.             xml.optAttribute("label", getLabel());
  660.             xml.rightAngleBracket();

  661.             // Add element
  662.             xml.element("value", getValueString());

  663.             xml.closeElement(this);
  664.             return xml;
  665.         }

  666.         @Override
  667.         public boolean equals(Object obj) {
  668.             return EqualsUtil.equals(this, obj, (e, o) -> {
  669.                 e.append(value, o.value)
  670.                  .append(label, o.label);
  671.             });
  672.         }

  673.         private final HashCode.Cache hashCodeCache = new HashCode.Cache();

  674.         @Override
  675.         public int hashCode() {
  676.             return hashCodeCache.getHashCode(c ->
  677.                 c.append(value)
  678.                  .append(label)
  679.             );
  680.         }

  681.     }

  682.     public static class Description extends StandardFormFieldChildElement {

  683.         public static final String ELEMENT = "desc";

  684.         public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

  685.         private final String description;

  686.         public Description(String description) {
  687.             this.description = description;
  688.         }

  689.         public String getDescription() {
  690.             return description;
  691.         }

  692.         @Override
  693.         public String getElementName() {
  694.             return ELEMENT;
  695.         }

  696.         @Override
  697.         public String getNamespace() {
  698.             return NAMESPACE;
  699.         }

  700.         @Override
  701.         public QName getQName() {
  702.             return QNAME;
  703.         }

  704.         @Override
  705.         public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
  706.             XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
  707.             xml.rightAngleBracket();
  708.             xml.escape(description);
  709.             xml.closeElement(this);
  710.             return xml;
  711.         }
  712.     }

  713.     public static final class Required extends StandardFormFieldChildElement {

  714.         public static final Required INSTANCE = new Required();

  715.         public static final String ELEMENT = "required";

  716.         public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

  717.         private Required() {
  718.         }

  719.         @Override
  720.         public String getElementName() {
  721.             return ELEMENT;
  722.         }

  723.         @Override
  724.         public String getNamespace() {
  725.             return NAMESPACE;
  726.         }

  727.         @Override
  728.         public QName getQName() {
  729.             return QNAME;
  730.         }

  731.         @Override
  732.         public boolean mustBeOnlyOfHisKind() {
  733.             return true;
  734.         }

  735.         @Override
  736.         public String toXML(XmlEnvironment xmlEnvironment) {
  737.             return '<' + ELEMENT + "/>";
  738.         }
  739.     }

  740.     public static class Value implements XmlElement {

  741.         public static final String ELEMENT = "value";

  742.         public static final QName QNAME = new QName(NAMESPACE, ELEMENT);

  743.         private final CharSequence value;

  744.         public Value(CharSequence value) {
  745.             this.value = value;
  746.         }

  747.         public CharSequence getValue() {
  748.             return value;
  749.         }

  750.         @Override
  751.         public String getElementName() {
  752.             return ELEMENT;
  753.         }

  754.         @Override
  755.         public String getNamespace() {
  756.             return NAMESPACE;
  757.         }

  758.         @Override
  759.         public QName getQName() {
  760.             return QNAME;
  761.         }

  762.         @Override
  763.         public XmlStringBuilder toXML(XmlEnvironment xmlEnvironment) {
  764.             XmlStringBuilder xml = new XmlStringBuilder(this, xmlEnvironment);
  765.             xml.rightAngleBracket();
  766.             xml.escape(value);
  767.             return xml.closeElement(this);
  768.         }

  769.         @Override
  770.         public boolean equals(Object other) {
  771.             return EqualsUtil.equals(this, other, (e, o) -> e.append(this.value, o.value));
  772.         }

  773.         @Override
  774.         public int hashCode() {
  775.             return value.hashCode();
  776.         }
  777.     }
  778. }