/** * * Copyright 2003-2007 Jive Software. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.jivesoftware.smackx.xdata; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.jivesoftware.smack.packet.NamedElement; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.XmlStringBuilder; import org.jivesoftware.smackx.xdatavalidation.packet.ValidateElement; /** * Represents a field of a form. The field could be used to represent a question to complete, * a completed question or a data returned from a search. The exact interpretation of the field * depends on the context where the field is used. * * @author Gaston Dombiak */ public class FormField implements NamedElement { public static final String ELEMENT = "field"; /** * The constant String "FORM_TYPE". */ public static final String FORM_TYPE = "FORM_TYPE"; /** * Form Field Types as defined in XEP-4 § 3.3. * * @see <a href="http://xmpp.org/extensions/xep-0004.html#protocol-fieldtypes">XEP-4 § 3.3 Field Types</a> */ public enum Type { /** * Boolean type. Can be 0 or 1, true or false, yes or no. Default value is 0. * <p> * Note that in XEP-4 this type is called 'boolean', but since that String is a restricted keyword in Java, it * is named 'bool' in Smack. * </p> */ bool, /** * Fixed for putting in text to show sections, or just advertise your web site in the middle of the form. */ fixed, /** * Is not given to the user at all, but returned with the questionnaire. */ hidden, /** * multiple entries for JIDs. */ jid_multi, /** * Jabber ID - choosing a JID from your roster, and entering one based on the rules for a JID. */ jid_single, /** * Given a list of choices, pick one or more. */ list_multi, /** * Given a list of choices, pick one. */ list_single, /** * Multiple lines of text entry. */ text_multi, /** * Instead of showing the user what they typed, you show ***** to protect it. */ text_private, /** * Single line or word of text. */ text_single, ; @Override public String toString() { switch (this) { case bool: return "boolean"; default: return this.name().replace('_', '-'); } } /** * Get a form field type from the given string. If <code>string</code> is null, then null will be returned. * * @param string the string to transform or null. * @return the type or null. */ public static Type fromString(String string) { if (string == null) { return null; } switch (string) { case "boolean": return bool; default: string = string.replace('-', '_'); return Type.valueOf(string); } } } private final String variable; private String description; private boolean required = false; private String label; private Type type; private final List<Option> options = new ArrayList<Option>(); private final List<String> values = new ArrayList<String>(); private ValidateElement validateElement; /** * Creates a new FormField with the variable name that uniquely identifies the field * in the context of the form. * * @param variable the variable name of the question. */ public FormField(String variable) { this.variable = StringUtils.requireNotNullOrEmpty(variable, "Variable must not be null or empty"); } /** * Creates a new FormField of type FIXED. The fields of type FIXED do not define a variable * name. */ public FormField() { this.variable = null; this.type = Type.fixed; } /** * Returns a description that provides extra clarification about the question. This information * could be presented to the user either in tool-tip, help button, or as a section of text * before the question. * <p> * If the question is of type FIXED then the description should remain empty. * </p> * * @return description that provides extra clarification about the question. */ public String getDescription() { return description; } /** * Returns the label of the question which should give enough information to the user to * fill out the form. * * @return label of the question. */ public String getLabel() { return label; } /** * Returns a List of the available options that the user has in order to answer * the question. * * @return List of the available options. */ public List<Option> getOptions() { synchronized (options) { return Collections.unmodifiableList(new ArrayList<Option>(options)); } } /** * Returns true if the question must be answered in order to complete the questionnaire. * * @return true if the question must be answered in order to complete the questionnaire. */ public boolean isRequired() { return required; } /** * Returns an indicative of the format for the data to answer. * * @return format for the data to answer. * @see Type */ public Type getType() { return type; } /** * Returns a List of the default values of the question if the question is part * of a form to fill out. Otherwise, returns a List of the answered values of * the question. * * @return a List of the default values or answered values of the question. */ public List<String> getValues() { synchronized (values) { return Collections.unmodifiableList(new ArrayList<String>(values)); } } /** * Returns the variable name that the question is filling out. * <p> * According to XEP-4 § 3.2 the variable name (the 'var' attribute) * "uniquely identifies the field in the context of the form" (if the field is not of type 'fixed', in which case * the field "MAY possess a 'var' attribute") * </p> * * @return the variable name of the question. */ public String getVariable() { return variable; } /** * Get validate element. * * @return the validateElement */ public ValidateElement getValidateElement() { return validateElement; } /** * Sets a description that provides extra clarification about the question. This information * could be presented to the user either in tool-tip, help button, or as a section of text * before the question. * <p> * If the question is of type FIXED then the description should remain empty. * </p> * * @param description provides extra clarification about the question. */ public void setDescription(String description) { this.description = description; } /** * Sets the label of the question which should give enough information to the user to * fill out the form. * * @param label the label of the question. */ public void setLabel(String label) { this.label = label; } /** * Sets if the question must be answered in order to complete the questionnaire. * * @param required if the question must be answered in order to complete the questionnaire. */ public void setRequired(boolean required) { this.required = required; } /** * Set validate element. * @param validateElement the validateElement to set */ public void setValidateElement(ValidateElement validateElement) { validateElement.checkConsistency(this); this.validateElement = validateElement; } /** * Sets an indicative of the format for the data to answer. * <p> * This method will throw an IllegalArgumentException if type is 'fixed'. To create FormFields of type 'fixed' use * {@link #FormField()} instead. * </p> * * @param type an indicative of the format for the data to answer. * @see Type * @throws IllegalArgumentException if type is 'fixed'. */ public void setType(Type type) { if (type == Type.fixed) { throw new IllegalArgumentException("Can not set type to fixed, use FormField constructor without arguments instead."); } this.type = type; } /** * Adds a default value to the question if the question is part of a form to fill out. * Otherwise, adds an answered value to the question. * * @param value a default value or an answered value of the question. */ public void addValue(String value) { synchronized (values) { values.add(value); } } /** * Adds a default values to the question if the question is part of a form to fill out. * Otherwise, adds an answered values to the question. * * @param newValues default values or an answered values of the question. */ public void addValues(List<String> newValues) { synchronized (values) { values.addAll(newValues); } } /** * Removes all the values of the field. */ protected void resetValues() { synchronized (values) { values.removeAll(new ArrayList<String>(values)); } } /** * Adss an available options to the question that the user has in order to answer * the question. * * @param option a new available option for the question. */ public void addOption(Option option) { synchronized (options) { options.add(option); } } @Override public String getElementName() { return ELEMENT; } @Override public XmlStringBuilder toXML() { XmlStringBuilder buf = new XmlStringBuilder(this); // Add attributes buf.optAttribute("label", getLabel()); buf.optAttribute("var", getVariable()); buf.optAttribute("type", getType()); buf.rightAngleBracket(); // Add elements buf.optElement("desc", getDescription()); buf.condEmptyElement(isRequired(), "required"); // Loop through all the values and append them to the string buffer for (String value : getValues()) { buf.element("value", value); } // Loop through all the values and append them to the string buffer for (Option option : getOptions()) { buf.append(option.toXML()); } buf.optElement(validateElement); buf.closeElement(this); return buf; } @Override public boolean equals(Object obj) { if (obj == null) return false; if (obj == this) return true; if (!(obj instanceof FormField)) return false; FormField other = (FormField) obj; return toXML().equals(other.toXML()); } @Override public int hashCode() { return toXML().hashCode(); } /** * Represents the available option of a given FormField. * * @author Gaston Dombiak */ public static class Option implements NamedElement { public static final String ELEMENT = "option"; private final String value; private String label; public Option(String value) { this.value = value; } public Option(String label, String value) { this.label = label; this.value = value; } /** * Returns the label that represents the option. * * @return the label that represents the option. */ public String getLabel() { return label; } /** * Returns the value of the option. * * @return the value of the option. */ public String getValue() { return value; } @Override public String toString() { return getLabel(); } @Override public String getElementName() { return ELEMENT; } @Override public XmlStringBuilder toXML() { XmlStringBuilder xml = new XmlStringBuilder(this); // Add attribute xml.optAttribute("label", getLabel()); xml.rightAngleBracket(); // Add element xml.element("value", getValue()); xml.closeElement(this); return xml; } @Override public boolean equals(Object obj) { if (obj == null) return false; if (obj == this) return true; if (obj.getClass() != getClass()) return false; Option other = (Option) obj; if (!value.equals(other.value)) return false; String thisLabel = label == null ? "" : label; String otherLabel = other.label == null ? "" : other.label; if (!thisLabel.equals(otherLabel)) return false; return true; } @Override public int hashCode() { int result = 1; result = 37 * result + value.hashCode(); result = 37 * result + (label == null ? 0 : label.hashCode()); return result; } } }