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 */
017
018package org.jivesoftware.smackx.xdata.packet;
019
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.LinkedHashMap;
023import java.util.List;
024import java.util.Locale;
025import java.util.Map;
026
027import org.jivesoftware.smack.packet.Element;
028import org.jivesoftware.smack.packet.ExtensionElement;
029import org.jivesoftware.smack.packet.Stanza;
030import org.jivesoftware.smack.util.XmlStringBuilder;
031
032import org.jivesoftware.smackx.xdata.FormField;
033
034/**
035 * Represents a form that could be use for gathering data as well as for reporting data
036 * returned from a search.
037 *
038 * @author Gaston Dombiak
039 */
040public class DataForm implements ExtensionElement {
041
042    public static final String NAMESPACE = "jabber:x:data";
043    public static final String ELEMENT = "x";
044
045    public enum Type {
046        /**
047         * This stanza(/packet) contains a form to fill out. Display it to the user (if your program can).
048         */
049        form,
050
051        /**
052         * The form is filled out, and this is the data that is being returned from the form.
053         */
054        submit,
055
056        /**
057         * The form was cancelled. Tell the asker that piece of information.
058         */
059        cancel,
060
061        /**
062         * Data results being returned from a search, or some other query.
063         */
064        result,
065        ;
066
067        public static Type fromString(String string) {
068            return Type.valueOf(string.toLowerCase(Locale.US));
069        }
070    }
071
072    private Type type;
073    private String title;
074    private final List<String> instructions = new ArrayList<>();
075    private ReportedData reportedData;
076    private final List<Item> items = new ArrayList<>();
077    private final Map<String, FormField> fields = new LinkedHashMap<>();
078    private final List<Element> extensionElements = new ArrayList<>();
079
080    public DataForm(Type type) {
081        this.type = type;
082    }
083
084    /**
085     * Returns the meaning of the data within the context. The data could be part of a form
086     * to fill out, a form submission or data results.
087     * 
088     * @return the form's type.
089     */
090    public Type getType() {
091        return type; 
092    }
093
094    /**
095     * Returns the description of the data. It is similar to the title on a web page or an X 
096     * window.  You can put a &lt;title/&gt; on either a form to fill out, or a set of data results.
097     * 
098     * @return description of the data.
099     */
100    public String getTitle() {
101        return title;
102    }
103
104    /**
105     * Returns a List of the list of instructions that explain how to fill out the form and 
106     * what the form is about. The dataform could include multiple instructions since each 
107     * instruction could not contain newlines characters. Join the instructions together in order 
108     * to show them to the user.
109     * 
110     * @return a List of the list of instructions that explain how to fill out the form.
111     */
112    public List<String> getInstructions() {
113        synchronized (instructions) {
114            return Collections.unmodifiableList(new ArrayList<>(instructions));
115        }
116    }
117
118    /**
119     * Returns the fields that will be returned from a search.
120     * 
121     * @return fields that will be returned from a search.
122     */
123    public ReportedData getReportedData() {
124        return reportedData;
125    }
126
127    /**
128     * Returns a List of the items returned from a search.
129     *
130     * @return a List of the items returned from a search.
131     */
132    public List<Item> getItems() {
133        synchronized (items) {
134            return Collections.unmodifiableList(new ArrayList<>(items));
135        }
136    }
137
138    /**
139     * Returns a List of the fields that are part of the form.
140     *
141     * @return a List of the fields that are part of the form.
142     */
143    public List<FormField> getFields() {
144        synchronized (fields) {
145            return new ArrayList<>(fields.values());
146        }
147    }
148
149    /**
150     * Return the form field with the given variable name or null.
151     *
152     * @param variableName
153     * @return the form field or null.
154     * @since 4.1
155     */
156    public FormField getField(String variableName) {
157        synchronized (fields) {
158            return fields.get(variableName);
159        }
160    }
161
162    /**
163     * Check if a form field with the given variable name exists.
164     *
165     * @param variableName
166     * @return true if a form field with the variable name exists, false otherwise.
167     * @since 4.2
168     */
169    public boolean hasField(String variableName) {
170        synchronized (fields) {
171            return fields.containsKey(variableName);
172        }
173    }
174
175    @Override
176    public String getElementName() {
177        return ELEMENT;
178    }
179
180    @Override
181    public String getNamespace() {
182        return NAMESPACE;
183    }
184
185    /**
186     * Sets the description of the data. It is similar to the title on a web page or an X window.
187     * You can put a &lt;title/&gt; on either a form to fill out, or a set of data results.
188     * 
189     * @param title description of the data.
190     */
191    public void setTitle(String title) {
192        this.title = title;
193    }
194
195    /**
196     * Sets the list of instructions that explain how to fill out the form and what the form is 
197     * about. The dataform could include multiple instructions since each instruction could not 
198     * contain newlines characters. 
199     * 
200     * @param instructions list of instructions that explain how to fill out the form.
201     */
202    public void setInstructions(List<String> instructions) {
203        synchronized (this.instructions) {
204            this.instructions.clear();
205            this.instructions.addAll(instructions);
206        }
207    }
208
209    /**
210     * Sets the fields that will be returned from a search.
211     * 
212     * @param reportedData the fields that will be returned from a search.
213     */
214    public void setReportedData(ReportedData reportedData) {
215        this.reportedData = reportedData;
216    }
217
218    /**
219     * Adds a new field as part of the form.
220     * 
221     * @param field the field to add to the form.
222     */
223    public void addField(FormField field) {
224        String fieldVariableName = field.getVariable();
225        // Form field values must be unique unless they are of type 'fixed', in
226        // which case their variable name may be 'null', and therefore could
227        // appear multiple times within the same form.
228        if (fieldVariableName != null && hasField(fieldVariableName)) {
229            throw new IllegalArgumentException("This data form already contains a form field with the variable name '"
230                            + fieldVariableName + "'");
231        }
232        synchronized (fields) {
233            fields.put(fieldVariableName, field);
234        }
235    }
236
237    /**
238     * Adds a new instruction to the list of instructions that explain how to fill out the form 
239     * and what the form is about. The dataform could include multiple instructions since each 
240     * instruction could not contain newlines characters. 
241     * 
242     * @param instruction the new instruction that explain how to fill out the form.
243     */
244    public void addInstruction(String instruction) {
245        synchronized (instructions) {
246            instructions.add(instruction);
247        }
248    }
249
250    /**
251     * Adds a new item returned from a search.
252     * 
253     * @param item the item returned from a search.
254     */
255    public void addItem(Item item) {
256        synchronized (items) {
257            items.add(item);
258        }
259    }
260
261    public void addExtensionElement(Element element) {
262        extensionElements.add(element);
263    }
264
265    public List<Element> getExtensionElements() {
266        return Collections.unmodifiableList(extensionElements);
267    }
268
269    /**
270     * Returns the hidden FORM_TYPE field or null if this data form has none.
271     *
272     * @return the hidden FORM_TYPE field or null.
273     * @since 4.1
274     */
275    public FormField getHiddenFormTypeField() {
276        FormField field = getField(FormField.FORM_TYPE);
277        if (field != null && field.getType() == FormField.Type.hidden) {
278            return field;
279        }
280        return null;
281    }
282
283    /**
284     * Returns true if this DataForm has at least one FORM_TYPE field which is
285     * hidden. This method is used for sanity checks.
286     *
287     * @return true if there is at least one field which is hidden.
288     */
289    public boolean hasHiddenFormTypeField() {
290        return getHiddenFormTypeField() != null;
291    }
292
293    @Override
294    public XmlStringBuilder toXML() {
295        XmlStringBuilder buf = new XmlStringBuilder(this);
296        buf.attribute("type", getType());
297        buf.rightAngleBracket();
298
299        buf.optElement("title", getTitle());
300        for (String instruction : getInstructions()) {
301            buf.element("instructions", instruction);
302        }
303        // Append the list of fields returned from a search
304        if (getReportedData() != null) {
305            buf.append(getReportedData().toXML());
306        }
307        // Loop through all the items returned from a search and append them to the string buffer
308        for (Item item : getItems()) {
309            buf.append(item.toXML());
310        }
311        // Loop through all the form fields and append them to the string buffer
312        for (FormField field : getFields()) {
313            buf.append(field.toXML());
314        }
315        for (Element element : extensionElements) {
316            buf.append(element.toXML());
317        }
318        buf.closeElement(this);
319        return buf;
320    }
321
322    /**
323     * Get data form from stanza.
324     * @param packet
325     * @return the DataForm or null
326     */
327    public static DataForm from(Stanza packet) {
328        return (DataForm) packet.getExtension(ELEMENT, NAMESPACE);
329    }
330
331    /**
332     * 
333     * Represents the fields that will be returned from a search. This information is useful when 
334     * you try to use the jabber:iq:search namespace to return dynamic form information.
335     *
336     * @author Gaston Dombiak
337     */
338    public static class ReportedData {
339        public static final String ELEMENT = "reported";
340
341        private List<FormField> fields = new ArrayList<>();
342
343        public ReportedData(List<FormField> fields) {
344            this.fields = fields;
345        }
346
347        /**
348         * Returns the fields returned from a search.
349         * 
350         * @return the fields returned from a search.
351         */
352        public List<FormField> getFields() {
353            return Collections.unmodifiableList(new ArrayList<>(fields));
354        }
355
356        public CharSequence toXML() {
357            XmlStringBuilder buf = new XmlStringBuilder();
358            buf.openElement(ELEMENT);
359            // Loop through all the form items and append them to the string buffer
360            for (FormField field : getFields()) {
361                buf.append(field.toXML());
362            }
363            buf.closeElement(ELEMENT);
364            return buf;
365        }
366    }
367
368    /**
369     * 
370     * Represents items of reported data.
371     *
372     * @author Gaston Dombiak
373     */
374    public static class Item {
375        public static final String ELEMENT = "item";
376
377        private List<FormField> fields = new ArrayList<>();
378
379        public Item(List<FormField> fields) {
380            this.fields = fields;
381        }
382
383        /**
384         * Returns the fields that define the data that goes with the item.
385         * 
386         * @return the fields that define the data that goes with the item.
387         */
388        public List<FormField> getFields() {
389            return Collections.unmodifiableList(new ArrayList<>(fields));
390        }
391
392        public CharSequence toXML() {
393            XmlStringBuilder buf = new XmlStringBuilder();
394            buf.openElement(ELEMENT);
395            // Loop through all the form items and append them to the string buffer
396            for (FormField field : getFields()) {
397                buf.append(field.toXML());
398            }
399            buf.closeElement(ELEMENT);
400            return buf;
401        }
402    }
403}