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 */ 017package org.jivesoftware.smackx.xdata; 018 019import java.util.ArrayList; 020import java.util.Iterator; 021import java.util.List; 022import java.util.StringTokenizer; 023 024import org.jivesoftware.smack.packet.Packet; 025import org.jivesoftware.smack.packet.PacketExtension; 026import org.jivesoftware.smackx.xdata.packet.DataForm; 027 028/** 029 * Represents a Form for gathering data. The form could be of the following types: 030 * <ul> 031 * <li>form -> Indicates a form to fill out.</li> 032 * <li>submit -> The form is filled out, and this is the data that is being returned from 033 * the form.</li> 034 * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li> 035 * <li>result -> Data results being returned from a search, or some other query.</li> 036 * </ul> 037 * 038 * Depending of the form's type different operations are available. For example, it's only possible 039 * to set answers if the form is of type "submit". 040 * 041 * @see <a href="http://xmpp.org/extensions/xep-0004.html">XEP-0004 Data Forms</a> 042 * 043 * @author Gaston Dombiak 044 */ 045public class Form { 046 047 public static final String TYPE_FORM = "form"; 048 public static final String TYPE_SUBMIT = "submit"; 049 public static final String TYPE_CANCEL = "cancel"; 050 public static final String TYPE_RESULT = "result"; 051 052 private DataForm dataForm; 053 054 /** 055 * Returns a new ReportedData if the packet is used for gathering data and includes an 056 * extension that matches the elementName and namespace "x","jabber:x:data". 057 * 058 * @param packet the packet used for gathering data. 059 * @return the data form parsed from the packet or <tt>null</tt> if there was not 060 * a form in the packet. 061 */ 062 public static Form getFormFrom(Packet packet) { 063 // Check if the packet includes the DataForm extension 064 PacketExtension packetExtension = packet.getExtension("x","jabber:x:data"); 065 if (packetExtension != null) { 066 // Check if the existing DataForm is not a result of a search 067 DataForm dataForm = (DataForm) packetExtension; 068 if (dataForm.getReportedData() == null) 069 return new Form(dataForm); 070 } 071 // Otherwise return null 072 return null; 073 } 074 075 /** 076 * Creates a new Form that will wrap an existing DataForm. The wrapped DataForm must be 077 * used for gathering data. 078 * 079 * @param dataForm the data form used for gathering data. 080 */ 081 public Form(DataForm dataForm) { 082 this.dataForm = dataForm; 083 } 084 085 /** 086 * Creates a new Form of a given type from scratch.<p> 087 * 088 * Possible form types are: 089 * <ul> 090 * <li>form -> Indicates a form to fill out.</li> 091 * <li>submit -> The form is filled out, and this is the data that is being returned from 092 * the form.</li> 093 * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li> 094 * <li>result -> Data results being returned from a search, or some other query.</li> 095 * </ul> 096 * 097 * @param type the form's type (e.g. form, submit,cancel,result). 098 */ 099 public Form(String type) { 100 this.dataForm = new DataForm(type); 101 } 102 103 /** 104 * Adds a new field to complete as part of the form. 105 * 106 * @param field the field to complete. 107 */ 108 public void addField(FormField field) { 109 dataForm.addField(field); 110 } 111 112 /** 113 * Sets a new String value to a given form's field. The field whose variable matches the 114 * requested variable will be completed with the specified value. If no field could be found 115 * for the specified variable then an exception will be raised.<p> 116 * 117 * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you 118 * can use this message where the String value is the String representation of the object. 119 * 120 * @param variable the variable name that was completed. 121 * @param value the String value that was answered. 122 * @throws IllegalStateException if the form is not of type "submit". 123 * @throws IllegalArgumentException if the form does not include the specified variable or 124 * if the answer type does not correspond with the field type.. 125 */ 126 public void setAnswer(String variable, String value) { 127 FormField field = getField(variable); 128 if (field == null) { 129 throw new IllegalArgumentException("Field not found for the specified variable name."); 130 } 131 if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) 132 && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) 133 && !FormField.TYPE_TEXT_SINGLE.equals(field.getType()) 134 && !FormField.TYPE_JID_SINGLE.equals(field.getType()) 135 && !FormField.TYPE_HIDDEN.equals(field.getType())) { 136 throw new IllegalArgumentException("This field is not of type String."); 137 } 138 setAnswer(field, value); 139 } 140 141 /** 142 * Sets a new int value to a given form's field. The field whose variable matches the 143 * requested variable will be completed with the specified value. If no field could be found 144 * for the specified variable then an exception will be raised. 145 * 146 * @param variable the variable name that was completed. 147 * @param value the int value that was answered. 148 * @throws IllegalStateException if the form is not of type "submit". 149 * @throws IllegalArgumentException if the form does not include the specified variable or 150 * if the answer type does not correspond with the field type. 151 */ 152 public void setAnswer(String variable, int value) { 153 FormField field = getField(variable); 154 if (field == null) { 155 throw new IllegalArgumentException("Field not found for the specified variable name."); 156 } 157 if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) 158 && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) 159 && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { 160 throw new IllegalArgumentException("This field is not of type int."); 161 } 162 setAnswer(field, value); 163 } 164 165 /** 166 * Sets a new long value to a given form's field. The field whose variable matches the 167 * requested variable will be completed with the specified value. If no field could be found 168 * for the specified variable then an exception will be raised. 169 * 170 * @param variable the variable name that was completed. 171 * @param value the long value that was answered. 172 * @throws IllegalStateException if the form is not of type "submit". 173 * @throws IllegalArgumentException if the form does not include the specified variable or 174 * if the answer type does not correspond with the field type. 175 */ 176 public void setAnswer(String variable, long value) { 177 FormField field = getField(variable); 178 if (field == null) { 179 throw new IllegalArgumentException("Field not found for the specified variable name."); 180 } 181 if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) 182 && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) 183 && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { 184 throw new IllegalArgumentException("This field is not of type long."); 185 } 186 setAnswer(field, value); 187 } 188 189 /** 190 * Sets a new float value to a given form's field. The field whose variable matches the 191 * requested variable will be completed with the specified value. If no field could be found 192 * for the specified variable then an exception will be raised. 193 * 194 * @param variable the variable name that was completed. 195 * @param value the float value that was answered. 196 * @throws IllegalStateException if the form is not of type "submit". 197 * @throws IllegalArgumentException if the form does not include the specified variable or 198 * if the answer type does not correspond with the field type. 199 */ 200 public void setAnswer(String variable, float value) { 201 FormField field = getField(variable); 202 if (field == null) { 203 throw new IllegalArgumentException("Field not found for the specified variable name."); 204 } 205 if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) 206 && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) 207 && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { 208 throw new IllegalArgumentException("This field is not of type float."); 209 } 210 setAnswer(field, value); 211 } 212 213 /** 214 * Sets a new double value to a given form's field. The field whose variable matches the 215 * requested variable will be completed with the specified value. If no field could be found 216 * for the specified variable then an exception will be raised. 217 * 218 * @param variable the variable name that was completed. 219 * @param value the double value that was answered. 220 * @throws IllegalStateException if the form is not of type "submit". 221 * @throws IllegalArgumentException if the form does not include the specified variable or 222 * if the answer type does not correspond with the field type. 223 */ 224 public void setAnswer(String variable, double value) { 225 FormField field = getField(variable); 226 if (field == null) { 227 throw new IllegalArgumentException("Field not found for the specified variable name."); 228 } 229 if (!FormField.TYPE_TEXT_MULTI.equals(field.getType()) 230 && !FormField.TYPE_TEXT_PRIVATE.equals(field.getType()) 231 && !FormField.TYPE_TEXT_SINGLE.equals(field.getType())) { 232 throw new IllegalArgumentException("This field is not of type double."); 233 } 234 setAnswer(field, value); 235 } 236 237 /** 238 * Sets a new boolean value to a given form's field. The field whose variable matches the 239 * requested variable will be completed with the specified value. If no field could be found 240 * for the specified variable then an exception will be raised. 241 * 242 * @param variable the variable name that was completed. 243 * @param value the boolean value that was answered. 244 * @throws IllegalStateException if the form is not of type "submit". 245 * @throws IllegalArgumentException if the form does not include the specified variable or 246 * if the answer type does not correspond with the field type. 247 */ 248 public void setAnswer(String variable, boolean value) { 249 FormField field = getField(variable); 250 if (field == null) { 251 throw new IllegalArgumentException("Field not found for the specified variable name."); 252 } 253 if (!FormField.TYPE_BOOLEAN.equals(field.getType())) { 254 throw new IllegalArgumentException("This field is not of type boolean."); 255 } 256 setAnswer(field, (value ? "1" : "0")); 257 } 258 259 /** 260 * Sets a new Object value to a given form's field. In fact, the object representation 261 * (i.e. #toString) will be the actual value of the field.<p> 262 * 263 * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you 264 * will need to use {@link #setAnswer(String, String)} where the String value is the 265 * String representation of the object.<p> 266 * 267 * Before setting the new value to the field we will check if the form is of type submit. If 268 * the form isn't of type submit means that it's not possible to complete the form and an 269 * exception will be thrown. 270 * 271 * @param field the form field that was completed. 272 * @param value the Object value that was answered. The object representation will be the 273 * actual value. 274 * @throws IllegalStateException if the form is not of type "submit". 275 */ 276 private void setAnswer(FormField field, Object value) { 277 if (!isSubmitType()) { 278 throw new IllegalStateException("Cannot set an answer if the form is not of type " + 279 "\"submit\""); 280 } 281 field.resetValues(); 282 field.addValue(value.toString()); 283 } 284 285 /** 286 * Sets a new values to a given form's field. The field whose variable matches the requested 287 * variable will be completed with the specified values. If no field could be found for 288 * the specified variable then an exception will be raised.<p> 289 * 290 * The Objects contained in the List could be of any type. The String representation of them 291 * (i.e. #toString) will be actually used when sending the answer to the server. 292 * 293 * @param variable the variable that was completed. 294 * @param values the values that were answered. 295 * @throws IllegalStateException if the form is not of type "submit". 296 * @throws IllegalArgumentException if the form does not include the specified variable. 297 */ 298 public void setAnswer(String variable, List<String> values) { 299 if (!isSubmitType()) { 300 throw new IllegalStateException("Cannot set an answer if the form is not of type " + 301 "\"submit\""); 302 } 303 FormField field = getField(variable); 304 if (field != null) { 305 // Check that the field can accept a collection of values 306 if (!FormField.TYPE_JID_MULTI.equals(field.getType()) 307 && !FormField.TYPE_LIST_MULTI.equals(field.getType()) 308 && !FormField.TYPE_LIST_SINGLE.equals(field.getType()) 309 && !FormField.TYPE_TEXT_MULTI.equals(field.getType()) 310 && !FormField.TYPE_HIDDEN.equals(field.getType())) { 311 throw new IllegalArgumentException("This field only accept list of values."); 312 } 313 // Clear the old values 314 field.resetValues(); 315 // Set the new values. The string representation of each value will be actually used. 316 field.addValues(values); 317 } 318 else { 319 throw new IllegalArgumentException("Couldn't find a field for the specified variable."); 320 } 321 } 322 323 /** 324 * Sets the default value as the value of a given form's field. The field whose variable matches 325 * the requested variable will be completed with its default value. If no field could be found 326 * for the specified variable then an exception will be raised. 327 * 328 * @param variable the variable to complete with its default value. 329 * @throws IllegalStateException if the form is not of type "submit". 330 * @throws IllegalArgumentException if the form does not include the specified variable. 331 */ 332 public void setDefaultAnswer(String variable) { 333 if (!isSubmitType()) { 334 throw new IllegalStateException("Cannot set an answer if the form is not of type " + 335 "\"submit\""); 336 } 337 FormField field = getField(variable); 338 if (field != null) { 339 // Clear the old values 340 field.resetValues(); 341 // Set the default value 342 for (String value : field.getValues()) { 343 field.addValue(value); 344 } 345 } 346 else { 347 throw new IllegalArgumentException("Couldn't find a field for the specified variable."); 348 } 349 } 350 351 /** 352 * Returns a List of the fields that are part of the form. 353 * 354 * @return a List of the fields that are part of the form. 355 */ 356 public List<FormField> getFields() { 357 return dataForm.getFields(); 358 } 359 360 /** 361 * Returns the field of the form whose variable matches the specified variable. 362 * The fields of type FIXED will never be returned since they do not specify a 363 * variable. 364 * 365 * @param variable the variable to look for in the form fields. 366 * @return the field of the form whose variable matches the specified variable. 367 */ 368 public FormField getField(String variable) { 369 if (variable == null || variable.equals("")) { 370 throw new IllegalArgumentException("Variable must not be null or blank."); 371 } 372 // Look for the field whose variable matches the requested variable 373 for (FormField field : getFields()) { 374 if (variable.equals(field.getVariable())) { 375 return field; 376 } 377 } 378 return null; 379 } 380 381 /** 382 * Returns the instructions that explain how to fill out the form and what the form is about. 383 * 384 * @return instructions that explain how to fill out the form. 385 */ 386 public String getInstructions() { 387 StringBuilder sb = new StringBuilder(); 388 // Join the list of instructions together separated by newlines 389 for (Iterator<String> it = dataForm.getInstructions().iterator(); it.hasNext();) { 390 sb.append(it.next()); 391 // If this is not the last instruction then append a newline 392 if (it.hasNext()) { 393 sb.append("\n"); 394 } 395 } 396 return sb.toString(); 397 } 398 399 400 /** 401 * Returns the description of the data. It is similar to the title on a web page or an X 402 * window. You can put a <title/> on either a form to fill out, or a set of data results. 403 * 404 * @return description of the data. 405 */ 406 public String getTitle() { 407 return dataForm.getTitle(); 408 } 409 410 411 /** 412 * Returns the meaning of the data within the context. The data could be part of a form 413 * to fill out, a form submission or data results.<p> 414 * 415 * Possible form types are: 416 * <ul> 417 * <li>form -> Indicates a form to fill out.</li> 418 * <li>submit -> The form is filled out, and this is the data that is being returned from 419 * the form.</li> 420 * <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li> 421 * <li>result -> Data results being returned from a search, or some other query.</li> 422 * </ul> 423 * 424 * @return the form's type. 425 */ 426 public String getType() { 427 return dataForm.getType(); 428 } 429 430 431 /** 432 * Sets instructions that explain how to fill out the form and what the form is about. 433 * 434 * @param instructions instructions that explain how to fill out the form. 435 */ 436 public void setInstructions(String instructions) { 437 // Split the instructions into multiple instructions for each existent newline 438 ArrayList<String> instructionsList = new ArrayList<String>(); 439 StringTokenizer st = new StringTokenizer(instructions, "\n"); 440 while (st.hasMoreTokens()) { 441 instructionsList.add(st.nextToken()); 442 } 443 // Set the new list of instructions 444 dataForm.setInstructions(instructionsList); 445 446 } 447 448 449 /** 450 * Sets the description of the data. It is similar to the title on a web page or an X window. 451 * You can put a <title/> on either a form to fill out, or a set of data results. 452 * 453 * @param title description of the data. 454 */ 455 public void setTitle(String title) { 456 dataForm.setTitle(title); 457 } 458 459 /** 460 * Returns a DataForm that serves to send this Form to the server. If the form is of type 461 * submit, it may contain fields with no value. These fields will be removed since they only 462 * exist to assist the user while editing/completing the form in a UI. 463 * 464 * @return the wrapped DataForm. 465 */ 466 public DataForm getDataFormToSend() { 467 if (isSubmitType()) { 468 // Create a new DataForm that contains only the answered fields 469 DataForm dataFormToSend = new DataForm(getType()); 470 for(FormField field : getFields()) { 471 if (!field.getValues().isEmpty()) { 472 dataFormToSend.addField(field); 473 } 474 } 475 return dataFormToSend; 476 } 477 return dataForm; 478 } 479 480 /** 481 * Returns true if the form is a form to fill out. 482 * 483 * @return if the form is a form to fill out. 484 */ 485 private boolean isFormType() { 486 return TYPE_FORM.equals(dataForm.getType()); 487 } 488 489 /** 490 * Returns true if the form is a form to submit. 491 * 492 * @return if the form is a form to submit. 493 */ 494 private boolean isSubmitType() { 495 return TYPE_SUBMIT.equals(dataForm.getType()); 496 } 497 498 /** 499 * Returns a new Form to submit the completed values. The new Form will include all the fields 500 * of the original form except for the fields of type FIXED. Only the HIDDEN fields will 501 * include the same value of the original form. The other fields of the new form MUST be 502 * completed. If a field remains with no answer when sending the completed form, then it won't 503 * be included as part of the completed form.<p> 504 * 505 * The reason why the fields with variables are included in the new form is to provide a model 506 * for binding with any UI. This means that the UIs will use the original form (of type 507 * "form") to learn how to render the form, but the UIs will bind the fields to the form of 508 * type submit. 509 * 510 * @return a Form to submit the completed values. 511 */ 512 public Form createAnswerForm() { 513 if (!isFormType()) { 514 throw new IllegalStateException("Only forms of type \"form\" could be answered"); 515 } 516 // Create a new Form 517 Form form = new Form(TYPE_SUBMIT); 518 for (FormField field : getFields()) { 519 // Add to the new form any type of field that includes a variable. 520 // Note: The fields of type FIXED are the only ones that don't specify a variable 521 if (field.getVariable() != null) { 522 FormField newField = new FormField(field.getVariable()); 523 newField.setType(field.getType()); 524 form.addField(newField); 525 // Set the answer ONLY to the hidden fields 526 if (FormField.TYPE_HIDDEN.equals(field.getType())) { 527 // Since a hidden field could have many values we need to collect them 528 // in a list 529 List<String> values = new ArrayList<String>(); 530 for (String value : field.getValues()) { 531 values.add(value); 532 } 533 form.setAnswer(field.getVariable(), values); 534 } 535 } 536 } 537 return form; 538 } 539 540}