From 41f5cf84359ff26d6a2230a9bfc6a83521efcd8c Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 21 Feb 2018 20:21:10 +0100 Subject: [PATCH 01/11] Remove unused LOGGER from ServiceDiscoveryManager --- .../org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java index e4dc18f3c..c6a33013c 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java @@ -27,7 +27,6 @@ import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.ConcurrentHashMap; -import java.util.logging.Logger; import org.jivesoftware.smack.ConnectionCreationListener; import org.jivesoftware.smack.Manager; @@ -71,8 +70,6 @@ import org.jxmpp.util.cache.ExpirationCache; */ public final class ServiceDiscoveryManager extends Manager { - private static final Logger LOGGER = Logger.getLogger(ServiceDiscoveryManager.class.getName()); - private static final String DEFAULT_IDENTITY_NAME = "Smack"; private static final String DEFAULT_IDENTITY_CATEGORY = "client"; private static final String DEFAULT_IDENTITY_TYPE = "pc"; From ec4be1963ac48a13ef8607b4acb5331890c5b5ea Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Wed, 21 Feb 2018 20:25:44 +0100 Subject: [PATCH 02/11] Deprecate legacy disco-publish API of ServiceDiscoveryManager --- .../smackx/commands/AdHocCommandManager.java | 4 ++++ .../smackx/disco/ServiceDiscoveryManager.java | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommandManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommandManager.java index 80f6a2a02..f86a78c3f 100755 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommandManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/commands/AdHocCommandManager.java @@ -278,7 +278,11 @@ public final class AdHocCommandManager extends Manager { * @throws XMPPException if the operation failed for some reason. * @throws SmackException if there was no response from the server. * @throws InterruptedException + * @deprecated This method uses no longer existent XEP-0030 features and will be removed. */ + @SuppressWarnings("deprecation") + @Deprecated + // TODO: Remove in Smack 4.4. public void publishCommands(Jid jid) throws XMPPException, SmackException, InterruptedException { // Collects the commands to publish as items DiscoverItems discoverItems = new DiscoverItems(); diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java index c6a33013c..453fe462d 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/ServiceDiscoveryManager.java @@ -592,7 +592,10 @@ public final class ServiceDiscoveryManager extends Manager { * @throws NoResponseException * @throws NotConnectedException * @throws InterruptedException + * @deprecated The disco-publish feature was removed from XEP-0030 in 2008 in favor of XEP-0060: Publish-Subscribe. */ + @Deprecated + // TODO: Remove in Smack 4.4 public boolean canPublishItems(Jid entityID) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { DiscoverInfo info = discoverInfo(entityID); return canPublishItems(info); @@ -606,7 +609,10 @@ public final class ServiceDiscoveryManager extends Manager { * * @param info the discover info stanza(/packet) to check. * @return true if the server supports publishing of items. + * @deprecated The disco-publish feature was removed from XEP-0030 in 2008 in favor of XEP-0060: Publish-Subscribe. */ + @Deprecated + // TODO: Remove in Smack 4.4 public static boolean canPublishItems(DiscoverInfo info) { return info.containsFeature("http://jabber.org/protocol/disco#publish"); } @@ -623,7 +629,10 @@ public final class ServiceDiscoveryManager extends Manager { * @throws NoResponseException * @throws NotConnectedException * @throws InterruptedException + * @deprecated The disco-publish feature was removed from XEP-0030 in 2008 in favor of XEP-0060: Publish-Subscribe. */ + @Deprecated + // TODO: Remove in Smack 4.4 public void publishItems(Jid entityID, DiscoverItems discoverItems) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { publishItems(entityID, null, discoverItems); } @@ -641,7 +650,10 @@ public final class ServiceDiscoveryManager extends Manager { * @throws NoResponseException if there was no response from the server. * @throws NotConnectedException * @throws InterruptedException + * @deprecated The disco-publish feature was removed from XEP-0030 in 2008 in favor of XEP-0060: Publish-Subscribe. */ + @Deprecated + // TODO: Remove in Smack 4.4 public void publishItems(Jid entityID, String node, DiscoverItems discoverItems) throws NoResponseException, XMPPErrorException, NotConnectedException, InterruptedException { discoverItems.setType(IQ.Type.set); From b3b76b9ff49b79d4d49ddbb046263c2476f2f3d8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 21 Feb 2018 20:28:26 +0100 Subject: [PATCH 03/11] Add support for XEP-0359: Stable and Unique Stanza IDs Fixes SMACK-798 --- documentation/extensions/index.md | 1 + .../sid/StableUniqueStanzaIdManager.java | 118 ++++++++++++++++++ .../smackx/sid/element/OriginIdElement.java | 84 +++++++++++++ .../sid/element/StableAndUniqueIdElement.java | 44 +++++++ .../smackx/sid/element/StanzaIdElement.java | 81 ++++++++++++ .../smackx/sid/element/package-info.java | 20 +++ .../jivesoftware/smackx/sid/package-info.java | 20 +++ .../smackx/sid/provider/OriginIdProvider.java | 32 +++++ .../smackx/sid/provider/StanzaIdProvider.java | 34 +++++ .../smackx/sid/provider/package-info.java | 20 +++ .../experimental.providers | 12 ++ .../experimental.xml | 1 + .../smackx/sid/StableUniqueStanzaIdTest.java | 84 +++++++++++++ 13 files changed, 551 insertions(+) create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdManager.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/StableAndUniqueIdElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/StanzaIdElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/sid/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/OriginIdProvider.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/StanzaIdProvider.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/package-info.java create mode 100644 smack-experimental/src/test/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdTest.java diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index 687fabc0c..c00592041 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -92,6 +92,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental | [Internet of Things - Discovery](iot.md) | [XEP-0347](http://xmpp.org/extensions/xep-0347.html) | Describes how Things can be installed and discovered by their owners. | | Client State Indication | [XEP-0352](http://xmpp.org/extensions/xep-0352.html) | A way for the client to indicate its active/inactive state. | | [Push Notifications](pushnotifications.md) | [XEP-0357](http://xmpp.org/extensions/xep-0357.html) | Defines a way to manage push notifications from an XMPP Server. | +| Stable and Unique Stanza IDs | [XEP-0359](http://xmpp.org/extensions/xep-0359.html) | This specification describes unique and stable IDs for messages. | | HTTP File Upload | [XEP-0363](http://xmpp.org/extensions/xep-0363.html) | Protocol to request permissions to upload a file to an HTTP server and get a shareable URL. | | [Multi-User Chat Light](muclight.md) | [XEP-xxxx](http://mongooseim.readthedocs.io/en/latest/open-extensions/xeps/xep-muc-light.html) | Multi-User Chats for mobile XMPP applications and specific enviroment. | | [OMEMO Multi End Message and Object Encryption](omemo.md) | [XEP-XXXX](https://conversations.im/omemo/xep-omemo.html) | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). | diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdManager.java new file mode 100644 index 000000000..966861634 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdManager.java @@ -0,0 +1,118 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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.sid; + +import java.util.Map; +import java.util.WeakHashMap; + +import org.jivesoftware.smack.ConnectionCreationListener; +import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.StanzaListener; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smack.XMPPConnectionRegistry; +import org.jivesoftware.smack.filter.AndFilter; +import org.jivesoftware.smack.filter.MessageTypeFilter; +import org.jivesoftware.smack.filter.NotFilter; +import org.jivesoftware.smack.filter.StanzaExtensionFilter; +import org.jivesoftware.smack.filter.StanzaFilter; +import org.jivesoftware.smack.filter.ToTypeFilter; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; +import org.jivesoftware.smackx.sid.element.OriginIdElement; + +public final class StableUniqueStanzaIdManager extends Manager { + + public static final String NAMESPACE = "urn:xmpp:sid:0"; + + private static final Map INSTANCES = new WeakHashMap<>(); + + // Filter for outgoing stanzas. + private static final StanzaFilter OUTGOING_FILTER = new AndFilter( + MessageTypeFilter.NORMAL_OR_CHAT_OR_HEADLINE, + ToTypeFilter.ENTITY_FULL_OR_BARE_JID); + + private static final StanzaFilter ORIGIN_ID_FILTER = new StanzaExtensionFilter(OriginIdElement.ELEMENT, NAMESPACE); + + // Listener for outgoing stanzas that adds origin-ids to outgoing stanzas. + private final StanzaListener stanzaListener = new StanzaListener() { + @Override + public void processStanza(Stanza stanza) { + OriginIdElement.addOriginId((Message) stanza); + } + }; + + static { + XMPPConnectionRegistry.addConnectionCreationListener(new ConnectionCreationListener() { + @Override + public void connectionCreated(XMPPConnection connection) { + getInstanceFor(connection); + } + }); + } + + /** + * Private constructor. + * @param connection + */ + private StableUniqueStanzaIdManager(XMPPConnection connection) { + super(connection); + enable(); + } + + /** + * Return an instance of the StableUniqueStanzaIdManager for the given connection. + * + * @param connection xmpp-connection + * @return manager instance for the connection + */ + public static StableUniqueStanzaIdManager getInstanceFor(XMPPConnection connection) { + StableUniqueStanzaIdManager manager = INSTANCES.get(connection); + if (manager == null) { + manager = new StableUniqueStanzaIdManager(connection); + INSTANCES.put(connection, manager); + } + return manager; + } + + /** + * Start appending origin-id elements to outgoing stanzas and add the feature to disco. + */ + public synchronized void enable() { + ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(NAMESPACE); + StanzaFilter filter = new AndFilter(OUTGOING_FILTER, new NotFilter(OUTGOING_FILTER)); + connection().addPacketInterceptor(stanzaListener, filter); + } + + /** + * Stop appending origin-id elements to outgoing stanzas and remove the feature from disco. + */ + public synchronized void disable() { + ServiceDiscoveryManager.getInstanceFor(connection()).removeFeature(NAMESPACE); + connection().removePacketInterceptor(stanzaListener); + } + + /** + * Return true, if we automatically append origin-id elements to outgoing stanzas. + * + * @return true if functionality is enabled, otherwise false. + */ + public synchronized boolean isEnabled() { + ServiceDiscoveryManager disco = ServiceDiscoveryManager.getInstanceFor(connection()); + return disco.includesFeature(NAMESPACE); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java new file mode 100644 index 000000000..e17966cac --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/OriginIdElement.java @@ -0,0 +1,84 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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.sid.element; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager; + +public class OriginIdElement extends StableAndUniqueIdElement { + + public static final String ELEMENT = "origin-id"; + + public OriginIdElement() { + super(); + } + + public OriginIdElement(String id) { + super(id); + } + + /** + * Add an origin-id element to a message and set the stanzas id to the same id as in the origin-id element. + * + * @param message message. + */ + public static OriginIdElement addOriginId(Message message) { + OriginIdElement originId = new OriginIdElement(); + message.addExtension(originId); + // TODO: Find solution to have both the originIds stanzaId and a nice to look at incremental stanzaID. + // message.setStanzaId(originId.getId()); + return originId; + } + + /** + * Return true, if the message contains a origin-id element. + * + * @param message message + * @return true if the message contains a origin-id, false otherwise. + */ + public static boolean hasOriginId(Message message) { + return getOriginId(message) != null; + } + + /** + * Return the origin-id element of a message or null, if absent. + * + * @param message message + * @return origin-id element + */ + public static OriginIdElement getOriginId(Message message) { + return message.getExtension(OriginIdElement.ELEMENT, StableUniqueStanzaIdManager.NAMESPACE); + } + + @Override + public String getNamespace() { + return StableUniqueStanzaIdManager.NAMESPACE; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public CharSequence toXML() { + return new XmlStringBuilder(this) + .attribute(ATTR_ID, getId()) + .closeEmptyElement(); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/StableAndUniqueIdElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/StableAndUniqueIdElement.java new file mode 100644 index 000000000..7c87f4592 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/StableAndUniqueIdElement.java @@ -0,0 +1,44 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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.sid.element; + +import java.util.UUID; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.util.StringUtils; + +public abstract class StableAndUniqueIdElement implements ExtensionElement { + + public static final String ATTR_ID = "id"; + + private final String id; + + public StableAndUniqueIdElement() { + this.id = UUID.randomUUID().toString(); + } + + public String getId() { + return id; + } + + public StableAndUniqueIdElement(String id) { + if (StringUtils.isNullOrEmpty(id)) { + throw new IllegalArgumentException("Argument 'id' cannot be null or empty."); + } + this.id = id; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/StanzaIdElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/StanzaIdElement.java new file mode 100644 index 000000000..6c1e24bba --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/StanzaIdElement.java @@ -0,0 +1,81 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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.sid.element; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager; + +public class StanzaIdElement extends StableAndUniqueIdElement { + + public static final String ELEMENT = "stanza-id"; + public static final String ATTR_BY = "by"; + + private final String by; + + public StanzaIdElement(String by) { + super(); + this.by = by; + } + + public StanzaIdElement(String id, String by) { + super(id); + this.by = by; + } + + /** + * Return true, if a message contains a stanza-id element. + * + * @param message message + * @return true if message contains stanza-id element, otherwise false. + */ + public static boolean hasStanzaId(Message message) { + return getStanzaId(message) != null; + } + + /** + * Return the stanza-id element of a message. + * + * @param message message + * @return stanza-id element of a jid, or null if absent. + */ + public static StanzaIdElement getStanzaId(Message message) { + return message.getExtension(StanzaIdElement.ELEMENT, StableUniqueStanzaIdManager.NAMESPACE); + } + + public String getBy() { + return by; + } + + @Override + public String getNamespace() { + return StableUniqueStanzaIdManager.NAMESPACE; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public XmlStringBuilder toXML() { + return new XmlStringBuilder(this) + .attribute(ATTR_ID, getId()) + .attribute(ATTR_BY, getBy()) + .closeEmptyElement(); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/package-info.java new file mode 100644 index 000000000..7fed273a0 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/element/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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. + */ +/** + * Smack's API for XEP-0359: Stable and Unique Stanza IDs. + */ +package org.jivesoftware.smackx.sid.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/package-info.java new file mode 100644 index 000000000..0f2279331 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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. + */ +/** + * Smack's API for XEP-0359: Stable and Unique Stanza IDs. + */ +package org.jivesoftware.smackx.sid; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/OriginIdProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/OriginIdProvider.java new file mode 100644 index 000000000..19ef6edd7 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/OriginIdProvider.java @@ -0,0 +1,32 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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.sid.provider; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smackx.sid.element.OriginIdElement; + +import org.xmlpull.v1.XmlPullParser; + +public class OriginIdProvider extends ExtensionElementProvider { + + public static final OriginIdProvider TEST_INSTANCE = new OriginIdProvider(); + + @Override + public OriginIdElement parse(XmlPullParser parser, int initialDepth) throws Exception { + return new OriginIdElement(parser.getAttributeValue(null, OriginIdElement.ATTR_ID)); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/StanzaIdProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/StanzaIdProvider.java new file mode 100644 index 000000000..2a3fcad28 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/StanzaIdProvider.java @@ -0,0 +1,34 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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.sid.provider; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smackx.sid.element.StanzaIdElement; + +import org.xmlpull.v1.XmlPullParser; + +public class StanzaIdProvider extends ExtensionElementProvider { + + public static StanzaIdProvider TEST_INSTANCE = new StanzaIdProvider(); + + @Override + public StanzaIdElement parse(XmlPullParser parser, int initialDepth) throws Exception { + String id = parser.getAttributeValue(null, StanzaIdElement.ATTR_ID); + String by = parser.getAttributeValue(null, StanzaIdElement.ATTR_BY); + return new StanzaIdElement(id, by); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/package-info.java new file mode 100644 index 000000000..1ed2658f9 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/sid/provider/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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. + */ +/** + * Smack's API for XEP-0359: Stable and Unique Stanza IDs. + */ +package org.jivesoftware.smackx.sid.provider; diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers index b5c89b3fb..1ef9366a8 100644 --- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers +++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers @@ -211,6 +211,18 @@ org.jivesoftware.smackx.push_notifications.provider.RemoteDisablingProvider + + + stanza-id + urn:xmpp:sid:0 + org.jivesoftware.smackx.sid.provider.StanzaIdProvider + + + origin-id + urn:xmpp:sid:0 + org.jivesoftware.smackx.sid.provider.OriginIdProvider + + markable diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml index ab592717a..8a73c804f 100644 --- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml +++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.xml @@ -6,5 +6,6 @@ org.jivesoftware.smackx.iot.provisioning.IoTProvisioningManager org.jivesoftware.smackx.httpfileupload.HttpFileUploadManager org.jivesoftware.smackx.eme.ExplicitMessageEncryptionManager + org.jivesoftware.smackx.sid.StableUniqueStanzaIdManager diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdTest.java new file mode 100644 index 000000000..cf8ca2f33 --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/sid/StableUniqueStanzaIdTest.java @@ -0,0 +1,84 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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.sid; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertNotNull; +import static junit.framework.TestCase.assertTrue; +import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smack.test.util.TestUtils; +import org.jivesoftware.smackx.sid.element.OriginIdElement; +import org.jivesoftware.smackx.sid.element.StanzaIdElement; +import org.jivesoftware.smackx.sid.provider.OriginIdProvider; +import org.jivesoftware.smackx.sid.provider.StanzaIdProvider; + +import org.junit.Test; + +public class StableUniqueStanzaIdTest extends SmackTestSuite { + + @Test + public void stanzaIdProviderTest() throws Exception { + String xml = ""; + StanzaIdElement element = new StanzaIdElement("de305d54-75b4-431b-adb2-eb6b9e546013", "alice@wonderland.lit"); + assertEquals("de305d54-75b4-431b-adb2-eb6b9e546013", element.getId()); + assertEquals("alice@wonderland.lit", element.getBy()); + assertXMLEqual(xml, element.toXML().toString()); + + StanzaIdElement parsed = StanzaIdProvider.TEST_INSTANCE.parse(TestUtils.getParser(xml)); + assertEquals(element.getId(), parsed.getId()); + assertEquals(element.getBy(), parsed.getBy()); + } + + @Test + public void originIdProviderTest() throws Exception { + String xml = ""; + OriginIdElement element = new OriginIdElement("de305d54-75b4-431b-adb2-eb6b9e546013"); + assertEquals("de305d54-75b4-431b-adb2-eb6b9e546013", element.getId()); + assertXMLEqual(xml, element.toXML().toString()); + + OriginIdElement parsed = OriginIdProvider.TEST_INSTANCE.parse(TestUtils.getParser(xml)); + assertEquals(element.getId(), parsed.getId()); + } + + @Test + public void createOriginIdTest() { + OriginIdElement element = new OriginIdElement(); + assertNotNull(element); + assertEquals(StableUniqueStanzaIdManager.NAMESPACE, element.getNamespace()); + assertEquals(36, element.getId().length()); + } + + @Test + public void fromMessageTest() { + Message message = new Message(); + assertFalse(OriginIdElement.hasOriginId(message)); + assertFalse(StanzaIdElement.hasStanzaId(message)); + + OriginIdElement.addOriginId(message); + + assertTrue(OriginIdElement.hasOriginId(message)); + + StanzaIdElement stanzaId = new StanzaIdElement("alice@wonderland.lit"); + message.addExtension(stanzaId); + assertTrue(StanzaIdElement.hasStanzaId(message)); + assertEquals(stanzaId, StanzaIdElement.getStanzaId(message)); + } +} From a729a7c43b9d3a8bd09744916d718675b6a54a80 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 21 Feb 2018 20:49:01 +0100 Subject: [PATCH 04/11] Add support for XEP-0394: Message Markup Fixes SMACK-794. --- documentation/extensions/index.md | 1 + documentation/extensions/messagemarkup.md | 68 ++++ .../element/BlockQuoteElement.java | 62 ++++ .../element/CodeBlockElement.java | 62 ++++ .../message_markup/element/ListElement.java | 121 +++++++ .../message_markup/element/MarkupElement.java | 312 ++++++++++++++++++ .../message_markup/element/SpanElement.java | 93 ++++++ .../message_markup/element/package-info.java | 25 ++ .../smackx/message_markup/package-info.java | 25 ++ .../provider/MarkupElementProvider.java | 139 ++++++++ .../message_markup/provider/package-info.java | 25 ++ .../experimental.providers | 7 + .../message_markup/MessageMarkupTest.java | 227 +++++++++++++ 13 files changed, 1167 insertions(+) create mode 100644 documentation/extensions/messagemarkup.md create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/BlockQuoteElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/CodeBlockElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/ListElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/MarkupElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/SpanElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/provider/MarkupElementProvider.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/provider/package-info.java create mode 100644 smack-experimental/src/test/java/org/jivesoftware/smackx/message_markup/MessageMarkupTest.java diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index c00592041..46470b84d 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -98,6 +98,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental | [OMEMO Multi End Message and Object Encryption](omemo.md) | [XEP-XXXX](https://conversations.im/omemo/xep-omemo.html) | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). | | [Consistent Color Generation](consistent_colors.md) | [XEP-0392](http://xmpp.org/extensions/xep-0392.html) | Generate consistent colors for identifiers like usernames to provide a consistent user experience. | | Google GCM JSON payload | n/a | Semantically the same as XEP-0335: JSON Containers | +| [Message Markup](messagemarkup.md) | [XEP-0394](http://xmpp.org/extensions/xep-0394.html)| Style message bodies while keeping body and markup information separated. | Legacy Smack Extensions and currently supported XEPs of smack-legacy diff --git a/documentation/extensions/messagemarkup.md b/documentation/extensions/messagemarkup.md new file mode 100644 index 000000000..fb468673e --- /dev/null +++ b/documentation/extensions/messagemarkup.md @@ -0,0 +1,68 @@ +Message Markup +============== + +[Back](index.md) + +[Message Markup (XEP-0394)](https://xmpp.org/extensions/xep-0394.html) can be used as a an alternative to XHTML-IM to style messages, while keeping the body and markup information strictly separated. +This implementation can *not* be used to render message bodies, but will offer a simple to use interface for creating ExtensionElements which encode the markup information. + +## Usage + +The most important class is the `MarkupElement` class, which contains a Builder. + +To start creating a Message Markup Extension, call `MarkupElement.getBuilder()`. +(Almost) all method calls documented below will be made on the builder. + +Whenever a method call receives a `start` and `end` index, `start` represents the first character, which is affected by the styling, while `end` is the character *after* the last affected character. + +### Inline styling + +Currently there are 3 styles available: +* *emphasis*, which should be rendered by a client as *italic*, or **bold** +* *code*, which should be rendered in `monospace` +* *deleted*, which should be rendered as ~~strikethrough~~. + +Those styles are available by calling `builder.setEmphasis(int start, int end)`, +`builder.setDeleted(int start, int end)` and `builder.setCode(int start, int end)`. + +If you want to apply multiple inline styles to a section, you can do the following: +``` +Set spanStyles = new HashSet<>(); +styles.add(SpanElement.SpanStyle.emphasis); +styles.add(SpanElement.SpanStyle.deleted); +builder.addSpan(start, end, spanStyles); +``` + +Note, that spans cannot overlap one another. + +### Block Level Styling + +Available block level styles are: +* Code blocks, which should be rendered as +``` +blocks +of +code +``` + +* Itemized lists, which should render as + * Lists + * with possibly multiple + * entries + +* Block Quotes, which should be rendered by the client + > as quotes, which + >> also can be nested + +To mark a section as code block, call `builder.setCodeBlock(start, end)`. + +To create a list, call `MarkupElement.Builder.ListBuilder lbuilder = builder.beginList()`, which will return a list builder. +On this you can call `lbuilder.addEntry(start, end)` to add an entry. + +Note: If you add an entry, the start value MUST be equal to the end value of the previous added entry! + +To end the list, call `lbuilder.endList()`, which will return the MessageMarkup builder. + +To create a block quote, call `builder.setBlockQuote(start, end)`. + +Note that block level elements MUST NOT overlap each other boundaries, but may be fully contained (nested) within each other. \ No newline at end of file diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/BlockQuoteElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/BlockQuoteElement.java new file mode 100644 index 000000000..fb22e08af --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/BlockQuoteElement.java @@ -0,0 +1,62 @@ +/** + * + * Copyright © 2018 Paul Schaub + * + * 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.message_markup.element; + +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class BlockQuoteElement implements MarkupElement.BlockLevelMarkupElement { + + public static final String ELEMENT = "bquote"; + + private final int start, end; + + /** + * Create a new Block Quote element. + * + * @param start start index + * @param end end index + */ + public BlockQuoteElement(int start, int end) { + this.start = start; + this.end = end; + } + + @Override + public int getStart() { + return start; + } + + @Override + public int getEnd() { + return end; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public XmlStringBuilder toXML() { + XmlStringBuilder xml = new XmlStringBuilder(); + xml.halfOpenElement(this); + xml.attribute(ATTR_START, getStart()); + xml.attribute(ATTR_END, getEnd()); + xml.closeEmptyElement(); + return xml; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/CodeBlockElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/CodeBlockElement.java new file mode 100644 index 000000000..d80d02cae --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/CodeBlockElement.java @@ -0,0 +1,62 @@ +/** + * + * Copyright © 2018 Paul Schaub + * + * 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.message_markup.element; + +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class CodeBlockElement implements MarkupElement.BlockLevelMarkupElement { + + public static final String ELEMENT = "bcode"; + + private final int start, end; + + /** + * Create a new Code Block element. + * + * @param start start index + * @param end end index + */ + public CodeBlockElement(int start, int end) { + this.start = start; + this.end = end; + } + + @Override + public int getStart() { + return start; + } + + @Override + public int getEnd() { + return end; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public XmlStringBuilder toXML() { + XmlStringBuilder xml = new XmlStringBuilder(); + xml.halfOpenElement(this); + xml.attribute(ATTR_START, getStart()); + xml.attribute(ATTR_END, getEnd()); + xml.closeEmptyElement(); + return xml; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/ListElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/ListElement.java new file mode 100644 index 000000000..3f11bba6d --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/ListElement.java @@ -0,0 +1,121 @@ +/** + * + * Copyright © 2018 Paul Schaub + * + * 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.message_markup.element; + +import java.util.Collections; +import java.util.List; + +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class ListElement implements MarkupElement.MarkupChildElement { + + public static final String ELEMENT = "list"; + public static final String ELEM_LI = "li"; + + private final int start, end; + private final List entries; + + /** + * Create a new List element. + * + * @param start start index of the list + * @param end end index of the list + * @param entries list entries + */ + public ListElement(int start, int end, List entries) { + this.start = start; + this.end = end; + this.entries = Collections.unmodifiableList(entries); + } + + @Override + public int getStart() { + return start; + } + + @Override + public int getEnd() { + return end; + } + + /** + * Return a list of all list entries. + * + * @return entries + */ + public List getEntries() { + return entries; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public CharSequence toXML() { + XmlStringBuilder xml = new XmlStringBuilder(); + xml.halfOpenElement(this); + xml.attribute(ATTR_START, getStart()); + xml.attribute(ATTR_END, getEnd()); + xml.rightAngleBracket(); + + for (ListEntryElement li : getEntries()) { + xml.append(li.toXML()); + } + + xml.closeElement(this); + return xml; + } + + public static class ListEntryElement implements NamedElement { + + private final int start; + + /** + * Create a new ListEntry element. + * + * @param start start index + */ + public ListEntryElement(int start) { + this.start = start; + } + + /** + * Return the start index of this entry. + * @return start index + */ + public int getStart() { + return start; + } + + @Override + public String getElementName() { + return ELEM_LI; + } + + @Override + public XmlStringBuilder toXML() { + XmlStringBuilder xml = new XmlStringBuilder(); + xml.halfOpenElement(this); + xml.attribute(ATTR_START, getStart()); + xml.closeEmptyElement(); + return xml; + } + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/MarkupElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/MarkupElement.java new file mode 100644 index 000000000..ed86d3980 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/MarkupElement.java @@ -0,0 +1,312 @@ +/** + * + * Copyright © 2018 Paul Schaub + * + * 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.message_markup.element; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.NamedElement; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class MarkupElement implements ExtensionElement { + + public static final String NAMESPACE = "urn:xmpp:markup:0"; + public static final String ELEMENT = "markup"; + + private final List childElements; + + /** + * Create a new MarkupElement. + * + * @param childElements child elements. + */ + public MarkupElement(List childElements) { + this.childElements = Collections.unmodifiableList(childElements); + } + + /** + * Return a new Builder for Message Markup elements. + * @return builder. + */ + public static Builder getBuilder() { + return new Builder(); + } + + /** + * Return a list of all child elements. + * @return children + */ + public List getChildElements() { + return childElements; + } + + @Override + public String getNamespace() { + return NAMESPACE; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public XmlStringBuilder toXML() { + XmlStringBuilder xml = new XmlStringBuilder(this).rightAngleBracket(); + + for (MarkupChildElement child : getChildElements()) { + xml.append(child.toXML()); + } + + xml.closeElement(this); + return xml; + } + + + + public static final class Builder { + + private final List spans = new ArrayList<>(); + private final List quotes = new ArrayList<>(); + private final List codes = new ArrayList<>(); + private final List lists = new ArrayList<>(); + + private Builder() { + + } + + /** + * Mark a section of a message as deleted. + * + * @param start start index + * @param end end index + * @return builder + */ + public Builder setDeleted(int start, int end) { + return addSpan(start, end, Collections.singleton(SpanElement.SpanStyle.deleted)); + } + + /** + * Mark a section of a message as emphasized. + * + * @param start start index + * @param end end index + * @return builder + */ + public Builder setEmphasis(int start, int end) { + return addSpan(start, end, Collections.singleton(SpanElement.SpanStyle.emphasis)); + } + + /** + * Mark a section of a message as inline code. + * + * @param start start index + * @param end end index + * @return builder + */ + public Builder setCode(int start, int end) { + return addSpan(start, end, Collections.singleton(SpanElement.SpanStyle.code)); + } + + /** + * Add a span element. + * + * @param start start index + * @param end end index + * @param styles list of text styles for that span + * @return builder + */ + public Builder addSpan(int start, int end, Set styles) { + verifyStartEnd(start, end); + + for (SpanElement other : spans) { + if ((start >= other.getStart() && start <= other.getEnd()) || + (end >= other.getStart() && end <= other.getEnd())) { + throw new IllegalArgumentException("Spans MUST NOT overlap each other."); + } + } + + spans.add(new SpanElement(start, end, styles)); + return this; + } + + /** + * Mark a section of a message as block quote. + * + * @param start start index + * @param end end index + * @return builder + */ + public Builder setBlockQuote(int start, int end) { + verifyStartEnd(start, end); + + for (BlockQuoteElement other : quotes) { + // 1 if out, 0 if on, -1 if in + Integer s = start; + Integer e = end; + int startPos = s.compareTo(other.getStart()) * s.compareTo(other.getEnd()); + int endPos = e.compareTo(other.getStart()) * e.compareTo(other.getEnd()); + int allowed = startPos * endPos; + + if (allowed < 1) { + throw new IllegalArgumentException("BlockQuotes MUST NOT overlap each others boundaries"); + } + } + + quotes.add(new BlockQuoteElement(start, end)); + return this; + } + + /** + * Mark a section of a message as a code block. + * + * @param start start index + * @param end end index + * @return builder + */ + public Builder setCodeBlock(int start, int end) { + verifyStartEnd(start, end); + + codes.add(new CodeBlockElement(start, end)); + return this; + } + + /** + * Begin a list. + * + * @return list builder + */ + public Builder.ListBuilder beginList() { + return new Builder.ListBuilder(this); + } + + public static final class ListBuilder { + private final Builder markup; + private final ArrayList entries = new ArrayList<>(); + private int end = -1; + + private ListBuilder(Builder markup) { + this.markup = markup; + } + + /** + * Add an entry to the list. + * The start index of an entry must correspond to the end index of the previous entry + * (if a previous entry exists.) + * + * @param start start index + * @param end end index + * @return list builder + */ + public Builder.ListBuilder addEntry(int start, int end) { + verifyStartEnd(start, end); + + ListElement.ListEntryElement last = entries.size() == 0 ? null : entries.get(entries.size() - 1); + // Entries themselves do not store end values, that's why we store the last entries end value in this.end + if (last != null && start != this.end) { + throw new IllegalArgumentException("Next entries start must be equal to last entries end (" + this.end + ")."); + } + entries.add(new ListElement.ListEntryElement(start)); + this.end = end; + + return this; + } + + /** + * End the list. + * + * @return builder + */ + public Builder endList() { + if (entries.size() > 0) { + ListElement.ListEntryElement first = entries.get(0); + ListElement list = new ListElement(first.getStart(), end, entries); + markup.lists.add(list); + } + + return markup; + } + } + + /** + * Build a Message Markup element. + * + * @return extension element + */ + public MarkupElement build() { + List children = new ArrayList<>(); + children.addAll(spans); + children.addAll(quotes); + children.addAll(codes); + children.addAll(lists); + return new MarkupElement(children); + } + + private static void verifyStartEnd(int start, int end) { + if (start >= end || start < 0) { + throw new IllegalArgumentException("Start value (" + start + ") MUST be greater equal than 0 " + + "and MUST be smaller than end value (" + end + ")."); + } + } + } + + + + + + + + + + + + + + + /** + * Interface for child elements. + */ + public interface MarkupChildElement extends NamedElement { + + String ATTR_START = "start"; + String ATTR_END = "end"; + + /** + * Return the start index of this element. + * + * @return start index + */ + int getStart(); + + /** + * Return the end index of this element. + * + * @return end index + */ + int getEnd(); + } + + /** + * Interface for block level child elements. + */ + public interface BlockLevelMarkupElement extends MarkupChildElement { + + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/SpanElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/SpanElement.java new file mode 100644 index 000000000..8d3847716 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/SpanElement.java @@ -0,0 +1,93 @@ +/** + * + * Copyright © 2018 Paul Schaub + * + * 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.message_markup.element; + +import java.util.Collections; +import java.util.Set; + +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class SpanElement implements MarkupElement.MarkupChildElement { + + public static final String ELEMENT = "span"; + + private final int start, end; + private final Set styles; + + /** + * Create a new Span element. + * + * @param start start index + * @param end end index + * @param styles list of styles that apply to this span + */ + public SpanElement(int start, int end, Set styles) { + this.start = start; + this.end = end; + this.styles = Collections.unmodifiableSet(styles); + } + + @Override + public int getStart() { + return start; + } + + @Override + public int getEnd() { + return end; + } + + /** + * Return all styles of this span. + * + * @return styles + */ + public Set getStyles() { + return styles; + } + + public static final String emphasis = "emphasis"; + public static final String code = "code"; + public static final String deleted = "deleted"; + + public enum SpanStyle { + emphasis, + code, + deleted + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public XmlStringBuilder toXML() { + XmlStringBuilder xml = new XmlStringBuilder(); + xml.halfOpenElement(this); + xml.attribute(ATTR_START, getStart()); + xml.attribute(ATTR_END, getEnd()); + xml.rightAngleBracket(); + + for (SpanStyle style : getStyles()) { + xml.halfOpenElement(style.toString()).closeEmptyElement(); + } + + xml.closeElement(this); + return xml; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/package-info.java new file mode 100644 index 000000000..00c2ceabd --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/element/package-info.java @@ -0,0 +1,25 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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. + */ + +/** + * XEP-0394: Message Markup. + * + * @see XEP-0394: Message + * Markup + * + */ +package org.jivesoftware.smackx.message_markup.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/package-info.java new file mode 100644 index 000000000..8aa10ce24 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/package-info.java @@ -0,0 +1,25 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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. + */ + +/** + * XEP-0394: Message Markup. + * + * @see XEP-0394: Message + * Markup + * + */ +package org.jivesoftware.smackx.message_markup; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/provider/MarkupElementProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/provider/MarkupElementProvider.java new file mode 100644 index 000000000..00bb1627b --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/provider/MarkupElementProvider.java @@ -0,0 +1,139 @@ +/** + * + * Copyright © 2018 Paul Schaub + * + * 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.message_markup.provider; + +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.jivesoftware.smack.SmackException; +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.message_markup.element.BlockQuoteElement; +import org.jivesoftware.smackx.message_markup.element.CodeBlockElement; +import org.jivesoftware.smackx.message_markup.element.ListElement; +import org.jivesoftware.smackx.message_markup.element.MarkupElement; +import org.jivesoftware.smackx.message_markup.element.SpanElement; + +import org.xmlpull.v1.XmlPullParser; + +public class MarkupElementProvider extends ExtensionElementProvider { + + @Override + public MarkupElement parse(XmlPullParser parser, int initialDepth) throws Exception { + + MarkupElement.Builder markup = MarkupElement.getBuilder(); + + int spanStart = -1, spanEnd = -1; + Set spanStyles = new HashSet<>(); + + int listStart = -1, listEnd = -1; + List lis = new ArrayList<>(); + + while (true) { + int tag = parser.next(); + String name = parser.getName(); + int start, end; + switch (tag) { + case START_TAG: + switch (name) { + case BlockQuoteElement.ELEMENT: + start = ParserUtils.getIntegerAttributeOrThrow(parser, BlockQuoteElement.ATTR_START, + "Message Markup BlockQuoteElement MUST contain a 'start' attribute."); + end = ParserUtils.getIntegerAttributeOrThrow(parser, BlockQuoteElement.ATTR_END, + "Message Markup BlockQuoteElement MUST contain a 'end' attribute."); + markup.setBlockQuote(start, end); + break; + + case CodeBlockElement.ELEMENT: + start = ParserUtils.getIntegerAttributeOrThrow(parser, CodeBlockElement.ATTR_START, + "Message Markup CodeBlockElement MUST contain a 'start' attribute."); + end = ParserUtils.getIntegerAttributeOrThrow(parser, CodeBlockElement.ATTR_END, + "Message Markup CodeBlockElement MUST contain a 'end' attribute."); + markup.setCodeBlock(start, end); + break; + + case SpanElement.ELEMENT: + spanStyles = new HashSet<>(); + spanStart = ParserUtils.getIntegerAttributeOrThrow(parser, SpanElement.ATTR_START, + "Message Markup SpanElement MUST contain a 'start' attribute."); + spanEnd = ParserUtils.getIntegerAttributeOrThrow(parser, SpanElement.ATTR_END, + "Message Markup SpanElement MUST contain a 'end' attribute."); + break; + + case SpanElement.code: + spanStyles.add(SpanElement.SpanStyle.code); + break; + + case SpanElement.emphasis: + spanStyles.add(SpanElement.SpanStyle.emphasis); + break; + + case SpanElement.deleted: + spanStyles.add(SpanElement.SpanStyle.deleted); + break; + + case ListElement.ELEMENT: + lis = new ArrayList<>(); + listStart = ParserUtils.getIntegerAttributeOrThrow(parser, ListElement.ATTR_START, + "Message Markup ListElement MUST contain a 'start' attribute."); + listEnd = ParserUtils.getIntegerAttributeOrThrow(parser, ListElement.ATTR_END, + "Message Markup ListElement MUST contain a 'end' attribute."); + break; + + case ListElement.ELEM_LI: + start = ParserUtils.getIntegerAttributeOrThrow(parser, ListElement.ATTR_START, + "Message Markup ListElement 'li' MUST contain a 'start' attribute."); + lis.add(new ListElement.ListEntryElement(start)); + break; + } + break; + + case END_TAG: + switch (name) { + case SpanElement.ELEMENT: + markup.addSpan(spanStart, spanEnd, spanStyles); + spanStart = -1; spanEnd = -1; + break; + + case ListElement.ELEMENT: + MarkupElement.Builder.ListBuilder listBuilder = markup.beginList(); + if (lis.size() > 0 && lis.get(0).getStart() != listStart) { + throw new SmackException("Error while parsing incoming MessageMarkup ListElement: " + + "'start' attribute of first 'li' element must equal 'start' attribute of list."); + } + for (int i = 0; i < lis.size(); i++) { + int elemStart = lis.get(i).getStart(); + int elemEnd = i < lis.size() - 1 ? lis.get(i + 1).getStart() : listEnd; + listBuilder.addEntry(elemStart, elemEnd); + } + listBuilder.endList(); + break; + + case MarkupElement.ELEMENT: + return markup.build(); + } + + } + } + } + +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/provider/package-info.java new file mode 100644 index 000000000..f860b5b18 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/message_markup/provider/package-info.java @@ -0,0 +1,25 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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. + */ + +/** + * XEP-0394: Message Markup. + * + * @see XEP-0394: Message + * Markup + * + */ +package org.jivesoftware.smackx.message_markup.provider; diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers index 1ef9366a8..32452dfb9 100644 --- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers +++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers @@ -296,4 +296,11 @@ org.jivesoftware.smackx.eme.provider.ExplicitMessageEncryptionProvider + + + markup + urn:xmpp:markup:0 + org.jivesoftware.smackx.message_markup.provider.MarkupElementProvider + + diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/message_markup/MessageMarkupTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/message_markup/MessageMarkupTest.java new file mode 100644 index 000000000..7afcdc6e7 --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/message_markup/MessageMarkupTest.java @@ -0,0 +1,227 @@ +/** + * + * Copyright © 2018 Paul Schaub + * + * 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.message_markup; + +import static junit.framework.TestCase.assertEquals; +import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; + +import java.util.List; + +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smack.test.util.TestUtils; +import org.jivesoftware.smackx.message_markup.element.BlockQuoteElement; +import org.jivesoftware.smackx.message_markup.element.CodeBlockElement; +import org.jivesoftware.smackx.message_markup.element.ListElement; +import org.jivesoftware.smackx.message_markup.element.MarkupElement; +import org.jivesoftware.smackx.message_markup.element.SpanElement; +import org.jivesoftware.smackx.message_markup.provider.MarkupElementProvider; + +import org.junit.Test; +import org.xmlpull.v1.XmlPullParser; + +public class MessageMarkupTest extends SmackTestSuite { + + @Test + public void emphasisTest() throws Exception { + String xml = + "" + + "" + + "" + + "" + + ""; + MarkupElement.Builder m = MarkupElement.getBuilder(); + m.setEmphasis(9, 15); + assertXMLEqual(xml, m.build().toXML().toString()); + + XmlPullParser parser = TestUtils.getParser(xml); + MarkupElement parsed = new MarkupElementProvider().parse(parser); + List children = parsed.getChildElements(); + assertEquals(1, children.size()); + + SpanElement spanElement = (SpanElement) children.get(0); + assertEquals(9, spanElement.getStart()); + assertEquals(15, spanElement.getEnd()); + assertEquals(1, spanElement.getStyles().size()); + assertEquals(SpanElement.SpanStyle.emphasis, spanElement.getStyles().iterator().next()); + } + + @Test + public void codeTest() throws Exception { + String xml = + "" + + "" + + "" + + "" + + ""; + MarkupElement.Builder m = MarkupElement.getBuilder(); + m.setCode(9, 15); + assertXMLEqual(xml, m.build().toXML().toString()); + + XmlPullParser parser = TestUtils.getParser(xml); + MarkupElement parsed = new MarkupElementProvider().parse(parser); + List children = parsed.getChildElements(); + assertEquals(1, children.size()); + + SpanElement spanElement = (SpanElement) children.get(0); + assertEquals(9, spanElement.getStart()); + assertEquals(15, spanElement.getEnd()); + assertEquals(1, spanElement.getStyles().size()); + assertEquals(SpanElement.SpanStyle.code, spanElement.getStyles().iterator().next()); + } + + @Test + public void deletedTest() throws Exception { + String xml = + "" + + "" + + "" + + "" + + ""; + MarkupElement.Builder m = MarkupElement.getBuilder(); + m.setDeleted(9, 15); + assertXMLEqual(xml, m.build().toXML().toString()); + + XmlPullParser parser = TestUtils.getParser(xml); + MarkupElement parsed = new MarkupElementProvider().parse(parser); + List children = parsed.getChildElements(); + assertEquals(1, children.size()); + + SpanElement spanElement = (SpanElement) children.get(0); + assertEquals(9, spanElement.getStart()); + assertEquals(15, spanElement.getEnd()); + assertEquals(1, spanElement.getStyles().size()); + assertEquals(SpanElement.SpanStyle.deleted, spanElement.getStyles().iterator().next()); + } + + @Test(expected = IllegalArgumentException.class) + public void wrongStartEndTest() { + MarkupElement.getBuilder().setEmphasis(12, 10); + } + + @Test(expected = IllegalArgumentException.class) + public void overlappingSpansTest() { + MarkupElement.Builder m = MarkupElement.getBuilder(); + m.setEmphasis(0, 10); + m.setDeleted(5, 15); + } + + @Test + public void codeBlockTest() throws Exception { + String xml = + "" + + "" + + ""; + MarkupElement.Builder m = MarkupElement.getBuilder(); + m.setCodeBlock(23, 48); + assertXMLEqual(xml, m.build().toXML().toString()); + + XmlPullParser parser = TestUtils.getParser(xml); + MarkupElement parsed = new MarkupElementProvider().parse(parser); + List children = parsed.getChildElements(); + assertEquals(1, children.size()); + + CodeBlockElement codeBlock = (CodeBlockElement) children.get(0); + assertEquals(23, codeBlock.getStart()); + assertEquals(48, codeBlock.getEnd()); + } + + @Test + public void listTest() throws Exception { + String xml = + "" + + "" + + "
  • " + + "
  • " + + "
  • " + + "
  • " + + "" + + ""; + MarkupElement.Builder m = MarkupElement.getBuilder(); + m = m.beginList() + .addEntry(31, 47) + .addEntry(47, 61) + .addEntry(61, 69) + .addEntry(69, 89) + .endList(); + assertXMLEqual(xml, m.build().toXML().toString()); + + XmlPullParser parser = TestUtils.getParser(xml); + MarkupElement parsed = new MarkupElementProvider().parse(parser); + List children = parsed.getChildElements(); + assertEquals(1, children.size()); + + ListElement list = (ListElement) children.get(0); + assertEquals(31, list.getStart()); + assertEquals(89, list.getEnd()); + assertEquals(4, list.getEntries().size()); + assertEquals(list.getStart(), list.getEntries().get(0).getStart()); + assertEquals(47, list.getEntries().get(1).getStart()); + } + + @Test(expected = IllegalArgumentException.class) + public void listWrongSecondEntryTest() { + MarkupElement.Builder m = MarkupElement.getBuilder(); + m.beginList().addEntry(0,1).addEntry(3,4); + } + + @Test + public void blockQuoteTest() throws Exception { + String xml = + "" + + "" + + ""; + MarkupElement.Builder m = MarkupElement.getBuilder(); + m.setBlockQuote(9 ,32); + assertXMLEqual(xml, m.build().toXML().toString()); + + XmlPullParser parser = TestUtils.getParser(xml); + MarkupElement parsed = new MarkupElementProvider().parse(parser); + List children = parsed.getChildElements(); + assertEquals(1, children.size()); + + BlockQuoteElement quote = (BlockQuoteElement) children.get(0); + assertEquals(9, quote.getStart()); + assertEquals(32, quote.getEnd()); + } + + @Test + public void nestedBlockQuoteTest() throws Exception { + String xml = + "" + + "" + + "" + + ""; + MarkupElement.Builder m = MarkupElement.getBuilder(); + m.setBlockQuote(0, 57); + m.setBlockQuote(11, 34); + assertXMLEqual(xml, m.build().toXML().toString()); + + XmlPullParser parser = TestUtils.getParser(xml); + MarkupElement parsed = new MarkupElementProvider().parse(parser); + List children = parsed.getChildElements(); + assertEquals(2, children.size()); + + BlockQuoteElement q1 = (BlockQuoteElement) children.get(0); + BlockQuoteElement q2 = (BlockQuoteElement) children.get(1); + + assertEquals(0, q1.getStart()); + assertEquals(57, q1.getEnd()); + + assertEquals(11, q2.getStart()); + assertEquals(34, q2.getEnd()); + } +} From ce19ea41140fa191a52e3b94c17b54ebee9d2603 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 22 Feb 2018 08:51:54 +0100 Subject: [PATCH 05/11] Add support for XEP-0382: Spoiler Messages Fixes SMACK-795. --- documentation/extensions/index.md | 1 + documentation/extensions/spoiler.md | 33 ++++ .../smackx/spoiler/SpoilerManager.java | 69 ++++++++ .../spoiler/element/SpoilerElement.java | 165 ++++++++++++++++++ .../smackx/spoiler/element/package-info.java | 20 +++ .../smackx/spoiler/package-info.java | 20 +++ .../spoiler/provider/SpoilerProvider.java | 49 ++++++ .../smackx/spoiler/provider/package-info.java | 20 +++ .../experimental.providers | 7 + .../smackx/spoiler/SpoilerTest.java | 126 +++++++++++++ .../smackx/spoiler/package-info.java | 20 +++ 11 files changed, 530 insertions(+) create mode 100644 documentation/extensions/spoiler.md create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/SpoilerManager.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/package-info.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/provider/SpoilerProvider.java create mode 100644 smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/provider/package-info.java create mode 100644 smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java create mode 100644 smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/package-info.java diff --git a/documentation/extensions/index.md b/documentation/extensions/index.md index 46470b84d..6e737430a 100644 --- a/documentation/extensions/index.md +++ b/documentation/extensions/index.md @@ -94,6 +94,7 @@ Experimental Smack Extensions and currently supported XEPs of smack-experimental | [Push Notifications](pushnotifications.md) | [XEP-0357](http://xmpp.org/extensions/xep-0357.html) | Defines a way to manage push notifications from an XMPP Server. | | Stable and Unique Stanza IDs | [XEP-0359](http://xmpp.org/extensions/xep-0359.html) | This specification describes unique and stable IDs for messages. | | HTTP File Upload | [XEP-0363](http://xmpp.org/extensions/xep-0363.html) | Protocol to request permissions to upload a file to an HTTP server and get a shareable URL. | +| [Spoiler Messages](spoiler.md) | [XEP-0382](http://xmpp.org/extensions/xep-0382.html) | Indicate that the body of a message should be treated as a spoiler | | [Multi-User Chat Light](muclight.md) | [XEP-xxxx](http://mongooseim.readthedocs.io/en/latest/open-extensions/xeps/xep-muc-light.html) | Multi-User Chats for mobile XMPP applications and specific enviroment. | | [OMEMO Multi End Message and Object Encryption](omemo.md) | [XEP-XXXX](https://conversations.im/omemo/xep-omemo.html) | Encrypt messages using OMEMO encryption (currently only with smack-omemo-signal -> GPLv3). | | [Consistent Color Generation](consistent_colors.md) | [XEP-0392](http://xmpp.org/extensions/xep-0392.html) | Generate consistent colors for identifiers like usernames to provide a consistent user experience. | diff --git a/documentation/extensions/spoiler.md b/documentation/extensions/spoiler.md new file mode 100644 index 000000000..5d4ac94ea --- /dev/null +++ b/documentation/extensions/spoiler.md @@ -0,0 +1,33 @@ +Spoiler Messages +================ + +[Back](index.md) + +Spoiler Messages can be used to indicate that the body of a message is a spoiler and should be displayed as such. + +## Usage + +To get an instance of the SpoilerManager, call +``` +SpoilerManager manager = SpoilerManager.getInstanceFor(connection); +``` +This will automatically add Spoilers to the list of supported features of your client. + +The manager can then be used to add SpoilerElements to messages like follows: +``` +Message message = new Message(); + +// spoiler without hint +SpoilerElement.addSpoiler(message); + +// spoiler with hint about content +SpoilerElement.addSpoiler(message, "End of Love Story"); + +// spoiler with localized hint +SpoilerElement.addSpoiler(message, "de", "Der Kuchen ist eine Lüge"); +``` + +To get Spoilers from a message call +``` +Map spoilers = SpoilerElement.getSpoilers(message); +``` \ No newline at end of file diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/SpoilerManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/SpoilerManager.java new file mode 100644 index 000000000..1faf91a63 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/SpoilerManager.java @@ -0,0 +1,69 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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.spoiler; + +import java.util.Map; +import java.util.WeakHashMap; + +import org.jivesoftware.smack.Manager; +import org.jivesoftware.smack.XMPPConnection; +import org.jivesoftware.smackx.disco.ServiceDiscoveryManager; + +public final class SpoilerManager extends Manager { + + public static final String NAMESPACE_0 = "urn:xmpp:spoiler:0"; + + private static final Map INSTANCES = new WeakHashMap<>(); + + /** + * Create a new SpoilerManager and add Spoiler to disco features. + * + * @param connection xmpp connection + */ + private SpoilerManager(XMPPConnection connection) { + super(connection); + } + + /** + * Begin announcing support for Spoiler messages. + */ + public void startAnnounceSupport() { + ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(NAMESPACE_0); + } + + /** + * End announcing support for Spoiler messages. + */ + public void stopAnnounceSupport() { + ServiceDiscoveryManager.getInstanceFor(connection()).removeFeature(NAMESPACE_0); + } + + /** + * Return the connections instance of the SpoilerManager. + * + * @param connection xmpp connection + * @return SpoilerManager + */ + public static SpoilerManager getInstanceFor(XMPPConnection connection) { + SpoilerManager manager = INSTANCES.get(connection); + if (manager == null) { + manager = new SpoilerManager(connection); + INSTANCES.put(connection, manager); + } + return manager; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java new file mode 100644 index 000000000..1392cd65d --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java @@ -0,0 +1,165 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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.spoiler.element; + +import static org.jivesoftware.smackx.spoiler.SpoilerManager.NAMESPACE_0; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jivesoftware.smack.packet.ExtensionElement; +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.util.XmlStringBuilder; + +public class SpoilerElement implements ExtensionElement { + + public static final String ELEMENT = "spoiler"; + public static final SpoilerElement EMPTY = new SpoilerElement(null, null); + + private final String hint; + private final String language; + + /** + * Create a new SpoilerElement with a hint about a content and a language attribute. + * + * @param language language of the hint. + * @param hint hint about the content. + */ + public SpoilerElement(String language, String hint) { + if (language != null && !language.equals("")) { + if (hint == null || hint.equals("")) { + throw new IllegalArgumentException("Hint cannot be null or empty if language is not empty."); + } + } + this.language = language; + this.hint = hint; + } + + /** + * Return the hint text of the spoiler. + * May be null. + * + * @return hint text + */ + public String getHint() { + return hint; + } + + /** + * Add a SpoilerElement to a message. + * + * @param message message to add the Spoiler to. + */ + public static void addSpoiler(Message message) { + message.addExtension(SpoilerElement.EMPTY); + } + + /** + * Add a SpoilerElement with a hint to a message. + * + * @param message Message to add the Spoiler to. + * @param hint Hint about the Spoilers content. + */ + public static void addSpoiler(Message message, String hint) { + message.addExtension(new SpoilerElement(null, hint)); + } + + /** + * Add a SpoilerElement with a hint in a certain language to a message. + * + * @param message Message to add the Spoiler to. + * @param lang language of the Spoiler hint. + * @param hint hint. + */ + public static void addSpoiler(Message message, String lang, String hint) { + message.addExtension(new SpoilerElement(lang, hint)); + } + + + /** + * Returns true, if the message has at least one spoiler element. + * + * @param message message + * @return true if message has spoiler extension + */ + public static boolean containsSpoiler(Message message) { + return message.hasExtension(SpoilerElement.ELEMENT, NAMESPACE_0); + } + + /** + * Return a map of all spoilers contained in a message. + * The map uses the language of a spoiler as key. + * If a spoiler has no language attribute, its key will be an empty String. + * + * @param message message + * @return map of spoilers + */ + public static Map getSpoilers(Message message) { + if (!containsSpoiler(message)) { + return null; + } + + List spoilers = message.getExtensions(SpoilerElement.ELEMENT, NAMESPACE_0); + Map map = new HashMap<>(); + + for (ExtensionElement e : spoilers) { + SpoilerElement s = (SpoilerElement) e; + if (s.getLanguage() == null || s.getLanguage().equals("")) { + map.put("", s.getHint()); + } else { + map.put(s.getLanguage(), s.getHint()); + } + } + + return map; + } + + /** + * Return the language of the hint. + * May be null. + * + * @return language of hint text + */ + public String getLanguage() { + return language; + } + + @Override + public String getNamespace() { + return NAMESPACE_0; + } + + @Override + public String getElementName() { + return ELEMENT; + } + + @Override + public CharSequence toXML() { + XmlStringBuilder xml = new XmlStringBuilder(this); + xml.optXmlLangAttribute(getLanguage()); + if (getHint() == null) { + xml.closeEmptyElement(); + } else { + xml.rightAngleBracket(); + xml.append(getHint()); + xml.closeElement(this); + } + return xml; + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/package-info.java new file mode 100644 index 000000000..67e8b1307 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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. + */ +/** + * Smack's API for XEP-0382: Spoiler Messages. + */ +package org.jivesoftware.smackx.spoiler.element; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/package-info.java new file mode 100644 index 000000000..a7e811501 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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. + */ +/** + * Smack's API for XEP-0382: Spoiler Messages. + */ +package org.jivesoftware.smackx.spoiler; diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/provider/SpoilerProvider.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/provider/SpoilerProvider.java new file mode 100644 index 000000000..090d8c499 --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/provider/SpoilerProvider.java @@ -0,0 +1,49 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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.spoiler.provider; + +import static org.xmlpull.v1.XmlPullParser.END_TAG; +import static org.xmlpull.v1.XmlPullParser.TEXT; + +import org.jivesoftware.smack.provider.ExtensionElementProvider; +import org.jivesoftware.smack.util.ParserUtils; +import org.jivesoftware.smackx.spoiler.element.SpoilerElement; + +import org.xmlpull.v1.XmlPullParser; + +public class SpoilerProvider extends ExtensionElementProvider { + + public static SpoilerProvider INSTANCE = new SpoilerProvider(); + + @Override + public SpoilerElement parse(XmlPullParser parser, int initialDepth) throws Exception { + String lang = ParserUtils.getXmlLang(parser); + String hint = null; + + outerloop: while (true) { + int tag = parser.next(); + switch (tag) { + case TEXT: + hint = parser.getText(); + break; + case END_TAG: + break outerloop; + } + } + return new SpoilerElement(lang, hint); + } +} diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/provider/package-info.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/provider/package-info.java new file mode 100644 index 000000000..5640cce8a --- /dev/null +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/provider/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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. + */ +/** + * Smack's API for XEP-0382: Spoiler Messages. + */ +package org.jivesoftware.smackx.spoiler.provider; diff --git a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers index 32452dfb9..686db8ee6 100644 --- a/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers +++ b/smack-experimental/src/main/resources/org.jivesoftware.smack.experimental/experimental.providers @@ -296,6 +296,13 @@ org.jivesoftware.smackx.eme.provider.ExplicitMessageEncryptionProvider + + + spoiler + urn:xmpp:spoiler:0 + org.jivesoftware.smackx.spoiler.provider.SpoilerProvider + + markup diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java new file mode 100644 index 000000000..3049e44df --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java @@ -0,0 +1,126 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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.spoiler; + +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertNull; +import static junit.framework.TestCase.assertTrue; +import static org.custommonkey.xmlunit.XMLAssert.assertXMLEqual; + +import java.util.Map; + +import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.test.util.SmackTestSuite; +import org.jivesoftware.smack.test.util.TestUtils; +import org.jivesoftware.smackx.spoiler.element.SpoilerElement; +import org.jivesoftware.smackx.spoiler.provider.SpoilerProvider; + +import org.junit.Test; +import org.xmlpull.v1.XmlPullParser; + +public class SpoilerTest extends SmackTestSuite { + + @Test + public void emptySpoilerTest() throws Exception { + final String xml = ""; + + Message message = new Message(); + SpoilerElement.addSpoiler(message); + + SpoilerElement empty = message.getExtension(SpoilerElement.ELEMENT, SpoilerManager.NAMESPACE_0); + + assertNull(empty.getHint()); + assertNull(empty.getLanguage()); + + assertXMLEqual(xml, empty.toXML().toString()); + + XmlPullParser parser = TestUtils.getParser(xml); + SpoilerElement parsed = SpoilerProvider.INSTANCE.parse(parser); + assertXMLEqual(xml, parsed.toXML().toString()); + } + + @Test + public void hintSpoilerTest() throws Exception { + final String xml = "Love story end"; + + Message message = new Message(); + SpoilerElement.addSpoiler(message, "Love story end"); + + SpoilerElement withHint = message.getExtension(SpoilerElement.ELEMENT, SpoilerManager.NAMESPACE_0); + + assertEquals("Love story end", withHint.getHint()); + assertNull(withHint.getLanguage()); + + assertXMLEqual(xml, withHint.toXML().toString()); + + XmlPullParser parser = TestUtils.getParser(xml); + SpoilerElement parsed = SpoilerProvider.INSTANCE.parse(parser); + + assertXMLEqual(xml, parsed.toXML().toString()); + } + + @Test + public void i18nHintSpoilerTest() throws Exception { + final String xml = "Der Kuchen ist eine Lüge!"; + + Message message = new Message(); + SpoilerElement.addSpoiler(message, "de", "Der Kuchen ist eine Lüge!"); + + SpoilerElement i18nHint = message.getExtension(SpoilerElement.ELEMENT, SpoilerManager.NAMESPACE_0); + + assertEquals("Der Kuchen ist eine Lüge!", i18nHint.getHint()); + assertEquals("de", i18nHint.getLanguage()); + + assertXMLEqual(xml, i18nHint.toXML().toString()); + + XmlPullParser parser = TestUtils.getParser(xml); + SpoilerElement parsed = SpoilerProvider.INSTANCE.parse(parser); + assertEquals(i18nHint.getLanguage(), parsed.getLanguage()); + + assertXMLEqual(xml, parsed.toXML().toString()); + } + + @Test + public void getSpoilersTest() { + Message m = new Message(); + assertNull(SpoilerElement.getSpoilers(m)); + + SpoilerElement.addSpoiler(m); + assertTrue(SpoilerElement.containsSpoiler(m)); + + Map spoilers = SpoilerElement.getSpoilers(m); + assertEquals(1, spoilers.size()); + assertEquals(null, spoilers.get("")); + + final String spoilerText = "Spoiler Text"; + + SpoilerElement.addSpoiler(m, "de", spoilerText); + spoilers = SpoilerElement.getSpoilers(m); + assertEquals(2, spoilers.size()); + assertEquals(spoilerText, spoilers.get("de")); + } + + @Test(expected = IllegalArgumentException.class) + public void spoilerCheckArgumentsNullTest() { + SpoilerElement spoilerElement = new SpoilerElement("de", null); + } + + @Test(expected = IllegalArgumentException.class) + public void spoilerCheckArgumentsEmptyTest() { + SpoilerElement spoilerElement = new SpoilerElement("de", ""); + } +} diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/package-info.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/package-info.java new file mode 100644 index 000000000..a7e811501 --- /dev/null +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/package-info.java @@ -0,0 +1,20 @@ +/** + * + * Copyright 2018 Paul Schaub + * + * 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. + */ +/** + * Smack's API for XEP-0382: Spoiler Messages. + */ +package org.jivesoftware.smackx.spoiler; From 81f599425a14586a360f99268d433a3e6527ee37 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 22 Feb 2018 09:02:28 +0100 Subject: [PATCH 06/11] Make SDM a field in SpoilerManager --- .../org/jivesoftware/smackx/spoiler/SpoilerManager.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/SpoilerManager.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/SpoilerManager.java index 1faf91a63..f3bb80c85 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/SpoilerManager.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/SpoilerManager.java @@ -29,6 +29,8 @@ public final class SpoilerManager extends Manager { private static final Map INSTANCES = new WeakHashMap<>(); + private final ServiceDiscoveryManager serviceDiscoveryManager; + /** * Create a new SpoilerManager and add Spoiler to disco features. * @@ -36,20 +38,21 @@ public final class SpoilerManager extends Manager { */ private SpoilerManager(XMPPConnection connection) { super(connection); + serviceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(connection); } /** * Begin announcing support for Spoiler messages. */ public void startAnnounceSupport() { - ServiceDiscoveryManager.getInstanceFor(connection()).addFeature(NAMESPACE_0); + serviceDiscoveryManager.addFeature(NAMESPACE_0); } /** * End announcing support for Spoiler messages. */ public void stopAnnounceSupport() { - ServiceDiscoveryManager.getInstanceFor(connection()).removeFeature(NAMESPACE_0); + serviceDiscoveryManager.removeFeature(NAMESPACE_0); } /** From 4292659f22f3afa87bdcb1a7131338f67111608b Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 22 Feb 2018 09:03:09 +0100 Subject: [PATCH 07/11] Use StringUtils in SpoilerElement --- .../smackx/spoiler/element/SpoilerElement.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java index 1392cd65d..3ee6b08ee 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java @@ -24,6 +24,7 @@ import java.util.Map; import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.Message; +import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.XmlStringBuilder; public class SpoilerElement implements ExtensionElement { @@ -41,10 +42,8 @@ public class SpoilerElement implements ExtensionElement { * @param hint hint about the content. */ public SpoilerElement(String language, String hint) { - if (language != null && !language.equals("")) { - if (hint == null || hint.equals("")) { - throw new IllegalArgumentException("Hint cannot be null or empty if language is not empty."); - } + if (StringUtils.isNotEmpty(language) && StringUtils.isNullOrEmpty(hint)) { + throw new IllegalArgumentException("Hint cannot be null or empty if language is not empty."); } this.language = language; this.hint = hint; From bde6239c261c15734a3432cd734a6f9411b1264e Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 22 Feb 2018 09:03:33 +0100 Subject: [PATCH 08/11] Return empty map in SpoilerElement.getSpoilers() instead of null --- .../jivesoftware/smackx/spoiler/element/SpoilerElement.java | 3 ++- .../test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java index 3ee6b08ee..f0c53a71f 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java @@ -18,6 +18,7 @@ package org.jivesoftware.smackx.spoiler.element; import static org.jivesoftware.smackx.spoiler.SpoilerManager.NAMESPACE_0; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -110,7 +111,7 @@ public class SpoilerElement implements ExtensionElement { */ public static Map getSpoilers(Message message) { if (!containsSpoiler(message)) { - return null; + return Collections.emptyMap(); } List spoilers = message.getExtensions(SpoilerElement.ELEMENT, NAMESPACE_0); diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java index 3049e44df..d1a77cff3 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java @@ -97,7 +97,7 @@ public class SpoilerTest extends SmackTestSuite { @Test public void getSpoilersTest() { Message m = new Message(); - assertNull(SpoilerElement.getSpoilers(m)); + assertTrue(SpoilerElement.getSpoilers(m).isEmpty()); SpoilerElement.addSpoiler(m); assertTrue(SpoilerElement.containsSpoiler(m)); From c1e557e1d44514bdfbbdb6492d3c2b104ec7ae04 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 22 Feb 2018 09:03:53 +0100 Subject: [PATCH 09/11] Add NAMESPACE field to SpoilerElement --- .../smackx/spoiler/element/SpoilerElement.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java index f0c53a71f..1f1794ff1 100644 --- a/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java +++ b/smack-experimental/src/main/java/org/jivesoftware/smackx/spoiler/element/SpoilerElement.java @@ -16,8 +16,6 @@ */ package org.jivesoftware.smackx.spoiler.element; -import static org.jivesoftware.smackx.spoiler.SpoilerManager.NAMESPACE_0; - import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -27,10 +25,13 @@ import org.jivesoftware.smack.packet.ExtensionElement; import org.jivesoftware.smack.packet.Message; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.XmlStringBuilder; +import org.jivesoftware.smackx.spoiler.SpoilerManager; public class SpoilerElement implements ExtensionElement { public static final String ELEMENT = "spoiler"; + public static final String NAMESPACE = SpoilerManager.NAMESPACE_0; + public static final SpoilerElement EMPTY = new SpoilerElement(null, null); private final String hint; @@ -98,7 +99,7 @@ public class SpoilerElement implements ExtensionElement { * @return true if message has spoiler extension */ public static boolean containsSpoiler(Message message) { - return message.hasExtension(SpoilerElement.ELEMENT, NAMESPACE_0); + return message.hasExtension(SpoilerElement.ELEMENT, NAMESPACE); } /** @@ -114,7 +115,7 @@ public class SpoilerElement implements ExtensionElement { return Collections.emptyMap(); } - List spoilers = message.getExtensions(SpoilerElement.ELEMENT, NAMESPACE_0); + List spoilers = message.getExtensions(SpoilerElement.ELEMENT, NAMESPACE); Map map = new HashMap<>(); for (ExtensionElement e : spoilers) { @@ -141,7 +142,7 @@ public class SpoilerElement implements ExtensionElement { @Override public String getNamespace() { - return NAMESPACE_0; + return NAMESPACE; } @Override From 4bf5c0c714d2151c04d9c10e426581f1b2749751 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 22 Feb 2018 09:31:24 +0100 Subject: [PATCH 10/11] Suppress 'unsused' warning in SpoilerTest --- .../test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java index d1a77cff3..d20ad6082 100644 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java +++ b/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/SpoilerTest.java @@ -116,11 +116,13 @@ public class SpoilerTest extends SmackTestSuite { @Test(expected = IllegalArgumentException.class) public void spoilerCheckArgumentsNullTest() { + @SuppressWarnings("unused") SpoilerElement spoilerElement = new SpoilerElement("de", null); } @Test(expected = IllegalArgumentException.class) public void spoilerCheckArgumentsEmptyTest() { + @SuppressWarnings("unused") SpoilerElement spoilerElement = new SpoilerElement("de", ""); } } From e1eb2d4ef1b91cad4e06d5b30a0eef51546e6841 Mon Sep 17 00:00:00 2001 From: Florian Schmaus Date: Thu, 22 Feb 2018 14:16:04 +0100 Subject: [PATCH 11/11] Remove package-info.java from test code Otherwise we have duplicate package-info.java files which caues some build systems and IDEs to report a failure. --- .../smackx/spoiler/package-info.java | 20 ------------------- 1 file changed, 20 deletions(-) delete mode 100644 smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/package-info.java diff --git a/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/package-info.java b/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/package-info.java deleted file mode 100644 index a7e811501..000000000 --- a/smack-experimental/src/test/java/org/jivesoftware/smackx/spoiler/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/** - * - * Copyright 2018 Paul Schaub - * - * 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. - */ -/** - * Smack's API for XEP-0382: Spoiler Messages. - */ -package org.jivesoftware.smackx.spoiler;