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