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 <field/>. 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}