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