Form.java

  1. /**
  2.  *
  3.  * Copyright 2003-2007 Jive Software.
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smackx.xdata;

  18. import java.util.ArrayList;
  19. import java.util.Iterator;
  20. import java.util.List;
  21. import java.util.StringTokenizer;

  22. import org.jivesoftware.smack.packet.Stanza;
  23. import org.jivesoftware.smackx.xdata.packet.DataForm;

  24. /**
  25.  * Represents a Form for gathering data. The form could be of the following types:
  26.  * <ul>
  27.  *  <li>form -> Indicates a form to fill out.</li>
  28.  *  <li>submit -> The form is filled out, and this is the data that is being returned from
  29.  * the form.</li>
  30.  *  <li>cancel -> The form was cancelled. Tell the asker that piece of information.</li>
  31.  *  <li>result -> Data results being returned from a search, or some other query.</li>
  32.  * </ul>
  33.  *
  34.  * Depending of the form's type different operations are available. For example, it's only possible
  35.  * to set answers if the form is of type "submit".
  36.  *
  37.  * @see <a href="http://xmpp.org/extensions/xep-0004.html">XEP-0004 Data Forms</a>
  38.  *
  39.  * @author Gaston Dombiak
  40.  */
  41. public class Form {

  42.     private DataForm dataForm;

  43.     /**
  44.      * Returns a new ReportedData if the packet is used for gathering data and includes an
  45.      * extension that matches the elementName and namespace "x","jabber:x:data".  
  46.      *
  47.      * @param packet the packet used for gathering data.
  48.      * @return the data form parsed from the packet or <tt>null</tt> if there was not
  49.      *      a form in the packet.
  50.      */
  51.     public static Form getFormFrom(Stanza packet) {
  52.         // Check if the packet includes the DataForm extension
  53.         DataForm dataForm = DataForm.from(packet);
  54.         if (dataForm != null) {
  55.             if (dataForm.getReportedData() == null)
  56.                 return new Form(dataForm);
  57.         }
  58.         // Otherwise return null
  59.         return null;
  60.     }

  61.     /**
  62.      * Creates a new Form that will wrap an existing DataForm. The wrapped DataForm must be
  63.      * used for gathering data.
  64.      *
  65.      * @param dataForm the data form used for gathering data.
  66.      */
  67.     public Form(DataForm dataForm) {
  68.         this.dataForm = dataForm;
  69.     }
  70.    
  71.     /**
  72.      * Creates a new Form of a given type from scratch.
  73.      *
  74.      * @param type the form's type (e.g. form, submit,cancel,result).
  75.      */
  76.     public Form(DataForm.Type type) {
  77.         this.dataForm = new DataForm(type);
  78.     }
  79.    
  80.     /**
  81.      * Adds a new field to complete as part of the form.
  82.      *
  83.      * @param field the field to complete.
  84.      */
  85.     public void addField(FormField field) {
  86.         dataForm.addField(field);
  87.     }
  88.    
  89.     /**
  90.      * Sets a new String value to a given form's field. The field whose variable matches the
  91.      * requested variable will be completed with the specified value. If no field could be found
  92.      * for the specified variable then an exception will be raised.<p>
  93.      *
  94.      * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you
  95.      * can use this message where the String value is the String representation of the object.
  96.      *
  97.      * @param variable the variable name that was completed.
  98.      * @param value the String value that was answered.
  99.      * @throws IllegalStateException if the form is not of type "submit".
  100.      * @throws IllegalArgumentException if the form does not include the specified variable or
  101.      *      if the answer type does not correspond with the field type..
  102.      */
  103.     public void setAnswer(String variable, String value) {
  104.         FormField field = getField(variable);
  105.         if (field == null) {
  106.             throw new IllegalArgumentException("Field not found for the specified variable name.");
  107.         }
  108.         switch (field.getType()) {
  109.         case text_multi:
  110.         case text_private:
  111.         case text_single:
  112.         case jid_single:
  113.         case hidden:
  114.             break;
  115.         default:
  116.             throw new IllegalArgumentException("This field is not of type String.");
  117.         }
  118.         setAnswer(field, value);
  119.     }

  120.     /**
  121.      * Sets a new int value to a given form's field. The field whose variable matches the
  122.      * requested variable will be completed with the specified value. If no field could be found
  123.      * for the specified variable then an exception will be raised.
  124.      *
  125.      * @param variable the variable name that was completed.
  126.      * @param value the int value that was answered.
  127.      * @throws IllegalStateException if the form is not of type "submit".
  128.      * @throws IllegalArgumentException if the form does not include the specified variable or
  129.      *      if the answer type does not correspond with the field type.
  130.      */
  131.     public void setAnswer(String variable, int value) {
  132.         FormField field = getField(variable);
  133.         if (field == null) {
  134.             throw new IllegalArgumentException("Field not found for the specified variable name.");
  135.         }
  136.         validateThatFieldIsText(field);
  137.         setAnswer(field, value);
  138.     }

  139.     /**
  140.      * Sets a new long value to a given form's field. The field whose variable matches the
  141.      * requested variable will be completed with the specified value. If no field could be found
  142.      * for the specified variable then an exception will be raised.
  143.      *
  144.      * @param variable the variable name that was completed.
  145.      * @param value the long value that was answered.
  146.      * @throws IllegalStateException if the form is not of type "submit".
  147.      * @throws IllegalArgumentException if the form does not include the specified variable or
  148.      *      if the answer type does not correspond with the field type.
  149.      */
  150.     public void setAnswer(String variable, long value) {
  151.         FormField field = getField(variable);
  152.         if (field == null) {
  153.             throw new IllegalArgumentException("Field not found for the specified variable name.");
  154.         }
  155.         validateThatFieldIsText(field);
  156.         setAnswer(field, value);
  157.     }

  158.     /**
  159.      * Sets a new float value to a given form's field. The field whose variable matches the
  160.      * requested variable will be completed with the specified value. If no field could be found
  161.      * for the specified variable then an exception will be raised.
  162.      *
  163.      * @param variable the variable name that was completed.
  164.      * @param value the float value that was answered.
  165.      * @throws IllegalStateException if the form is not of type "submit".
  166.      * @throws IllegalArgumentException if the form does not include the specified variable or
  167.      *      if the answer type does not correspond with the field type.
  168.      */
  169.     public void setAnswer(String variable, float value) {
  170.         FormField field = getField(variable);
  171.         if (field == null) {
  172.             throw new IllegalArgumentException("Field not found for the specified variable name.");
  173.         }
  174.         validateThatFieldIsText(field);
  175.         setAnswer(field, value);
  176.     }

  177.     /**
  178.      * Sets a new double value to a given form's field. The field whose variable matches the
  179.      * requested variable will be completed with the specified value. If no field could be found
  180.      * for the specified variable then an exception will be raised.
  181.      *
  182.      * @param variable the variable name that was completed.
  183.      * @param value the double value that was answered.
  184.      * @throws IllegalStateException if the form is not of type "submit".
  185.      * @throws IllegalArgumentException if the form does not include the specified variable or
  186.      *      if the answer type does not correspond with the field type.
  187.      */
  188.     public void setAnswer(String variable, double value) {
  189.         FormField field = getField(variable);
  190.         if (field == null) {
  191.             throw new IllegalArgumentException("Field not found for the specified variable name.");
  192.         }
  193.         validateThatFieldIsText(field);
  194.         setAnswer(field, value);
  195.     }

  196.     private static void validateThatFieldIsText(FormField field) {
  197.         switch(field.getType()) {
  198.         case text_multi:
  199.         case text_private:
  200.         case text_single:
  201.             break;
  202.         default:
  203.             throw new IllegalArgumentException("This field is not of type text (multi, private or single).");
  204.         }
  205.     }

  206.     /**
  207.      * Sets a new boolean value to a given form's field. The field whose variable matches the
  208.      * requested variable will be completed with the specified value. If no field could be found
  209.      * for the specified variable then an exception will be raised.
  210.      *
  211.      * @param variable the variable name that was completed.
  212.      * @param value the boolean value that was answered.
  213.      * @throws IllegalStateException if the form is not of type "submit".
  214.      * @throws IllegalArgumentException if the form does not include the specified variable or
  215.      *      if the answer type does not correspond with the field type.
  216.      */
  217.     public void setAnswer(String variable, boolean value) {
  218.         FormField field = getField(variable);
  219.         if (field == null) {
  220.             throw new IllegalArgumentException("Field not found for the specified variable name.");
  221.         }
  222.         if (field.getType() != FormField.Type.bool) {
  223.             throw new IllegalArgumentException("This field is not of type boolean.");
  224.         }
  225.         setAnswer(field, (value ? "1" : "0"));
  226.     }

  227.     /**
  228.      * Sets a new Object value to a given form's field. In fact, the object representation
  229.      * (i.e. #toString) will be the actual value of the field.<p>
  230.      *
  231.      * If the value to set to the field is not a basic type (e.g. String, boolean, int, etc.) you
  232.      * will need to use {@link #setAnswer(String, String)} where the String value is the
  233.      * String representation of the object.<p>
  234.      *
  235.      * Before setting the new value to the field we will check if the form is of type submit. If
  236.      * the form isn't of type submit means that it's not possible to complete the form and an  
  237.      * exception will be thrown.
  238.      *
  239.      * @param field the form field that was completed.
  240.      * @param value the Object value that was answered. The object representation will be the
  241.      * actual value.
  242.      * @throws IllegalStateException if the form is not of type "submit".
  243.      */
  244.     private void setAnswer(FormField field, Object value) {
  245.         if (!isSubmitType()) {
  246.             throw new IllegalStateException("Cannot set an answer if the form is not of type " +
  247.             "\"submit\"");
  248.         }
  249.         field.resetValues();
  250.         field.addValue(value.toString());
  251.     }

  252.     /**
  253.      * Sets a new values to a given form's field. The field whose variable matches the requested
  254.      * variable will be completed with the specified values. If no field could be found for
  255.      * the specified variable then an exception will be raised.<p>
  256.      *
  257.      * The Objects contained in the List could be of any type. The String representation of them
  258.      * (i.e. #toString) will be actually used when sending the answer to the server.
  259.      *
  260.      * @param variable the variable that was completed.
  261.      * @param values the values that were answered.
  262.      * @throws IllegalStateException if the form is not of type "submit".
  263.      * @throws IllegalArgumentException if the form does not include the specified variable.
  264.      */
  265.     public void setAnswer(String variable, List<String> values) {
  266.         if (!isSubmitType()) {
  267.             throw new IllegalStateException("Cannot set an answer if the form is not of type " +
  268.             "\"submit\"");
  269.         }
  270.         FormField field = getField(variable);
  271.         if (field != null) {
  272.             // Check that the field can accept a collection of values
  273.             switch (field.getType()) {
  274.             case jid_multi:
  275.             case list_multi:
  276.             case list_single:
  277.             case text_multi:
  278.             case hidden:
  279.                 break;
  280.             default:
  281.                 throw new IllegalArgumentException("This field only accept list of values.");
  282.             }
  283.             // Clear the old values
  284.             field.resetValues();
  285.             // Set the new values. The string representation of each value will be actually used.
  286.             field.addValues(values);
  287.         }
  288.         else {
  289.             throw new IllegalArgumentException("Couldn't find a field for the specified variable.");
  290.         }
  291.     }

  292.     /**
  293.      * Sets the default value as the value of a given form's field. The field whose variable matches
  294.      * the requested variable will be completed with its default value. If no field could be found
  295.      * for the specified variable then an exception will be raised.
  296.      *
  297.      * @param variable the variable to complete with its default value.
  298.      * @throws IllegalStateException if the form is not of type "submit".
  299.      * @throws IllegalArgumentException if the form does not include the specified variable.
  300.      */
  301.     public void setDefaultAnswer(String variable) {
  302.         if (!isSubmitType()) {
  303.             throw new IllegalStateException("Cannot set an answer if the form is not of type " +
  304.             "\"submit\"");
  305.         }
  306.         FormField field = getField(variable);
  307.         if (field != null) {
  308.             // Clear the old values
  309.             field.resetValues();
  310.             // Set the default value
  311.             for (String value : field.getValues()) {
  312.                 field.addValue(value);
  313.             }
  314.         }
  315.         else {
  316.             throw new IllegalArgumentException("Couldn't find a field for the specified variable.");
  317.         }
  318.     }

  319.     /**
  320.      * Returns a List of the fields that are part of the form.
  321.      *
  322.      * @return a List of the fields that are part of the form.
  323.      */
  324.     public List<FormField> getFields() {
  325.         return dataForm.getFields();
  326.     }

  327.     /**
  328.      * Returns the field of the form whose variable matches the specified variable.
  329.      * The fields of type FIXED will never be returned since they do not specify a
  330.      * variable.
  331.      *
  332.      * @param variable the variable to look for in the form fields.
  333.      * @return the field of the form whose variable matches the specified variable.
  334.      */
  335.     public FormField getField(String variable) {
  336.         if (variable == null || variable.equals("")) {
  337.             throw new IllegalArgumentException("Variable must not be null or blank.");
  338.         }
  339.         // Look for the field whose variable matches the requested variable
  340.         for (FormField field : getFields()) {
  341.             if (variable.equals(field.getVariable())) {
  342.                 return field;
  343.             }
  344.         }
  345.         return null;
  346.     }

  347.     /**
  348.      * Returns the instructions that explain how to fill out the form and what the form is about.
  349.      *
  350.      * @return instructions that explain how to fill out the form.
  351.      */
  352.     public String getInstructions() {
  353.         StringBuilder sb = new StringBuilder();
  354.         // Join the list of instructions together separated by newlines
  355.         for (Iterator<String> it = dataForm.getInstructions().iterator(); it.hasNext();) {
  356.             sb.append(it.next());
  357.             // If this is not the last instruction then append a newline
  358.             if (it.hasNext()) {
  359.                 sb.append("\n");
  360.             }
  361.         }
  362.         return sb.toString();
  363.     }


  364.     /**
  365.      * Returns the description of the data. It is similar to the title on a web page or an X
  366.      * window.  You can put a <title/> on either a form to fill out, or a set of data results.
  367.      *
  368.      * @return description of the data.
  369.      */
  370.     public String getTitle() {
  371.         return dataForm.getTitle();
  372.     }


  373.     /**
  374.      * Returns the meaning of the data within the context. The data could be part of a form
  375.      * to fill out, a form submission or data results.
  376.      *
  377.      * @return the form's type.
  378.      */
  379.     public DataForm.Type getType() {
  380.         return dataForm.getType();
  381.     }
  382.    

  383.     /**
  384.      * Sets instructions that explain how to fill out the form and what the form is about.
  385.      *
  386.      * @param instructions instructions that explain how to fill out the form.
  387.      */
  388.     public void setInstructions(String instructions) {
  389.         // Split the instructions into multiple instructions for each existent newline
  390.         ArrayList<String> instructionsList = new ArrayList<String>();
  391.         StringTokenizer st = new StringTokenizer(instructions, "\n");
  392.         while (st.hasMoreTokens()) {
  393.             instructionsList.add(st.nextToken());
  394.         }
  395.         // Set the new list of instructions
  396.         dataForm.setInstructions(instructionsList);
  397.        
  398.     }


  399.     /**
  400.      * Sets the description of the data. It is similar to the title on a web page or an X window.
  401.      * You can put a <title/> on either a form to fill out, or a set of data results.
  402.      *
  403.      * @param title description of the data.
  404.      */
  405.     public void setTitle(String title) {
  406.         dataForm.setTitle(title);
  407.     }
  408.    
  409.     /**
  410.      * Returns a DataForm that serves to send this Form to the server. If the form is of type
  411.      * submit, it may contain fields with no value. These fields will be removed since they only
  412.      * exist to assist the user while editing/completing the form in a UI.
  413.      *
  414.      * @return the wrapped DataForm.
  415.      */
  416.     public DataForm getDataFormToSend() {
  417.         if (isSubmitType()) {
  418.             // Create a new DataForm that contains only the answered fields
  419.             DataForm dataFormToSend = new DataForm(getType());
  420.             for(FormField field : getFields()) {
  421.                 if (!field.getValues().isEmpty()) {
  422.                     dataFormToSend.addField(field);
  423.                 }
  424.             }
  425.             return dataFormToSend;
  426.         }
  427.         return dataForm;
  428.     }
  429.    
  430.     /**
  431.      * Returns true if the form is a form to fill out.
  432.      *
  433.      * @return if the form is a form to fill out.
  434.      */
  435.     private boolean isFormType() {
  436.         return DataForm.Type.form == dataForm.getType();
  437.     }
  438.    
  439.     /**
  440.      * Returns true if the form is a form to submit.
  441.      *
  442.      * @return if the form is a form to submit.
  443.      */
  444.     private boolean isSubmitType() {
  445.         return DataForm.Type.submit == dataForm.getType();
  446.     }

  447.     /**
  448.      * Returns a new Form to submit the completed values. The new Form will include all the fields
  449.      * of the original form except for the fields of type FIXED. Only the HIDDEN fields will
  450.      * include the same value of the original form. The other fields of the new form MUST be
  451.      * completed. If a field remains with no answer when sending the completed form, then it won't
  452.      * be included as part of the completed form.<p>
  453.      *
  454.      * The reason why the fields with variables are included in the new form is to provide a model
  455.      * for binding with any UI. This means that the UIs will use the original form (of type
  456.      * "form") to learn how to render the form, but the UIs will bind the fields to the form of
  457.      * type submit.
  458.      *
  459.      * @return a Form to submit the completed values.
  460.      */
  461.     public Form createAnswerForm() {
  462.         if (!isFormType()) {
  463.             throw new IllegalStateException("Only forms of type \"form\" could be answered");
  464.         }
  465.         // Create a new Form
  466.         Form form = new Form(DataForm.Type.submit);
  467.         for (FormField field : getFields()) {
  468.             // Add to the new form any type of field that includes a variable.
  469.             // Note: The fields of type FIXED are the only ones that don't specify a variable
  470.             if (field.getVariable() != null) {
  471.                 FormField newField = new FormField(field.getVariable());
  472.                 newField.setType(field.getType());
  473.                 form.addField(newField);
  474.                 // Set the answer ONLY to the hidden fields
  475.                 if (field.getType() == FormField.Type.hidden) {
  476.                     // Since a hidden field could have many values we need to collect them
  477.                     // in a list
  478.                     List<String> values = new ArrayList<String>();
  479.                     for (String value : field.getValues()) {
  480.                         values.add(value);
  481.                     }
  482.                     form.setAnswer(field.getVariable(), values);
  483.                 }
  484.             }
  485.         }
  486.         return form;
  487.     }

  488. }