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