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 synchronized (values) { 265 firstValue = values.get(0); 266 } 267 if (firstValue == null) { 268 return null; 269 } 270 return firstValue.toString(); 271 } 272 273 /** 274 * Parses the first value of this form field as XEP-0082 date/time format and returns a date instance or {@code null}. 275 * 276 * @return a Date instance representing the date/time information of the first value of this field. 277 * @throws ParseException if parsing fails. 278 * @since 4.3.0 279 */ 280 public Date getFirstValueAsDate() throws ParseException { 281 String valueString = getFirstValue(); 282 if (valueString == null) { 283 return null; 284 } 285 return XmppDateTime.parseXEP0082Date(valueString); 286 } 287 288 /** 289 * Returns the variable name that the question is filling out. 290 * <p> 291 * According to XEP-4 § 3.2 the variable name (the 'var' attribute) 292 * "uniquely identifies the field in the context of the form" (if the field is not of type 'fixed', in which case 293 * the field "MAY possess a 'var' attribute") 294 * </p> 295 * 296 * @return the variable name of the question. 297 */ 298 public String getVariable() { 299 return variable; 300 } 301 302 /** 303 * Get validate element. 304 * 305 * @return the validateElement 306 */ 307 public ValidateElement getValidateElement() { 308 return validateElement; 309 } 310 311 /** 312 * Sets a description that provides extra clarification about the question. This information 313 * could be presented to the user either in tool-tip, help button, or as a section of text 314 * before the question. 315 * <p> 316 * If the question is of type FIXED then the description should remain empty. 317 * </p> 318 * 319 * @param description provides extra clarification about the question. 320 */ 321 public void setDescription(String description) { 322 this.description = description; 323 } 324 325 /** 326 * Sets the label of the question which should give enough information to the user to 327 * fill out the form. 328 * 329 * @param label the label of the question. 330 */ 331 public void setLabel(String label) { 332 this.label = label; 333 } 334 335 /** 336 * Sets if the question must be answered in order to complete the questionnaire. 337 * 338 * @param required if the question must be answered in order to complete the questionnaire. 339 */ 340 public void setRequired(boolean required) { 341 this.required = required; 342 } 343 344 /** 345 * Set validate element. 346 * @param validateElement the validateElement to set 347 */ 348 public void setValidateElement(ValidateElement validateElement) { 349 validateElement.checkConsistency(this); 350 this.validateElement = validateElement; 351 } 352 353 /** 354 * Sets an indicative of the format for the data to answer. 355 * <p> 356 * This method will throw an IllegalArgumentException if type is 'fixed'. To create FormFields of type 'fixed' use 357 * {@link #FormField()} instead. 358 * </p> 359 * 360 * @param type an indicative of the format for the data to answer. 361 * @see Type 362 * @throws IllegalArgumentException if type is 'fixed'. 363 */ 364 public void setType(Type type) { 365 if (type == Type.fixed) { 366 throw new IllegalArgumentException("Can not set type to fixed, use FormField constructor without arguments instead."); 367 } 368 this.type = type; 369 } 370 371 /** 372 * Adds a default value to the question if the question is part of a form to fill out. 373 * Otherwise, adds an answered value to the question. 374 * 375 * @param value a default value or an answered value of the question. 376 */ 377 public void addValue(CharSequence value) { 378 synchronized (values) { 379 values.add(value); 380 } 381 } 382 383 /** 384 * Adds the given Date as XEP-0082 formated string by invoking {@link #addValue(CharSequence)} after the date 385 * instance was formated. 386 * 387 * @param date the date instance to add as XEP-0082 formated string. 388 * @since 4.3.0 389 */ 390 public void addValue(Date date) { 391 String dateString = XmppDateTime.formatXEP0082Date(date); 392 addValue(dateString); 393 } 394 395 /** 396 * Adds a default values to the question if the question is part of a form to fill out. 397 * Otherwise, adds an answered values to the question. 398 * 399 * @param newValues default values or an answered values of the question. 400 */ 401 public void addValues(List<? extends CharSequence> newValues) { 402 synchronized (values) { 403 values.addAll(newValues); 404 } 405 } 406 407 /** 408 * Removes all the values of the field. 409 */ 410 protected void resetValues() { 411 synchronized (values) { 412 values.clear(); 413 } 414 } 415 416 /** 417 * Adss an available options to the question that the user has in order to answer 418 * the question. 419 * 420 * @param option a new available option for the question. 421 */ 422 public void addOption(Option option) { 423 synchronized (options) { 424 options.add(option); 425 } 426 } 427 428 @Override 429 public String getElementName() { 430 return ELEMENT; 431 } 432 433 @Override 434 public XmlStringBuilder toXML(String enclosingNamespace) { 435 XmlStringBuilder buf = new XmlStringBuilder(this); 436 // Add attributes 437 buf.optAttribute("label", getLabel()); 438 buf.optAttribute("var", getVariable()); 439 buf.optAttribute("type", getType()); 440 buf.rightAngleBracket(); 441 // Add elements 442 buf.optElement("desc", getDescription()); 443 buf.condEmptyElement(isRequired(), "required"); 444 // Loop through all the values and append them to the string buffer 445 for (CharSequence value : getValues()) { 446 buf.element("value", value); 447 } 448 // Loop through all the values and append them to the string buffer 449 for (Option option : getOptions()) { 450 buf.append(option.toXML(null)); 451 } 452 buf.optElement(validateElement); 453 buf.closeElement(this); 454 return buf; 455 } 456 457 @Override 458 public boolean equals(Object obj) { 459 if (obj == null) 460 return false; 461 if (obj == this) 462 return true; 463 if (!(obj instanceof FormField)) 464 return false; 465 466 FormField other = (FormField) obj; 467 468 return toXML(null).equals(other.toXML(null)); 469 } 470 471 @Override 472 public int hashCode() { 473 return toXML(null).hashCode(); 474 } 475 476 /** 477 * Represents the available option of a given FormField. 478 * 479 * @author Gaston Dombiak 480 */ 481 public static class Option implements NamedElement { 482 483 public static final String ELEMENT = "option"; 484 485 private final String value; 486 private String label; 487 488 public Option(String value) { 489 this.value = value; 490 } 491 492 public Option(String label, String value) { 493 this.label = label; 494 this.value = value; 495 } 496 497 /** 498 * Returns the label that represents the option. 499 * 500 * @return the label that represents the option. 501 */ 502 public String getLabel() { 503 return label; 504 } 505 506 /** 507 * Returns the value of the option. 508 * 509 * @return the value of the option. 510 */ 511 public String getValue() { 512 return value; 513 } 514 515 @Override 516 public String toString() { 517 return getLabel(); 518 } 519 520 @Override 521 public String getElementName() { 522 return ELEMENT; 523 } 524 525 @Override 526 public XmlStringBuilder toXML(String enclosingNamespace) { 527 XmlStringBuilder xml = new XmlStringBuilder(this); 528 // Add attribute 529 xml.optAttribute("label", getLabel()); 530 xml.rightAngleBracket(); 531 532 // Add element 533 xml.element("value", getValue()); 534 535 xml.closeElement(this); 536 return xml; 537 } 538 539 @Override 540 public boolean equals(Object obj) { 541 if (obj == null) 542 return false; 543 if (obj == this) 544 return true; 545 if (obj.getClass() != getClass()) 546 return false; 547 548 Option other = (Option) obj; 549 550 if (!value.equals(other.value)) 551 return false; 552 553 String thisLabel = label == null ? "" : label; 554 String otherLabel = other.label == null ? "" : other.label; 555 556 if (!thisLabel.equals(otherLabel)) 557 return false; 558 559 return true; 560 } 561 562 @Override 563 public int hashCode() { 564 int result = 1; 565 result = 37 * result + value.hashCode(); 566 result = 37 * result + (label == null ? 0 : label.hashCode()); 567 return result; 568 } 569 } 570}