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