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