1 /** 2 * $RCSfile$ 3 * $Revision$ 4 * $Date$ 5 * 6 * Copyright 2003-2007 Jive Software. 7 * 8 * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); 9 * you may not use this file except in compliance with the License. 10 * You may obtain a copy of the License at 11 * 12 * http://www.apache.org/licenses/LICENSE-2.0 13 * 14 * Unless required by applicable law or agreed to in writing, software 15 * distributed under the License is distributed on an "AS IS" BASIS, 16 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21 package org.jivesoftware.smackx; 22 23 import java.util.ArrayList; 24 import java.util.Iterator; 25 import java.util.List; 26 import java.util.StringTokenizer; 27 28 import org.jivesoftware.smack.packet.Packet; 29 import org.jivesoftware.smack.packet.PacketExtension; 30 import org.jivesoftware.smackx.packet.DataForm; 31 32 /** 33 * Represents a Form for gathering data. The form could be of the following types: 34 * <ul> 35 * <li>form -> Indicates a form to fill out.</li> 36 * <li>submit -> The form is filled out, and this is the data that is being returned from 37 * the form.</li> 38 * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li> 39 * <li>result -> Data results being returned from a search, or some other query.</li> 40 * </ul> 41 * 42 * Depending of the form's type different operations are available. For example, it's only possible 43 * to set answers if the form is of type "submit". 44 * 45 * @see <a href="http://xmpp.org/extensions/xep-0004.html">XEP-0004 Data Forms</a> 46 * 47 * @author Gaston Dombiak 48 */ 49 public class Form { 50 51 public static final String TYPE_FORM = "form"; 52 public static final String TYPE_SUBMIT = "submit"; 53 public static final String TYPE_CANCEL = "cancel"; 54 public static final String TYPE_RESULT = "result"; 55 56 public static final String NAMESPACE = "jabber:x:data"; 57 public static final String ELEMENT = "x"; 58 59 private DataForm dataForm; 60 61 /** 62 * Returns a new ReportedData if the packet is used for gathering data and includes an 63 * extension that matches the elementName and namespace "x","jabber:x:data". 64 * 65 * @param packet the packet used for gathering data. 66 * @return the data form parsed from the packet or <tt>null</tt> if there was not 67 * a form in the packet. 68 */ 69 public static Form getFormFrom(Packet packet) { 70 // Check if the packet includes the DataForm extension 71 PacketExtension packetExtension = packet.getExtension("x","jabber:x:data"); 72 if (packetExtension != null) { 73 // Check if the existing DataForm is not a result of a search 74 DataForm dataForm = (DataForm) packetExtension; 75 if (dataForm.getReportedData() == null) 76 return new Form(dataForm); 77 } 78 // Otherwise return null 79 return null; 80 } 81 82 /** 83 * Creates a new Form that will wrap an existing DataForm. The wrapped DataForm must be 84 * used for gathering data. 85 * 86 * @param dataForm the data form used for gathering data. 87 */ 88 public Form(DataForm dataForm) { 89 this.dataForm = dataForm; 90 } 91 92 /** 93 * Creates a new Form of a given type from scratch.<p> 94 * 95 * Possible form types are: 96 * <ul> 97 * <li>form -> Indicates a form to fill out.</li> 98 * <li>submit -> The form is filled out, and this is the data that is being returned from 99 * the form.</li> 100 * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li> 101 * <li>result -> Data results being returned from a search, or some other query.</li> 102 * </ul> 103 * 104 * @param type the form's type (e.g. form, submit,cancel,result). 105 */ 106 public Form(String type) { 107 this.dataForm = new DataForm(type); 108 } 109 110 /** 111 * Adds a new field to complete as part of the form. 112 * 113 * @param field the field to complete. 114 */ 115 public void addField(FormField field) { 116 dataForm.addField(field); 117 } 118 119 /** 120 * Sets a new String value to a given form's field. The field whose variable matches the 121 * requested variable will be completed with the specified value. If no field could be found 122 * for the specified variable then an exception will be raised.<p> 123 * 124 * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you 125 * can use this message where the String value is the String representation of the object. 126 * 127 * @param variable the variable name that was completed. 128 * @param value the String value that was answered. 129 * @throws IllegalStateException if the form is not of type "submit". 130 * @throws IllegalArgumentException if the form does not include the specified variable or 131 * if the answer type does not correspond with the field type.. 132 */ 133 public void setAnswer(String variable, String value) { 134 FormField field = getField(variable); 135 if (field == null) { 136 throw new IllegalArgumentException("Field not found for the specified variable name."); 137 } 138 if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) 139 && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) 140 && !FormField.TYPE_TEXT_SINGLE.equals(field.getType()) 141 && !FormField.TYPE_JID_SINGLE.equals(field.getType()) 142 && !FormField.TYPE_HIDDEN.equals(field.getType())) { 143 throw new IllegalArgumentException("This field is not of type String."); 144 } 145 setAnswer(field, value); 146 } 147 148 /** 149 * Sets a new int value to a given form's field. The field whose variable matches the 150 * requested variable will be completed with the specified value. If no field could be found 151 * for the specified variable then an exception will be raised. 152 * 153 * @param variable the variable name that was completed. 154 * @param value the int value that was answered. 155 * @throws IllegalStateException if the form is not of type "submit". 156 * @throws IllegalArgumentException if the form does not include the specified variable or 157 * if the answer type does not correspond with the field type. 158 */ 159 public void setAnswer(String variable, int value) { 160 FormField field = getField(variable); 161 if (field == null) { 162 throw new IllegalArgumentException("Field not found for the specified variable name."); 163 } 164 if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) 165 && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) 166 && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { 167 throw new IllegalArgumentException("This field is not of type int."); 168 } 169 setAnswer(field, value); 170 } 171 172 /** 173 * Sets a new long value to a given form's field. The field whose variable matches the 174 * requested variable will be completed with the specified value. If no field could be found 175 * for the specified variable then an exception will be raised. 176 * 177 * @param variable the variable name that was completed. 178 * @param value the long value that was answered. 179 * @throws IllegalStateException if the form is not of type "submit". 180 * @throws IllegalArgumentException if the form does not include the specified variable or 181 * if the answer type does not correspond with the field type. 182 */ 183 public void setAnswer(String variable, long value) { 184 FormField field = getField(variable); 185 if (field == null) { 186 throw new IllegalArgumentException("Field not found for the specified variable name."); 187 } 188 if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) 189 && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) 190 && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { 191 throw new IllegalArgumentException("This field is not of type long."); 192 } 193 setAnswer(field, value); 194 } 195 196 /** 197 * Sets a new float value to a given form's field. The field whose variable matches the 198 * requested variable will be completed with the specified value. If no field could be found 199 * for the specified variable then an exception will be raised. 200 * 201 * @param variable the variable name that was completed. 202 * @param value the float value that was answered. 203 * @throws IllegalStateException if the form is not of type "submit". 204 * @throws IllegalArgumentException if the form does not include the specified variable or 205 * if the answer type does not correspond with the field type. 206 */ 207 public void setAnswer(String variable, float value) { 208 FormField field = getField(variable); 209 if (field == null) { 210 throw new IllegalArgumentException("Field not found for the specified variable name."); 211 } 212 if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) 213 && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) 214 && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { 215 throw new IllegalArgumentException("This field is not of type float."); 216 } 217 setAnswer(field, value); 218 } 219 220 /** 221 * Sets a new double value to a given form's field. The field whose variable matches the 222 * requested variable will be completed with the specified value. If no field could be found 223 * for the specified variable then an exception will be raised. 224 * 225 * @param variable the variable name that was completed. 226 * @param value the double value that was answered. 227 * @throws IllegalStateException if the form is not of type "submit". 228 * @throws IllegalArgumentException if the form does not include the specified variable or 229 * if the answer type does not correspond with the field type. 230 */ 231 public void setAnswer(String variable, double value) { 232 FormField field = getField(variable); 233 if (field == null) { 234 throw new IllegalArgumentException("Field not found for the specified variable name."); 235 } 236 if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) 237 && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) 238 && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { 239 throw new IllegalArgumentException("This field is not of type double."); 240 } 241 setAnswer(field, value); 242 } 243 244 /** 245 * Sets a new boolean value to a given form's field. The field whose variable matches the 246 * requested variable will be completed with the specified value. If no field could be found 247 * for the specified variable then an exception will be raised. 248 * 249 * @param variable the variable name that was completed. 250 * @param value the boolean value that was answered. 251 * @throws IllegalStateException if the form is not of type "submit". 252 * @throws IllegalArgumentException if the form does not include the specified variable or 253 * if the answer type does not correspond with the field type. 254 */ 255 public void setAnswer(String variable, boolean value) { 256 FormField field = getField(variable); 257 if (field == null) { 258 throw new IllegalArgumentException("Field not found for the specified variable name."); 259 } 260 if (!FormField.TYPE_BOOLEAN.equals(field.getType())) { 261 throw new IllegalArgumentException("This field is not of type boolean."); 262 } 263 setAnswer(field, (value ? "1" : "0")); 264 } 265 266 /** 267 * Sets a new Object value to a given form's field. In fact, the object representation 268 * (i.e. #toString) will be the actual value of the field.<p> 269 * 270 * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you 271 * will need to use {@link #setAnswer(String, String))} where the String value is the 272 * String representation of the object.<p> 273 * 274 * Before setting the new value to the field we will check if the form is of type submit. If 275 * the form isn't of type submit means that it's not possible to complete the form and an 276 * exception will be thrown. 277 * 278 * @param field the form field that was completed. 279 * @param value the Object value that was answered. The object representation will be the 280 * actual value. 281 * @throws IllegalStateException if the form is not of type "submit". 282 */ 283 private void setAnswer(FormField field, Object value) { 284 if (!isSubmitType()) { 285 throw new IllegalStateException("Cannot set an answer if the form is not of type " + 286 "\"submit\""); 287 } 288 field.resetValues(); 289 field.addValue(value.toString()); 290 } 291 292 /** 293 * Sets a new values to a given form's field. The field whose variable matches the requested 294 * variable will be completed with the specified values. If no field could be found for 295 * the specified variable then an exception will be raised.<p> 296 * 297 * The Objects contained in the List could be of any type. The String representation of them 298 * (i.e. #toString) will be actually used when sending the answer to the server. 299 * 300 * @param variable the variable that was completed. 301 * @param values the values that were answered. 302 * @throws IllegalStateException if the form is not of type "submit". 303 * @throws IllegalArgumentException if the form does not include the specified variable. 304 */ 305 public void setAnswer(String variable, List<String> values) { 306 if (!isSubmitType()) { 307 throw new IllegalStateException("Cannot set an answer if the form is not of type " + 308 "\"submit\""); 309 } 310 FormField field = getField(variable); 311 if (field != null) { 312 // Check that the field can accept a collection of values 313 if (!FormField.TYPE_JID_MULTI.equals(field.getType()) 314 && !FormField.TYPE_LIST_MULTI.equals(field.getType()) 315 && !FormField.TYPE_LIST_SINGLE.equals(field.getType()) 316 && !FormField.TYPE_TEXT_MULTI.equals(field.getType()) 317 && !FormField.TYPE_HIDDEN.equals(field.getType())) { 318 throw new IllegalArgumentException("This field only accept list of values."); 319 } 320 // Clear the old values 321 field.resetValues(); 322 // Set the new values. The string representation of each value will be actually used. 323 field.addValues(values); 324 } 325 else { 326 throw new IllegalArgumentException("Couldn't find a field for the specified variable."); 327 } 328 } 329 330 /** 331 * Sets the default value as the value of a given form's field. The field whose variable matches 332 * the requested variable will be completed with its default value. If no field could be found 333 * for the specified variable then an exception will be raised. 334 * 335 * @param variable the variable to complete with its default value. 336 * @throws IllegalStateException if the form is not of type "submit". 337 * @throws IllegalArgumentException if the form does not include the specified variable. 338 */ 339 public void setDefaultAnswer(String variable) { 340 if (!isSubmitType()) { 341 throw new IllegalStateException("Cannot set an answer if the form is not of type " + 342 "\"submit\""); 343 } 344 FormField field = getField(variable); 345 if (field != null) { 346 // Clear the old values 347 field.resetValues(); 348 // Set the default value 349 for (Iterator<String> it = field.getValues(); it.hasNext();) { 350 field.addValue(it.next()); 351 } 352 } 353 else { 354 throw new IllegalArgumentException("Couldn't find a field for the specified variable."); 355 } 356 } 357 358 /** 359 * Returns an Iterator for the fields that are part of the form. 360 * 361 * @return an Iterator for the fields that are part of the form. 362 */ 363 public Iterator<FormField> getFields() { 364 return dataForm.getFields(); 365 } 366 367 /** 368 * Returns the field of the form whose variable matches the specified variable. 369 * The fields of type FIXED will never be returned since they do not specify a 370 * variable. 371 * 372 * @param variable the variable to look for in the form fields. 373 * @return the field of the form whose variable matches the specified variable. 374 */ 375 public FormField getField(String variable) { 376 if (variable == null || variable.equals("")) { 377 throw new IllegalArgumentException("Variable must not be null or blank."); 378 } 379 // Look for the field whose variable matches the requested variable 380 FormField field; 381 for (Iterator<FormField> it=getFields();it.hasNext();) { 382 field = it.next(); 383 if (variable.equals(field.getVariable())) { 384 return field; 385 } 386 } 387 return null; 388 } 389 390 /** 391 * Returns the instructions that explain how to fill out the form and what the form is about. 392 * 393 * @return instructions that explain how to fill out the form. 394 */ 395 public String getInstructions() { 396 StringBuilder sb = new StringBuilder(); 397 // Join the list of instructions together separated by newlines 398 for (Iterator<String> it = dataForm.getInstructions(); it.hasNext();) { 399 sb.append(it.next()); 400 // If this is not the last instruction then append a newline 401 if (it.hasNext()) { 402 sb.append("\n"); 403 } 404 } 405 return sb.toString(); 406 } 407 408 409 /** 410 * Returns the description of the data. It is similar to the title on a web page or an X 411 * window. You can put a <title/> on either a form to fill out, or a set of data results. 412 * 413 * @return description of the data. 414 */ 415 public String getTitle() { 416 return dataForm.getTitle(); 417 } 418 419 420 /** 421 * Returns the meaning of the data within the context. The data could be part of a form 422 * to fill out, a form submission or data results.<p> 423 * 424 * Possible form types are: 425 * <ul> 426 * <li>form -> Indicates a form to fill out.</li> 427 * <li>submit -> The form is filled out, and this is the data that is being returned from 428 * the form.</li> 429 * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li> 430 * <li>result -> Data results being returned from a search, or some other query.</li> 431 * </ul> 432 * 433 * @return the form's type. 434 */ 435 public String getType() { 436 return dataForm.getType(); 437 } 438 439 440 /** 441 * Sets instructions that explain how to fill out the form and what the form is about. 442 * 443 * @param instructions instructions that explain how to fill out the form. 444 */ 445 public void setInstructions(String instructions) { 446 // Split the instructions into multiple instructions for each existent newline 447 ArrayList<String> instructionsList = new ArrayList<String>(); 448 StringTokenizer st = new StringTokenizer(instructions, "\n"); 449 while (st.hasMoreTokens()) { 450 instructionsList.add(st.nextToken()); 451 } 452 // Set the new list of instructions 453 dataForm.setInstructions(instructionsList); 454 455 } 456 457 458 /** 459 * Sets the description of the data. It is similar to the title on a web page or an X window. 460 * You can put a <title/> on either a form to fill out, or a set of data results. 461 * 462 * @param title description of the data. 463 */ 464 public void setTitle(String title) { 465 dataForm.setTitle(title); 466 } 467 468 /** 469 * Returns a DataForm that serves to send this Form to the server. If the form is of type 470 * submit, it may contain fields with no value. These fields will be removed since they only 471 * exist to assist the user while editing/completing the form in a UI. 472 * 473 * @return the wrapped DataForm. 474 */ 475 public DataForm getDataFormToSend() { 476 if (isSubmitType()) { 477 // Create a new DataForm that contains only the answered fields 478 DataForm dataFormToSend = new DataForm(getType()); 479 for(Iterator<FormField> it=getFields();it.hasNext();) { 480 FormField field = it.next(); 481 if (field.getValues().hasNext()) { 482 dataFormToSend.addField(field); 483 } 484 } 485 return dataFormToSend; 486 } 487 return dataForm; 488 } 489 490 /** 491 * Returns true if the form is a form to fill out. 492 * 493 * @return if the form is a form to fill out. 494 */ 495 private boolean isFormType() { 496 return TYPE_FORM.equals(dataForm.getType()); 497 } 498 499 /** 500 * Returns true if the form is a form to submit. 501 * 502 * @return if the form is a form to submit. 503 */ 504 private boolean isSubmitType() { 505 return TYPE_SUBMIT.equals(dataForm.getType()); 506 } 507 508 /** 509 * Returns a new Form to submit the completed values. The new Form will include all the fields 510 * of the original form except for the fields of type FIXED. Only the HIDDEN fields will 511 * include the same value of the original form. The other fields of the new form MUST be 512 * completed. If a field remains with no answer when sending the completed form, then it won't 513 * be included as part of the completed form.<p> 514 * 515 * The reason why the fields with variables are included in the new form is to provide a model 516 * for binding with any UI. This means that the UIs will use the original form (of type 517 * "form") to learn how to render the form, but the UIs will bind the fields to the form of 518 * type submit. 519 * 520 * @return a Form to submit the completed values. 521 */ 522 public Form createAnswerForm() { 523 if (!isFormType()) { 524 throw new IllegalStateException("Only forms of type \"form\" could be answered"); 525 } 526 // Create a new Form 527 Form form = new Form(TYPE_SUBMIT); 528 for (Iterator<FormField> fields=getFields(); fields.hasNext();) { 529 FormField field = fields.next(); 530 // Add to the new form any type of field that includes a variable. 531 // Note: The fields of type FIXED are the only ones that don't specify a variable 532 if (field.getVariable() != null) { 533 FormField newField = new FormField(field.getVariable()); 534 newField.setType(field.getType()); 535 form.addField(newField); 536 // Set the answer ONLY to the hidden fields 537 if (FormField.TYPE_HIDDEN.equals(field.getType())) { 538 // Since a hidden field could have many values we need to collect them 539 // in a list 540 List<String> values = new ArrayList<String>(); 541 for (Iterator<String> it=field.getValues();it.hasNext();) { 542 values.add(it.next()); 543 } 544 form.setAnswer(field.getVariable(), values); 545 } 546 } 547 } 548 return form; 549 } 550 551 } 552