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 if (variable == null || variable.equals("")) { 354 throw new IllegalArgumentException("Variable must not be null or blank."); 355 } 356 // Look for the field whose variable matches the requested variable 357 for (FormField field : getFields()) { 358 if (variable.equals(field.getVariable())) { 359 return field; 360 } 361 } 362 return null; 363 } 364 365 /** 366 * Returns the instructions that explain how to fill out the form and what the form is about. 367 * 368 * @return instructions that explain how to fill out the form. 369 */ 370 public String getInstructions() { 371 StringBuilder sb = new StringBuilder(); 372 // Join the list of instructions together separated by newlines 373 for (Iterator<String> it = dataForm.getInstructions().iterator(); it.hasNext();) { 374 sb.append(it.next()); 375 // If this is not the last instruction then append a newline 376 if (it.hasNext()) { 377 sb.append("\n"); 378 } 379 } 380 return sb.toString(); 381 } 382 383 384 /** 385 * Returns the description of the data. It is similar to the title on a web page or an X 386 * window. You can put a <title/> on either a form to fill out, or a set of data results. 387 * 388 * @return description of the data. 389 */ 390 public String getTitle() { 391 return dataForm.getTitle(); 392 } 393 394 395 /** 396 * Returns the meaning of the data within the context. The data could be part of a form 397 * to fill out, a form submission or data results. 398 * 399 * @return the form's type. 400 */ 401 public DataForm.Type getType() { 402 return dataForm.getType(); 403 } 404 405 406 /** 407 * Sets instructions that explain how to fill out the form and what the form is about. 408 * 409 * @param instructions instructions that explain how to fill out the form. 410 */ 411 public void setInstructions(String instructions) { 412 // Split the instructions into multiple instructions for each existent newline 413 ArrayList<String> instructionsList = new ArrayList<String>(); 414 StringTokenizer st = new StringTokenizer(instructions, "\n"); 415 while (st.hasMoreTokens()) { 416 instructionsList.add(st.nextToken()); 417 } 418 // Set the new list of instructions 419 dataForm.setInstructions(instructionsList); 420 421 } 422 423 424 /** 425 * Sets the description of the data. It is similar to the title on a web page or an X window. 426 * You can put a <title/> on either a form to fill out, or a set of data results. 427 * 428 * @param title description of the data. 429 */ 430 public void setTitle(String title) { 431 dataForm.setTitle(title); 432 } 433 434 /** 435 * Returns a DataForm that serves to send this Form to the server. If the form is of type 436 * submit, it may contain fields with no value. These fields will be removed since they only 437 * exist to assist the user while editing/completing the form in a UI. 438 * 439 * @return the wrapped DataForm. 440 */ 441 public DataForm getDataFormToSend() { 442 if (isSubmitType()) { 443 // Create a new DataForm that contains only the answered fields 444 DataForm dataFormToSend = new DataForm(getType()); 445 for(FormField field : getFields()) { 446 if (!field.getValues().isEmpty()) { 447 dataFormToSend.addField(field); 448 } 449 } 450 return dataFormToSend; 451 } 452 return dataForm; 453 } 454 455 /** 456 * Returns true if the form is a form to fill out. 457 * 458 * @return if the form is a form to fill out. 459 */ 460 private boolean isFormType() { 461 return DataForm.Type.form == dataForm.getType(); 462 } 463 464 /** 465 * Returns true if the form is a form to submit. 466 * 467 * @return if the form is a form to submit. 468 */ 469 private boolean isSubmitType() { 470 return DataForm.Type.submit == dataForm.getType(); 471 } 472 473 /** 474 * Returns a new Form to submit the completed values. The new Form will include all the fields 475 * of the original form except for the fields of type FIXED. Only the HIDDEN fields will 476 * include the same value of the original form. The other fields of the new form MUST be 477 * completed. If a field remains with no answer when sending the completed form, then it won't 478 * be included as part of the completed form.<p> 479 * 480 * The reason why the fields with variables are included in the new form is to provide a model 481 * for binding with any UI. This means that the UIs will use the original form (of type 482 * "form") to learn how to render the form, but the UIs will bind the fields to the form of 483 * type submit. 484 * 485 * @return a Form to submit the completed values. 486 */ 487 public Form createAnswerForm() { 488 if (!isFormType()) { 489 throw new IllegalStateException("Only forms of type \"form\" could be answered"); 490 } 491 // Create a new Form 492 Form form = new Form(DataForm.Type.submit); 493 for (FormField field : getFields()) { 494 // Add to the new form any type of field that includes a variable. 495 // Note: The fields of type FIXED are the only ones that don't specify a variable 496 if (field.getVariable() != null) { 497 FormField newField = new FormField(field.getVariable()); 498 newField.setType(field.getType()); 499 form.addField(newField); 500 // Set the answer ONLY to the hidden fields 501 if (field.getType() == FormField.Type.hidden) { 502 // Since a hidden field could have many values we need to collect them 503 // in a list 504 List<String> values = new ArrayList<String>(); 505 for (String value : field.getValues()) { 506 values.add(value); 507 } 508 form.setAnswer(field.getVariable(), values); 509 } 510 } 511 } 512 return form; 513 } 514 515}