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