blob: 992c03619215d50e23dab35022caa2027158e2db [file] [log] [blame]
Shuyi Chend7955ce2013-05-22 14:51:55 -07001/**
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
21package org.jivesoftware.smackx;
22
23import java.util.ArrayList;
24import java.util.Iterator;
25import java.util.List;
26import java.util.StringTokenizer;
27
28import org.jivesoftware.smack.packet.Packet;
29import org.jivesoftware.smack.packet.PacketExtension;
30import 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 */
49public 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}