diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java index 6da004405..a5838083a 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/CollectionUtil.java @@ -1,6 +1,6 @@ /** * - * Copyright 2015-2018 Florian Schmaus + * Copyright 2015-2019 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,4 +49,10 @@ public class CollectionUtil { public interface Predicate { boolean test(T t); } + + public static ArrayList newListWith(Collection collection) { + ArrayList arrayList = new ArrayList<>(collection.size()); + arrayList.addAll(collection); + return arrayList; + } } diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/MultiMap.java b/smack-core/src/main/java/org/jivesoftware/smack/util/MultiMap.java index fb5ec3ad7..334695982 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/MultiMap.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/MultiMap.java @@ -22,6 +22,7 @@ import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Set; /** @@ -57,7 +58,11 @@ public class MultiMap { * @param size the initial capacity. */ public MultiMap(int size) { - map = new LinkedHashMap<>(size); + this(new LinkedHashMap<>(size)); + } + + private MultiMap(Map> map) { + this.map = map; } public int size() { @@ -235,6 +240,18 @@ public class MultiMap { return entrySet; } + public MultiMap asUnmodifiableMultiMap() { + LinkedHashMap> mapCopy = new LinkedHashMap<>(map.size()); + for (Entry> entry : map.entrySet()) { + K key = entry.getKey(); + List values = entry.getValue(); + + mapCopy.put(key, Collections.unmodifiableList(values)); + } + + return new MultiMap(Collections.unmodifiableMap(mapCopy)); + } + private static final class SimpleMapEntry implements Map.Entry { private final K key; diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java index e710f52e2..2cf4496db 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/XmlStringBuilder.java @@ -273,6 +273,15 @@ public class XmlStringBuilder implements Appendable, CharSequence, Element { return this; } + public > XmlStringBuilder attribute(String name, E value, E implicitDefault) { + if (value == null || value == implicitDefault) { + return this; + } + + attribute(name, value.toString()); + return this; + } + public XmlStringBuilder attribute(String name, int value) { assert name != null; return attribute(name, String.valueOf(value)); diff --git a/smack-core/src/test/java/org/jivesoftware/smack/test/util/SmackTestSuite.java b/smack-core/src/test/java/org/jivesoftware/smack/test/util/SmackTestSuite.java index ae60c4543..155954c0e 100644 --- a/smack-core/src/test/java/org/jivesoftware/smack/test/util/SmackTestSuite.java +++ b/smack-core/src/test/java/org/jivesoftware/smack/test/util/SmackTestSuite.java @@ -18,6 +18,7 @@ package org.jivesoftware.smack.test.util; import java.util.Base64; +import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.util.stringencoder.Base64.Encoder; /** @@ -27,6 +28,7 @@ import org.jivesoftware.smack.util.stringencoder.Base64.Encoder; public class SmackTestSuite { static { + SmackConfiguration.getVersion(); org.jivesoftware.smack.util.stringencoder.Base64.setEncoder(new Encoder() { @Override diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java index 3aa0c614f..692b054fc 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/mam/MamManager.java @@ -21,6 +21,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.WeakHashMap; @@ -295,7 +296,7 @@ public final class MamManager extends Manager { public static final class Builder { private String node; - private final Map formFields = new HashMap<>(8); + private final Map formFields = new LinkedHashMap<>(8); private int maxResults = -1; @@ -329,8 +330,9 @@ public final class MamManager extends Manager { return this; } - FormField formField = new FormField(FORM_FIELD_START); - formField.addValue(start); + FormField formField = FormField.builder(FORM_FIELD_START) + .addValue(start) + .build(); formFields.put(formField.getVariable(), formField); FormField endFormField = formFields.get(FORM_FIELD_END); @@ -356,8 +358,9 @@ public final class MamManager extends Manager { return this; } - FormField formField = new FormField(FORM_FIELD_END); - formField.addValue(end); + FormField formField = FormField.builder(FORM_FIELD_END) + .addValue(end) + .build(); formFields.put(formField.getVariable(), formField); FormField startFormField = formFields.get(FORM_FIELD_START); @@ -469,9 +472,9 @@ public final class MamManager extends Manager { } private static FormField getWithFormField(Jid withJid) { - FormField formField = new FormField(FORM_FIELD_WITH); - formField.addValue(withJid.toString()); - return formField; + return FormField.builder(FORM_FIELD_WITH) + .addValue(withJid.toString()) + .build(); } public MamQuery queryMostRecentPage(Jid jid, int max) throws NoResponseException, XMPPErrorException, @@ -711,9 +714,7 @@ public final class MamManager extends Manager { } private static DataForm getNewMamForm() { - FormField field = new FormField(FormField.FORM_TYPE); - field.setType(FormField.Type.hidden); - field.addValue(MamElements.NAMESPACE); + FormField field = FormField.hiddenFormType(MamElements.NAMESPACE); DataForm form = new DataForm(DataForm.Type.submit); form.addField(field); return form; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/push_notifications/element/EnablePushNotificationsIQ.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/push_notifications/element/EnablePushNotificationsIQ.java index 5dcf891e2..577b1ab2c 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/push_notifications/element/EnablePushNotificationsIQ.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/push_notifications/element/EnablePushNotificationsIQ.java @@ -100,16 +100,19 @@ public class EnablePushNotificationsIQ extends IQ { if (publishOptions != null) { DataForm dataForm = new DataForm(DataForm.Type.submit); - FormField formTypeField = new FormField("FORM_TYPE"); + // TODO: Shouldn't this use some potentially existing PubSub API? Also FORM_TYPE fields are usually of type + // 'hidden', but the examples in XEP-0357 do also not set the value to hidden and FORM_TYPE itself appears + // to be more convention than specification. + FormField.Builder formTypeField = FormField.builder("FORM_TYPE"); formTypeField.addValue(PubSub.NAMESPACE + "#publish-options"); - dataForm.addField(formTypeField); + dataForm.addField(formTypeField.build()); Iterator> publishOptionsIterator = publishOptions.entrySet().iterator(); while (publishOptionsIterator.hasNext()) { Map.Entry pairVariableValue = publishOptionsIterator.next(); - FormField field = new FormField(pairVariableValue.getKey()); + FormField.Builder field = FormField.builder(pairVariableValue.getKey()); field.addValue(pairVariableValue.getValue()); - dataForm.addField(field); + dataForm.addField(field.build()); } xml.element(dataForm); diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java index ca6859eee..264319c0e 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/mam/RetrieveFormFieldsTest.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016 Fernando Ramirez, 2018 Florian Schmaus + * Copyright 2016 Fernando Ramirez, 2018-2019 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ public class RetrieveFormFieldsTest extends MamTest { private static final String additionalFieldsStanza = "" + "" + "" + MamElements.NAMESPACE + "" + "" - + "" + "Hi" + "" + + "" + "Hi" + "" + "" + "Hi2" + "" + ""; @@ -50,13 +50,15 @@ public class RetrieveFormFieldsTest extends MamTest { @Test public void checkAddAdditionalFieldsStanza() throws Exception { - FormField field1 = new FormField("urn:example:xmpp:free-text-search"); - field1.setType(FormField.Type.text_single); - field1.addValue("Hi"); + FormField field1 = FormField.builder("urn:example:xmpp:free-text-search") + .setType(FormField.Type.text_single) + .addValue("Hi") + .build(); - FormField field2 = new FormField("urn:example:xmpp:stanza-content"); - field2.setType(FormField.Type.jid_single); - field2.addValue("Hi2"); + FormField field2 = FormField.builder("urn:example:xmpp:stanza-content") + .setType(FormField.Type.jid_single) + .addValue("Hi2") + .build(); MamQueryArgs mamQueryArgs = MamQueryArgs.builder() .withAdditionalFormField(field1) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/admin/ServiceAdministrationManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/admin/ServiceAdministrationManager.java index ebc17dd29..cafcb5b36 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/admin/ServiceAdministrationManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/admin/ServiceAdministrationManager.java @@ -1,6 +1,6 @@ /** * - * Copyright 2016 Florian Schmaus + * Copyright 2016-2019 Florian Schmaus * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,11 +30,9 @@ import org.jivesoftware.smack.XMPPException.XMPPErrorException; import org.jivesoftware.smackx.commands.AdHocCommandManager; import org.jivesoftware.smackx.commands.RemoteCommand; import org.jivesoftware.smackx.xdata.Form; -import org.jivesoftware.smackx.xdata.FormField; import org.jxmpp.jid.EntityBareJid; import org.jxmpp.jid.Jid; -import org.jxmpp.jid.util.JidUtil; public class ServiceAdministrationManager extends Manager { @@ -76,14 +74,9 @@ public class ServiceAdministrationManager extends Manager { Form answerForm = command.getForm().createAnswerForm(); - FormField accountJidField = answerForm.getField("accountjid"); - accountJidField.addValue(userJid.toString()); - - FormField passwordField = answerForm.getField("password"); - passwordField.addValue(password); - - FormField passwordVerifyField = answerForm.getField("password-verify"); - passwordVerifyField.addValue(password); + answerForm.setAnswer("accountjid", userJid); + answerForm.setAnswer("password", password); + answerForm.setAnswer("password-verify", password); command.execute(answerForm); assert (command.isCompleted()); @@ -110,8 +103,7 @@ public class ServiceAdministrationManager extends Manager { Form answerForm = command.getForm().createAnswerForm(); - FormField accountJids = answerForm.getField("accountjids"); - accountJids.addValues(JidUtil.toStringList(jidsToDelete)); + answerForm.setAnswer("accountjids", jidsToDelete); command.execute(answerForm); assert (command.isCompleted()); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java index 1eeda099e..0d42e230a 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/FileTransferNegotiator.java @@ -226,7 +226,7 @@ public final class FileTransferNegotiator extends Manager { boolean isByteStream = false; boolean isIBB = false; for (FormField.Option option : field.getOptions()) { - variable = option.getValue(); + variable = option.getValueString(); if (variable.equals(Bytestream.NAMESPACE) && !IBB_ONLY) { isByteStream = true; } @@ -369,13 +369,15 @@ public final class FileTransferNegotiator extends Manager { private static DataForm createDefaultInitiationForm() { DataForm form = new DataForm(DataForm.Type.form); - FormField field = new FormField(STREAM_DATA_FIELD_NAME); - field.setType(FormField.Type.list_single); + FormField.Builder fieldBuilder = FormField.builder(); + fieldBuilder.setFieldName(STREAM_DATA_FIELD_NAME) + .setType(FormField.Type.list_single); + if (!IBB_ONLY) { - field.addOption(new FormField.Option(Bytestream.NAMESPACE)); + fieldBuilder.addOption(Bytestream.NAMESPACE); } - field.addOption(new FormField.Option(DataPacketExtension.NAMESPACE)); - form.addField(field); + fieldBuilder.addOption(DataPacketExtension.NAMESPACE); + form.addField(fieldBuilder.build()); return form; } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java index 5537f6f0d..393be25e6 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/filetransfer/StreamNegotiator.java @@ -81,12 +81,12 @@ public abstract class StreamNegotiator extends Manager { response.setStanzaId(streamInitiationOffer.getStanzaId()); DataForm form = new DataForm(DataForm.Type.submit); - FormField field = new FormField( + FormField.Builder field = FormField.builder( FileTransferNegotiator.STREAM_DATA_FIELD_NAME); for (String namespace : namespaces) { field.addValue(namespace); } - form.addField(field); + form.addField(field.build()); response.setFeatureNegotiationForm(form); return response; diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java index fd6457964..f593a2e90 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/muc/MultiUserChat.java @@ -1212,14 +1212,14 @@ public class MultiUserChat { */ public void requestVoice() throws NotConnectedException, InterruptedException { DataForm form = new DataForm(DataForm.Type.submit); - FormField formTypeField = new FormField(FormField.FORM_TYPE); + FormField.Builder formTypeField = FormField.builder(FormField.FORM_TYPE); formTypeField.addValue(MUCInitialPresence.NAMESPACE + "#request"); - form.addField(formTypeField); - FormField requestVoiceField = new FormField("muc#role"); + form.addField(formTypeField.build()); + FormField.Builder requestVoiceField = FormField.builder("muc#role"); requestVoiceField.setType(FormField.Type.text_single); requestVoiceField.setLabel("Requested role"); requestVoiceField.addValue("participant"); - form.addField(requestVoiceField); + form.addField(requestVoiceField.build()); Message message = new Message(room); message.addExtension(form); connection.sendStanza(message); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigureForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigureForm.java index d9cc09e92..162935d81 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigureForm.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/ConfigureForm.java @@ -665,8 +665,10 @@ public class ConfigureForm extends Form { String fieldName = nodeField.getFieldName(); if (getField(fieldName) == null) { - FormField field = new FormField(fieldName); - field.setType(type); + FormField field = FormField.builder() + .setVariable(fieldName) + .setType(type) + .build(); addField(field); } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeForm.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeForm.java index ddd095b3a..a5cb0ab6d 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeForm.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/pubsub/SubscribeForm.java @@ -209,9 +209,9 @@ public class SubscribeForm extends Form { String fieldName = nodeField.getFieldName(); if (getField(fieldName) == null) { - FormField field = new FormField(fieldName); + FormField.Builder field = FormField.builder(fieldName); field.setType(type); - addField(field); + addField(field.build()); } } } diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/search/UserSearch.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/search/UserSearch.java index c99cb6c1a..cf22f3266 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/search/UserSearch.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/search/UserSearch.java @@ -175,7 +175,7 @@ public class UserSearch extends SimpleIQ { if (eventType == XmlPullParser.Event.START_ELEMENT && !parser.getNamespace().equals("jabber:x:data")) { String name = parser.getName(); - FormField field = new FormField(name); + FormField.Builder field = FormField.builder(name); // Handle hard coded values. if (name.equals("first")) { @@ -192,7 +192,7 @@ public class UserSearch extends SimpleIQ { } field.setType(FormField.Type.text_single); - dataForm.addField(field); + dataForm.addField(field.build()); } else if (eventType == XmlPullParser.Event.END_ELEMENT) { if (parser.getName().equals("query")) { diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/Form.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/Form.java index 3e816d478..14be9d63c 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/Form.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/Form.java @@ -17,6 +17,7 @@ package org.jivesoftware.smackx.xdata; import java.util.ArrayList; +import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.StringTokenizer; @@ -107,7 +108,7 @@ public class Form { * @throws IllegalArgumentException if the form does not include the specified variable or * if the answer type does not correspond with the field type.. */ - public void setAnswer(String variable, String value) { + public void setAnswer(String variable, CharSequence value) { FormField field = getField(variable); if (field == null) { throw new IllegalArgumentException("Field not found for the specified variable name."); @@ -260,8 +261,9 @@ public class Form { throw new IllegalStateException("Cannot set an answer if the form is not of type " + "\"submit\""); } - field.resetValues(); - field.addValue(value.toString()); + + FormField filledOutfield = field.buildAnswer().addValue(value.toString()).build(); + dataForm.replaceField(filledOutfield); } /** @@ -277,7 +279,7 @@ public class Form { * @throws IllegalStateException if the form is not of type "submit". * @throws IllegalArgumentException if the form does not include the specified variable. */ - public void setAnswer(String variable, List values) { + public void setAnswer(String variable, Collection values) { if (!isSubmitType()) { throw new IllegalStateException("Cannot set an answer if the form is not of type " + "\"submit\""); @@ -295,10 +297,9 @@ public class Form { default: throw new IllegalArgumentException("This field only accept list of values."); } - // Clear the old values - field.resetValues(); - // Set the new values. The string representation of each value will be actually used. - field.addValues(values); + + FormField filledOutfield = field.buildAnswer().addValues(values).build(); + dataForm.replaceField(filledOutfield); } else { throw new IllegalArgumentException("Couldn't find a field for the specified variable."); @@ -321,12 +322,12 @@ public class Form { } FormField field = getField(variable); if (field != null) { - // Clear the old values - field.resetValues(); + FormField.Builder filledOutFormFieldBuilder = field.buildAnswer(); // Set the default value for (CharSequence value : field.getValues()) { - field.addValue(value); + filledOutFormFieldBuilder.addValue(value); } + dataForm.replaceField(filledOutFormFieldBuilder.build()); } else { throw new IllegalArgumentException("Couldn't find a field for the specified variable."); @@ -497,9 +498,9 @@ public class Form { // Add to the new form any type of field that includes a variable. // Note: The fields of type FIXED are the only ones that don't specify a variable if (field.getVariable() != null) { - FormField newField = new FormField(field.getVariable()); + FormField.Builder newField = FormField.builder(field.getVariable()); newField.setType(field.getType()); - form.addField(newField); + form.addField(newField.build()); // Set the answer ONLY to the hidden fields if (field.getType() == FormField.Type.hidden) { // Since a hidden field could have many values we need to collect them diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java index cf62a5274..08433bccb 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java @@ -1,6 +1,6 @@ /** * - * Copyright 2003-2007 Jive Software. + * Copyright 2003-2007 Jive Software, 2019 Florian Schmaus. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,16 +19,21 @@ package org.jivesoftware.smackx.xdata; import java.text.ParseException; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.Iterator; import java.util.List; -import org.jivesoftware.smack.packet.NamedElement; +import javax.xml.namespace.QName; + +import org.jivesoftware.smack.packet.FullyQualifiedElement; import org.jivesoftware.smack.packet.XmlEnvironment; -import org.jivesoftware.smack.util.StringUtils; +import org.jivesoftware.smack.util.CollectionUtil; +import org.jivesoftware.smack.util.MultiMap; import org.jivesoftware.smack.util.XmlStringBuilder; -import org.jivesoftware.smackx.xdatavalidation.packet.ValidateElement; +import org.jivesoftware.smackx.xdata.packet.DataForm; import org.jxmpp.util.XmppDateTime; @@ -39,10 +44,14 @@ import org.jxmpp.util.XmppDateTime; * * @author Gaston Dombiak */ -public class FormField implements NamedElement { +public final class FormField implements FullyQualifiedElement { public static final String ELEMENT = "field"; + public static final String NAMESPACE = DataForm.NAMESPACE; + + public static final QName QNAME = new QName(NAMESPACE, ELEMENT); + /** * The constant String "FORM_TYPE". */ @@ -140,33 +149,91 @@ public class FormField implements NamedElement { } } + /** + * The field's name. Put as value in the 'var' attribute of <field/>. + */ private final String variable; - private String description; - private boolean required = false; - private String label; - private Type type; - private final List