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