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