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;
019
020import java.util.ArrayList;
021import java.util.Collections;
022import java.util.List;
023
024import org.jivesoftware.smack.packet.NamedElement;
025import org.jivesoftware.smack.util.XmlStringBuilder;
026import org.jivesoftware.smackx.xdatavalidation.packet.ValidateElement;
027
028/**
029 * Represents a field of a form. The field could be used to represent a question to complete,
030 * a completed question or a data returned from a search. The exact interpretation of the field
031 * depends on the context where the field is used.
032 *
033 * @author Gaston Dombiak
034 */
035public class FormField implements NamedElement {
036
037    public static final String ELEMENT = "field";
038
039    /**
040     * The constant String "FORM_TYPE"
041     */
042    public static final String FORM_TYPE = "FORM_TYPE";
043
044    /**
045     * Form Field Types as defined in XEP-4 § 3.3.
046     * 
047     * @see <a href="http://xmpp.org/extensions/xep-0004.html#protocol-fieldtypes">XEP-4 § 3.3 Field Types</a>
048     */
049    public enum Type {
050
051        /**
052         * Boolean type. Can be 0 or 1, true or false, yes or no. Default value is 0.
053         * <p>
054         * Note that in XEP-4 this type is called 'boolean', but since that String is a restricted keyword in Java, it
055         * is named 'bool' in Smack.
056         * </p>
057         */
058        bool,
059
060        /**
061         * Fixed for putting in text to show sections, or just advertise your web site in the middle of the form
062         */
063        fixed,
064
065        /**
066         * Is not given to the user at all, but returned with the questionnaire
067         */
068        hidden,
069
070        /**
071         * multiple entries for JIDs
072         */
073        jid_multi,
074
075        /**
076         * Jabber ID - choosing a JID from your roster, and entering one based on the rules for a JID.
077         */
078        jid_single,
079
080        /**
081         * Given a list of choices, pick one or more.
082         */
083        list_multi,
084
085        /**
086         * Given a list of choices, pick one.
087         */
088        list_single,
089
090        /**
091         * Multiple lines of text entry.
092         */
093        text_multi,
094
095        /**
096         * Instead of showing the user what they typed, you show ***** to protect it.
097         */
098        text_private,
099
100        /**
101         * Single line or word of text.
102         */
103        text_single,
104        ;
105
106        @Override
107        public String toString() {
108            switch (this) {
109            case bool:
110                return "boolean";
111            default:
112                return this.name().replace('_', '-');
113            }
114        }
115
116        /**
117         * Get a form field type from the given string. If <code>string</code> is null, then null will be returned.
118         *
119         * @param string the string to transform or null.
120         * @return the type or null.
121         */
122        public static Type fromString(String string) {
123            if (string == null) {
124                return null;
125            }
126            switch (string) {
127            case "boolean":
128                return bool;
129            default:
130                string = string.replace('-', '_');
131                return Type.valueOf(string);
132            }
133        }
134    }
135
136    private final String variable;
137
138    private String description;
139    private boolean required = false;
140    private String label;
141    private Type type;
142    private final List<Option> options = new ArrayList<Option>();
143    private final List<String> values = new ArrayList<String>();
144    private ValidateElement validateElement;
145
146    /**
147     * Creates a new FormField with the variable name that uniquely identifies the field
148     * in the context of the form.
149     *
150     * @param variable the variable name of the question.
151     */
152    public FormField(String variable) {
153        this.variable = variable;
154    }
155
156    /**
157     * Creates a new FormField of type FIXED. The fields of type FIXED do not define a variable
158     * name.
159     */
160    public FormField() {
161        this(null);
162        this.type = Type.fixed;
163    }
164
165    /**
166     * Returns a description that provides extra clarification about the question. This information
167     * could be presented to the user either in tool-tip, help button, or as a section of text
168     * before the question.<p>
169     * <p/>
170     * If the question is of type FIXED then the description should remain empty.
171     *
172     * @return description that provides extra clarification about the question.
173     */
174    public String getDescription() {
175        return description;
176    }
177
178    /**
179     * Returns the label of the question which should give enough information to the user to
180     * fill out the form.
181     *
182     * @return label of the question.
183     */
184    public String getLabel() {
185        return label;
186    }
187
188    /**
189     * Returns a List of the available options that the user has in order to answer
190     * the question.
191     *
192     * @return List of the available options.
193     */
194    public List<Option> getOptions() {
195        synchronized (options) {
196            return Collections.unmodifiableList(new ArrayList<Option>(options));
197        }
198    }
199
200    /**
201     * Returns true if the question must be answered in order to complete the questionnaire.
202     *
203     * @return true if the question must be answered in order to complete the questionnaire.
204     */
205    public boolean isRequired() {
206        return required;
207    }
208
209    /**
210     * Returns an indicative of the format for the data to answer.
211     *
212     * @return format for the data to answer.
213     * @see Type
214     */
215    public Type getType() {
216        return type;
217    }
218
219    /**
220     * Returns a List of the default values of the question if the question is part
221     * of a form to fill out. Otherwise, returns a List of the answered values of
222     * the question.
223     *
224     * @return a List of the default values or answered values of the question.
225     */
226    public List<String> getValues() {
227        synchronized (values) {
228            return Collections.unmodifiableList(new ArrayList<String>(values));
229        }
230    }
231
232    /**
233     * Returns the variable name that the question is filling out.
234     * <p>
235     * According to XEP-4 § 3.2 the variable name (the 'var' attribute)
236     * "uniquely identifies the field in the context of the form" (if the field is not of type 'fixed', in which case
237     * the field "MAY possess a 'var' attribute")
238     * </p>
239     * 
240     * @return the variable name of the question.
241     */
242    public String getVariable() {
243        return variable;
244    }
245
246    /**
247     * @return the validateElement
248     */
249    public ValidateElement getValidateElement() {
250        return validateElement;
251    }
252
253    /**
254     * Sets a description that provides extra clarification about the question. This information
255     * could be presented to the user either in tool-tip, help button, or as a section of text
256     * before the question.<p>
257     * <p/>
258     * If the question is of type FIXED then the description should remain empty.
259     *
260     * @param description provides extra clarification about the question.
261     */
262    public void setDescription(String description) {
263        this.description = description;
264    }
265
266    /**
267     * Sets the label of the question which should give enough information to the user to
268     * fill out the form.
269     *
270     * @param label the label of the question.
271     */
272    public void setLabel(String label) {
273        this.label = label;
274    }
275
276    /**
277     * Sets if the question must be answered in order to complete the questionnaire.
278     *
279     * @param required if the question must be answered in order to complete the questionnaire.
280     */
281    public void setRequired(boolean required) {
282        this.required = required;
283    }
284
285    /**
286     * @param validateElement the validateElement to set
287     */
288    public void setValidateElement(ValidateElement validateElement) {
289        validateElement.checkConsistency(this);
290        this.validateElement = validateElement;
291    }
292
293    /**
294     * Sets an indicative of the format for the data to answer.
295     * <p>
296     * This method will throw an IllegalArgumentException if type is 'fixed'. To create FormFields of type 'fixed' use
297     * {@link #FormField()} instead.
298     * </p>
299     *
300     * @param type an indicative of the format for the data to answer.
301     * @see Type
302     * @throws IllegalArgumentException if type is 'fixed'.
303     */
304    public void setType(Type type) {
305        if (type == Type.fixed) {
306            throw new IllegalArgumentException("Can not set type to fixed, use FormField constructor without arguments instead.");
307        }
308        this.type = type;
309    }
310
311    /**
312     * Adds a default value to the question if the question is part of a form to fill out.
313     * Otherwise, adds an answered value to the question.
314     *
315     * @param value a default value or an answered value of the question.
316     */
317    public void addValue(String value) {
318        synchronized (values) {
319            values.add(value);
320        }
321    }
322
323    /**
324     * Adds a default values to the question if the question is part of a form to fill out.
325     * Otherwise, adds an answered values to the question.
326     *
327     * @param newValues default values or an answered values of the question.
328     */
329    public void addValues(List<String> newValues) {
330        synchronized (values) {
331            values.addAll(newValues);
332        }
333    }
334
335    /**
336     * Removes all the values of the field.
337     */
338    protected void resetValues() {
339        synchronized (values) {
340            values.removeAll(new ArrayList<String>(values));
341        }
342    }
343
344    /**
345     * Adss an available options to the question that the user has in order to answer
346     * the question.
347     *
348     * @param option a new available option for the question.
349     */
350    public void addOption(Option option) {
351        synchronized (options) {
352            options.add(option);
353        }
354    }
355
356    @Override
357    public String getElementName() {
358        return ELEMENT;
359    }
360
361    public XmlStringBuilder toXML() {
362        XmlStringBuilder buf = new XmlStringBuilder(this);
363        // Add attributes
364        buf.optAttribute("label", getLabel());
365        buf.optAttribute("var", getVariable());
366        buf.optAttribute("type", getType());
367        buf.rightAngleBracket();
368        // Add elements
369        buf.optElement("desc", getDescription());
370        buf.condEmptyElement(isRequired(), "required");
371        // Loop through all the values and append them to the string buffer
372        for (String value : getValues()) {
373            buf.element("value", value);
374        }
375        // Loop through all the values and append them to the string buffer
376        for (Option option : getOptions()) {
377            buf.append(option.toXML());
378        }
379        buf.optElement(validateElement);
380        buf.closeElement(this);
381        return buf;
382    }
383
384    @Override
385    public boolean equals(Object obj) {
386        if (obj == null)
387            return false;
388        if (obj == this)
389            return true;
390        if (!(obj instanceof FormField))
391            return false;
392
393        FormField other = (FormField) obj;
394
395        return toXML().equals(other.toXML());
396    }
397
398    @Override
399    public int hashCode() {
400        return toXML().hashCode();
401    }
402
403    /**
404     * Represents the available option of a given FormField.
405     *
406     * @author Gaston Dombiak
407     */
408    public static class Option implements NamedElement {
409
410        public static final String ELEMENT = "option";
411
412        private final String value;
413        private String label;
414
415        public Option(String value) {
416            this.value = value;
417        }
418
419        public Option(String label, String value) {
420            this.label = label;
421            this.value = value;
422        }
423
424        /**
425         * Returns the label that represents the option.
426         *
427         * @return the label that represents the option.
428         */
429        public String getLabel() {
430            return label;
431        }
432
433        /**
434         * Returns the value of the option.
435         *
436         * @return the value of the option.
437         */
438        public String getValue() {
439            return value;
440        }
441
442        @Override
443        public String toString() {
444            return getLabel();
445        }
446
447        @Override
448        public String getElementName() {
449            return ELEMENT;
450        }
451
452        public XmlStringBuilder toXML() {
453            XmlStringBuilder xml = new XmlStringBuilder(this);
454            // Add attribute
455            xml.optAttribute("label", getLabel());
456            xml.rightAngleBracket();
457
458            // Add element
459            xml.element("value", getValue());
460
461            xml.closeElement(this);
462            return xml;
463        }
464
465        @Override
466        public boolean equals(Object obj) {
467            if (obj == null)
468                return false;
469            if (obj == this)
470                return true;
471            if (obj.getClass() != getClass())
472                return false;
473
474            Option other = (Option) obj;
475
476            if (!value.equals(other.value))
477                return false;
478
479            String thisLabel = label == null ? "" : label;
480            String otherLabel = other.label == null ? "" : other.label;
481
482            if (!thisLabel.equals(otherLabel))
483                return false;
484
485            return true;
486        }
487
488        @Override
489        public int hashCode() {
490            int result = 1;
491            result = 37 * result + value.hashCode();
492            result = 37 * result + (label == null ? 0 : label.hashCode());
493            return result;
494        }
495    }
496}