Home | History | Annotate | Download | only in smackx
      1 /**
      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 
     21 package org.jivesoftware.smackx;
     22 
     23 import java.util.ArrayList;
     24 import java.util.Iterator;
     25 import java.util.List;
     26 import java.util.StringTokenizer;
     27 
     28 import org.jivesoftware.smack.packet.Packet;
     29 import org.jivesoftware.smack.packet.PacketExtension;
     30 import 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  */
     49 public 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 }
    552