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