001/** 002 * 003 * Copyright 2003-2007 Jive Software. 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.Collections; 023import java.util.Date; 024import java.util.List; 025 026import org.jivesoftware.smack.packet.NamedElement; 027import org.jivesoftware.smack.util.StringUtils; 028import org.jivesoftware.smack.util.XmlStringBuilder; 029 030import org.jivesoftware.smackx.xdatavalidation.packet.ValidateElement; 031 032import org.jxmpp.util.XmppDateTime; 033 034/** 035 * Represents a field of a form. The field could be used to represent a question to complete, 036 * a completed question or a data returned from a search. The exact interpretation of the field 037 * depends on the context where the field is used. 038 * 039 * @author Gaston Dombiak 040 */ 041public class FormField implements NamedElement { 042 043 public static final String ELEMENT = "field"; 044 045 /** 046 * The constant String "FORM_TYPE". 047 */ 048 public static final String FORM_TYPE = "FORM_TYPE"; 049 050 /** 051 * Form Field Types as defined in XEP-4 § 3.3. 052 * 053 * @see <a href="http://xmpp.org/extensions/xep-0004.html#protocol-fieldtypes">XEP-4 § 3.3 Field Types</a> 054 */ 055 public enum Type { 056 057 /** 058 * Boolean type. Can be 0 or 1, true or false, yes or no. Default value is 0. 059 * <p> 060 * Note that in XEP-4 this type is called 'boolean', but since that String is a restricted keyword in Java, it 061 * is named 'bool' in Smack. 062 * </p> 063 */ 064 bool, 065 066 /** 067 * Fixed for putting in text to show sections, or just advertise your web site in the middle of the form. 068 */ 069 fixed, 070 071 /** 072 * Is not given to the user at all, but returned with the questionnaire. 073 */ 074 hidden, 075 076 /** 077 * multiple entries for JIDs. 078 */ 079 jid_multi, 080 081 /** 082 * Jabber ID - choosing a JID from your roster, and entering one based on the rules for a JID. 083 */ 084 jid_single, 085 086 /** 087 * Given a list of choices, pick one or more. 088 */ 089 list_multi, 090 091 /** 092 * Given a list of choices, pick one. 093 */ 094 list_single, 095 096 /** 097 * Multiple lines of text entry. 098 */ 099 text_multi, 100 101 /** 102 * Instead of showing the user what they typed, you show ***** to protect it. 103 */ 104 text_private, 105 106 /** 107 * Single line or word of text. 108 */ 109 text_single, 110 ; 111 112 @Override 113 public String toString() { 114 switch (this) { 115 case bool: 116 return "boolean"; 117 default: 118 return this.name().replace('_', '-'); 119 } 120 } 121 122 /** 123 * Get a form field type from the given string. If <code>string</code> is null, then null will be returned. 124 * 125 * @param string the string to transform or null. 126 * @return the type or null. 127 */ 128 public static Type fromString(String string) { 129 if (string == null) { 130 return null; 131 } 132 switch (string) { 133 case "boolean": 134 return bool; 135 default: 136 string = string.replace('-', '_'); 137 return Type.valueOf(string); 138 } 139 } 140 } 141 142 private final String variable; 143 144 private String description; 145 private boolean required = false; 146 private String label; 147 private Type type; 148 private final List<Option> options = new ArrayList<>(); 149 private final List<CharSequence> values = new ArrayList<>(); 150 private ValidateElement validateElement; 151 152 /** 153 * Creates a new FormField with the variable name that uniquely identifies the field 154 * in the context of the form. 155 * 156 * @param variable the variable name of the question. 157 */ 158 public FormField(String variable) { 159 this.variable = StringUtils.requireNotNullOrEmpty(variable, "Variable must not be null or empty"); 160 } 161 162 /** 163 * Creates a new FormField of type FIXED. The fields of type FIXED do not define a variable 164 * name. 165 */ 166 public FormField() { 167 this.variable = null; 168 this.type = Type.fixed; 169 } 170 171 /** 172 * Returns a description that provides extra clarification about the question. This information 173 * could be presented to the user either in tool-tip, help button, or as a section of text 174 * before the question. 175 * <p> 176 * If the question is of type FIXED then the description should remain empty. 177 * </p> 178 * 179 * @return description that provides extra clarification about the question. 180 */ 181 public String getDescription() { 182 return description; 183 } 184 185 /** 186 * Returns the label of the question which should give enough information to the user to 187 * fill out the form. 188 * 189 * @return label of the question. 190 */ 191 public String getLabel() { 192 return label; 193 } 194 195 /** 196 * Returns a List of the available options that the user has in order to answer 197 * the question. 198 * 199 * @return List of the available options. 200 */ 201 public List<Option> getOptions() { 202 synchronized (options) { 203 return Collections.unmodifiableList(new ArrayList<>(options)); 204 } 205 } 206 207 /** 208 * Returns true if the question must be answered in order to complete the questionnaire. 209 * 210 * @return true if the question must be answered in order to complete the questionnaire. 211 */ 212 public boolean isRequired() { 213 return required; 214 } 215 216 /** 217 * Returns an indicative of the format for the data to answer. 218 * 219 * @return format for the data to answer. 220 * @see Type 221 */ 222 public Type getType() { 223 return type; 224 } 225 226 /** 227 * Returns a List of the default values of the question if the question is part 228 * of a form to fill out. Otherwise, returns a List of the answered values of 229 * the question. 230 * 231 * @return a List of the default values or answered values of the question. 232 */ 233 public List<CharSequence> getValues() { 234 synchronized (values) { 235 return Collections.unmodifiableList(new ArrayList<>(values)); 236 } 237 } 238 239 /** 240 * Returns the values a String. Note that you should use {@link #getValues()} whenever possible instead of this 241 * method. 242 * 243 * @return a list of Strings representing the values 244 * @see #getValues() 245 * @since 4.3 246 */ 247 public List<String> getValuesAsString() { 248 List<CharSequence> valuesAsCharSequence = getValues(); 249 List<String> res = new ArrayList<>(valuesAsCharSequence.size()); 250 for (CharSequence value : valuesAsCharSequence) { 251 res.add(value.toString()); 252 } 253 return res; 254 } 255 256 /** 257 * Returns the first value of this form fold or {@code null}. 258 * 259 * @return the first value or {@code null} 260 * @since 4.3 261 */ 262 public String getFirstValue() { 263 CharSequence firstValue; 264 265 synchronized (values) { 266 if (values.isEmpty()) { 267 return null; 268 } 269 firstValue = values.get(0); 270 } 271 272 return firstValue.toString(); 273 } 274 275 /** 276 * Parses the first value of this form field as XEP-0082 date/time format and returns a date instance or {@code null}. 277 * 278 * @return a Date instance representing the date/time information of the first value of this field. 279 * @throws ParseException if parsing fails. 280 * @since 4.3.0 281 */ 282 public Date getFirstValueAsDate() throws ParseException { 283 String valueString = getFirstValue(); 284 if (valueString == null) { 285 return null; 286 } 287 return XmppDateTime.parseXEP0082Date(valueString); 288 } 289 290 /** 291 * Returns the variable name that the question is filling out. 292 * <p> 293 * According to XEP-4 § 3.2 the variable name (the 'var' attribute) 294 * "uniquely identifies the field in the context of the form" (if the field is not of type 'fixed', in which case 295 * the field "MAY possess a 'var' attribute") 296 * </p> 297 * 298 * @return the variable name of the question. 299 */ 300 public String getVariable() { 301 return variable; 302 } 303 304 /** 305 * Get validate element. 306 * 307 * @return the validateElement 308 */ 309 public ValidateElement getValidateElement() { 310 return validateElement; 311 } 312 313 /** 314 * Sets a description that provides extra clarification about the question. This information 315 * could be presented to the user either in tool-tip, help button, or as a section of text 316 * before the question. 317 * <p> 318 * If the question is of type FIXED then the description should remain empty. 319 * </p> 320 * 321 * @param description provides extra clarification about the question. 322 */ 323 public void setDescription(String description) { 324 this.description = description; 325 } 326 327 /** 328 * Sets the label of the question which should give enough information to the user to 329 * fill out the form. 330 * 331 * @param label the label of the question. 332 */ 333 public void setLabel(String label) { 334 this.label = label; 335 } 336 337 /** 338 * Sets if the question must be answered in order to complete the questionnaire. 339 * 340 * @param required if the question must be answered in order to complete the questionnaire. 341 */ 342 public void setRequired(boolean required) { 343 this.required = required; 344 } 345 346 /** 347 * Set validate element. 348 * @param validateElement the validateElement to set 349 */ 350 public void setValidateElement(ValidateElement validateElement) { 351 validateElement.checkConsistency(this); 352 this.validateElement = validateElement; 353 } 354 355 /** 356 * Sets an indicative of the format for the data to answer. 357 * <p> 358 * This method will throw an IllegalArgumentException if type is 'fixed'. To create FormFields of type 'fixed' use 359 * {@link #FormField()} instead. 360 * </p> 361 * 362 * @param type an indicative of the format for the data to answer. 363 * @see Type 364 * @throws IllegalArgumentException if type is 'fixed'. 365 */ 366 public void setType(Type type) { 367 if (type == Type.fixed) { 368 throw new IllegalArgumentException("Can not set type to fixed, use FormField constructor without arguments instead."); 369 } 370 this.type = type; 371 } 372 373 /** 374 * Adds a default value to the question if the question is part of a form to fill out. 375 * Otherwise, adds an answered value to the question. 376 * 377 * @param value a default value or an answered value of the question. 378 */ 379 public void addValue(CharSequence value) { 380 synchronized (values) { 381 values.add(value); 382 } 383 } 384 385 /** 386 * Adds the given Date as XEP-0082 formated string by invoking {@link #addValue(CharSequence)} after the date 387 * instance was formated. 388 * 389 * @param date the date instance to add as XEP-0082 formated string. 390 * @since 4.3.0 391 */ 392 public void addValue(Date date) { 393 String dateString = XmppDateTime.formatXEP0082Date(date); 394 addValue(dateString); 395 } 396 397 /** 398 * Adds a default values to the question if the question is part of a form to fill out. 399 * Otherwise, adds an answered values to the question. 400 * 401 * @param newValues default values or an answered values of the question. 402 */ 403 public void addValues(List<? extends CharSequence> newValues) { 404 synchronized (values) { 405 values.addAll(newValues); 406 } 407 } 408 409 /** 410 * Removes all the values of the field. 411 */ 412 protected void resetValues() { 413 synchronized (values) { 414 values.clear(); 415 } 416 } 417 418 /** 419 * Adss an available options to the question that the user has in order to answer 420 * the question. 421 * 422 * @param option a new available option for the question. 423 */ 424 public void addOption(Option option) { 425 synchronized (options) { 426 options.add(option); 427 } 428 } 429 430 @Override 431 public String getElementName() { 432 return ELEMENT; 433 } 434 435 @Override 436 public XmlStringBuilder toXML(String enclosingNamespace) { 437 XmlStringBuilder buf = new XmlStringBuilder(this); 438 // Add attributes 439 buf.optAttribute("label", getLabel()); 440 buf.optAttribute("var", getVariable()); 441 buf.optAttribute("type", getType()); 442 buf.rightAngleBracket(); 443 // Add elements 444 buf.optElement("desc", getDescription()); 445 buf.condEmptyElement(isRequired(), "required"); 446 // Loop through all the values and append them to the string buffer 447 for (CharSequence value : getValues()) { 448 buf.element("value", value); 449 } 450 // Loop through all the values and append them to the string buffer 451 for (Option option : getOptions()) { 452 buf.append(option.toXML(null)); 453 } 454 buf.optElement(validateElement); 455 buf.closeElement(this); 456 return buf; 457 } 458 459 @Override 460 public boolean equals(Object obj) { 461 if (obj == null) 462 return false; 463 if (obj == this) 464 return true; 465 if (!(obj instanceof FormField)) 466 return false; 467 468 FormField other = (FormField) obj; 469 470 return toXML(null).equals(other.toXML(null)); 471 } 472 473 @Override 474 public int hashCode() { 475 return toXML(null).hashCode(); 476 } 477 478 /** 479 * Represents the available option of a given FormField. 480 * 481 * @author Gaston Dombiak 482 */ 483 public static class Option implements NamedElement { 484 485 public static final String ELEMENT = "option"; 486 487 private final String value; 488 private String label; 489 490 public Option(String value) { 491 this.value = value; 492 } 493 494 public Option(String label, String value) { 495 this.label = label; 496 this.value = value; 497 } 498 499 /** 500 * Returns the label that represents the option. 501 * 502 * @return the label that represents the option. 503 */ 504 public String getLabel() { 505 return label; 506 } 507 508 /** 509 * Returns the value of the option. 510 * 511 * @return the value of the option. 512 */ 513 public String getValue() { 514 return value; 515 } 516 517 @Override 518 public String toString() { 519 return getLabel(); 520 } 521 522 @Override 523 public String getElementName() { 524 return ELEMENT; 525 } 526 527 @Override 528 public XmlStringBuilder toXML(String enclosingNamespace) { 529 XmlStringBuilder xml = new XmlStringBuilder(this); 530 // Add attribute 531 xml.optAttribute("label", getLabel()); 532 xml.rightAngleBracket(); 533 534 // Add element 535 xml.element("value", getValue()); 536 537 xml.closeElement(this); 538 return xml; 539 } 540 541 @Override 542 public boolean equals(Object obj) { 543 if (obj == null) 544 return false; 545 if (obj == this) 546 return true; 547 if (obj.getClass() != getClass()) 548 return false; 549 550 Option other = (Option) obj; 551 552 if (!value.equals(other.value)) 553 return false; 554 555 String thisLabel = label == null ? "" : label; 556 String otherLabel = other.label == null ? "" : other.label; 557 558 if (!thisLabel.equals(otherLabel)) 559 return false; 560 561 return true; 562 } 563 564 @Override 565 public int hashCode() { 566 int result = 1; 567 result = 37 * result + value.hashCode(); 568 result = 37 * result + (label == null ? 0 : label.hashCode()); 569 return result; 570 } 571 } 572}