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