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