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