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}