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 java.util.ArrayList; 021import java.util.Collection; 022import java.util.Collections; 023import java.util.LinkedHashMap; 024import java.util.List; 025import java.util.Locale; 026import java.util.Map; 027 028import org.jivesoftware.smack.packet.Element; 029import org.jivesoftware.smack.packet.ExtensionElement; 030import org.jivesoftware.smack.packet.Stanza; 031import org.jivesoftware.smack.util.XmlStringBuilder; 032 033import org.jivesoftware.smackx.xdata.FormField; 034 035/** 036 * Represents a form that could be use for gathering data as well as for reporting data 037 * returned from a search. 038 * 039 * @author Gaston Dombiak 040 */ 041public class DataForm implements ExtensionElement { 042 043 public static final String NAMESPACE = "jabber:x:data"; 044 public static final String ELEMENT = "x"; 045 046 public enum Type { 047 /** 048 * This stanza contains a form to fill out. Display it to the user (if your program can). 049 */ 050 form, 051 052 /** 053 * The form is filled out, and this is the data that is being returned from the form. 054 */ 055 submit, 056 057 /** 058 * The form was cancelled. Tell the asker that piece of information. 059 */ 060 cancel, 061 062 /** 063 * Data results being returned from a search, or some other query. 064 */ 065 result, 066 ; 067 068 public static Type fromString(String string) { 069 return Type.valueOf(string.toLowerCase(Locale.US)); 070 } 071 } 072 073 private Type type; 074 private String title; 075 private final List<String> instructions = new ArrayList<>(); 076 private ReportedData reportedData; 077 private final List<Item> items = new ArrayList<>(); 078 private final Map<String, FormField> fields = new LinkedHashMap<>(); 079 private final List<Element> extensionElements = new ArrayList<>(); 080 081 public DataForm(Type type) { 082 this.type = type; 083 } 084 085 /** 086 * Returns the meaning of the data within the context. The data could be part of a form 087 * to fill out, a form submission or data results. 088 * 089 * @return the form's type. 090 */ 091 public Type getType() { 092 return type; 093 } 094 095 /** 096 * Returns the description of the data. It is similar to the title on a web page or an X 097 * window. You can put a <title/> on either a form to fill out, or a set of data results. 098 * 099 * @return description of the data. 100 */ 101 public String getTitle() { 102 return title; 103 } 104 105 /** 106 * Returns a List of the list of instructions that explain how to fill out the form and 107 * what the form is about. The dataform could include multiple instructions since each 108 * instruction could not contain newlines characters. Join the instructions together in order 109 * to show them to the user. 110 * 111 * @return a List of the list of instructions that explain how to fill out the form. 112 */ 113 public List<String> getInstructions() { 114 synchronized (instructions) { 115 return Collections.unmodifiableList(new ArrayList<>(instructions)); 116 } 117 } 118 119 /** 120 * Returns the fields that will be returned from a search. 121 * 122 * @return fields that will be returned from a search. 123 */ 124 public ReportedData getReportedData() { 125 return reportedData; 126 } 127 128 /** 129 * Returns a List of the items returned from a search. 130 * 131 * @return a List of the items returned from a search. 132 */ 133 public List<Item> getItems() { 134 synchronized (items) { 135 return Collections.unmodifiableList(new ArrayList<>(items)); 136 } 137 } 138 139 /** 140 * Returns a List of the fields that are part of the form. 141 * 142 * @return a List of the fields that are part of the form. 143 */ 144 public List<FormField> getFields() { 145 synchronized (fields) { 146 return new ArrayList<>(fields.values()); 147 } 148 } 149 150 /** 151 * Return the form field with the given variable name or null. 152 * 153 * @param variableName 154 * @return the form field or null. 155 * @since 4.1 156 */ 157 public FormField getField(String variableName) { 158 synchronized (fields) { 159 return fields.get(variableName); 160 } 161 } 162 163 /** 164 * Check if a form field with the given variable name exists. 165 * 166 * @param variableName 167 * @return true if a form field with the variable name exists, false otherwise. 168 * @since 4.2 169 */ 170 public boolean hasField(String variableName) { 171 synchronized (fields) { 172 return fields.containsKey(variableName); 173 } 174 } 175 176 @Override 177 public String getElementName() { 178 return ELEMENT; 179 } 180 181 @Override 182 public String getNamespace() { 183 return NAMESPACE; 184 } 185 186 /** 187 * Sets the description of the data. It is similar to the title on a web page or an X window. 188 * You can put a <title/> on either a form to fill out, or a set of data results. 189 * 190 * @param title description of the data. 191 */ 192 public void setTitle(String title) { 193 this.title = title; 194 } 195 196 /** 197 * Sets the list of instructions that explain how to fill out the form and what the form is 198 * about. The dataform could include multiple instructions since each instruction could not 199 * contain newlines characters. 200 * 201 * @param instructions list of instructions that explain how to fill out the form. 202 */ 203 public void setInstructions(List<String> instructions) { 204 synchronized (this.instructions) { 205 this.instructions.clear(); 206 this.instructions.addAll(instructions); 207 } 208 } 209 210 /** 211 * Sets the fields that will be returned from a search. 212 * 213 * @param reportedData the fields that will be returned from a search. 214 */ 215 public void setReportedData(ReportedData reportedData) { 216 this.reportedData = reportedData; 217 } 218 219 /** 220 * Adds a new field as part of the form. 221 * 222 * @param field the field to add to the form. 223 */ 224 public void addField(FormField field) { 225 String fieldVariableName = field.getVariable(); 226 // Form field values must be unique unless they are of type 'fixed', in 227 // which case their variable name may be 'null', and therefore could 228 // appear multiple times within the same form. 229 if (fieldVariableName != null && hasField(fieldVariableName)) { 230 throw new IllegalArgumentException("This data form already contains a form field with the variable name '" 231 + fieldVariableName + "'"); 232 } 233 synchronized (fields) { 234 fields.put(fieldVariableName, field); 235 } 236 } 237 238 /** 239 * Add the given fields to this form. 240 * 241 * @param fieldsToAdd 242 * @return true if a field was overridden. 243 * @since 4.3.0 244 */ 245 public boolean addFields(Collection<FormField> fieldsToAdd) { 246 boolean fieldOverridden = false; 247 synchronized (fields) { 248 for (FormField field : fieldsToAdd) { 249 FormField previousField = fields.put(field.getVariable(), field); 250 if (previousField != null) { 251 fieldOverridden = true; 252 } 253 } 254 } 255 return fieldOverridden; 256 } 257 258 /** 259 * Adds a new instruction to the list of instructions that explain how to fill out the form 260 * and what the form is about. The dataform could include multiple instructions since each 261 * instruction could not contain newlines characters. 262 * 263 * @param instruction the new instruction that explain how to fill out the form. 264 */ 265 public void addInstruction(String instruction) { 266 synchronized (instructions) { 267 instructions.add(instruction); 268 } 269 } 270 271 /** 272 * Adds a new item returned from a search. 273 * 274 * @param item the item returned from a search. 275 */ 276 public void addItem(Item item) { 277 synchronized (items) { 278 items.add(item); 279 } 280 } 281 282 public void addExtensionElement(Element element) { 283 extensionElements.add(element); 284 } 285 286 public List<Element> getExtensionElements() { 287 return Collections.unmodifiableList(extensionElements); 288 } 289 290 /** 291 * Returns the hidden FORM_TYPE field or null if this data form has none. 292 * 293 * @return the hidden FORM_TYPE field or null. 294 * @since 4.1 295 */ 296 public FormField getHiddenFormTypeField() { 297 FormField field = getField(FormField.FORM_TYPE); 298 if (field != null && field.getType() == FormField.Type.hidden) { 299 return field; 300 } 301 return null; 302 } 303 304 /** 305 * Returns true if this DataForm has at least one FORM_TYPE field which is 306 * hidden. This method is used for sanity checks. 307 * 308 * @return true if there is at least one field which is hidden. 309 */ 310 public boolean hasHiddenFormTypeField() { 311 return getHiddenFormTypeField() != null; 312 } 313 314 @Override 315 public XmlStringBuilder toXML(String enclosingNamespace) { 316 XmlStringBuilder buf = new XmlStringBuilder(this); 317 buf.attribute("type", getType()); 318 buf.rightAngleBracket(); 319 320 buf.optElement("title", getTitle()); 321 for (String instruction : getInstructions()) { 322 buf.element("instructions", instruction); 323 } 324 // Append the list of fields returned from a search 325 if (getReportedData() != null) { 326 buf.append(getReportedData().toXML()); 327 } 328 // Loop through all the items returned from a search and append them to the string buffer 329 for (Item item : getItems()) { 330 buf.append(item.toXML()); 331 } 332 // Loop through all the form fields and append them to the string buffer 333 for (FormField field : getFields()) { 334 buf.append(field.toXML(null)); 335 } 336 for (Element element : extensionElements) { 337 buf.append(element.toXML(null)); 338 } 339 buf.closeElement(this); 340 return buf; 341 } 342 343 /** 344 * Get data form from stanza. 345 * @param packet 346 * @return the DataForm or null 347 */ 348 public static DataForm from(Stanza packet) { 349 return (DataForm) packet.getExtension(ELEMENT, NAMESPACE); 350 } 351 352 /** 353 * 354 * Represents the fields that will be returned from a search. This information is useful when 355 * you try to use the jabber:iq:search namespace to return dynamic form information. 356 * 357 * @author Gaston Dombiak 358 */ 359 public static class ReportedData { 360 public static final String ELEMENT = "reported"; 361 362 private List<FormField> fields = new ArrayList<>(); 363 364 public ReportedData(List<FormField> fields) { 365 this.fields = fields; 366 } 367 368 /** 369 * Returns the fields returned from a search. 370 * 371 * @return the fields returned from a search. 372 */ 373 public List<FormField> getFields() { 374 return Collections.unmodifiableList(new ArrayList<>(fields)); 375 } 376 377 public CharSequence toXML() { 378 XmlStringBuilder buf = new XmlStringBuilder(); 379 buf.openElement(ELEMENT); 380 // Loop through all the form items and append them to the string buffer 381 for (FormField field : getFields()) { 382 buf.append(field.toXML(null)); 383 } 384 buf.closeElement(ELEMENT); 385 return buf; 386 } 387 } 388 389 /** 390 * 391 * Represents items of reported data. 392 * 393 * @author Gaston Dombiak 394 */ 395 public static class Item { 396 public static final String ELEMENT = "item"; 397 398 private List<FormField> fields = new ArrayList<>(); 399 400 public Item(List<FormField> fields) { 401 this.fields = fields; 402 } 403 404 /** 405 * Returns the fields that define the data that goes with the item. 406 * 407 * @return the fields that define the data that goes with the item. 408 */ 409 public List<FormField> getFields() { 410 return Collections.unmodifiableList(new ArrayList<>(fields)); 411 } 412 413 public CharSequence toXML() { 414 XmlStringBuilder buf = new XmlStringBuilder(); 415 buf.openElement(ELEMENT); 416 // Loop through all the form items and append them to the string buffer 417 for (FormField field : getFields()) { 418 buf.append(field.toXML(null)); 419 } 420 buf.closeElement(ELEMENT); 421 return buf; 422 } 423 } 424}