diff --git a/smack-core/src/main/java/org/jivesoftware/smack/packet/Message.java b/smack-core/src/main/java/org/jivesoftware/smack/packet/Message.java index fe29d4c3d..dece4f384 100644 --- a/smack-core/src/main/java/org/jivesoftware/smack/packet/Message.java +++ b/smack-core/src/main/java/org/jivesoftware/smack/packet/Message.java @@ -24,6 +24,8 @@ import java.util.List; import java.util.Locale; import java.util.Set; +import org.jivesoftware.smack.util.EqualsUtil; +import org.jivesoftware.smack.util.HashCode; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.TypedCloneable; import org.jivesoftware.smack.util.XmlStringBuilder; @@ -561,32 +563,22 @@ public final class Message extends Stanza implements TypedCloneable { return subject; } + private final HashCode.Cache hashCodeCache = new HashCode.Cache(); @Override public int hashCode() { - final int prime = 31; - int result = 1; - if (language != null) { - result = prime * result + this.language.hashCode(); - } - result = prime * result + this.subject.hashCode(); - return result; + return hashCodeCache.getHashCode(c -> + c.append(language) + .append(subject) + ); } @Override public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Subject other = (Subject) obj; - // simplified comparison because language and subject are always set - return this.language.equals(other.language) && this.subject.equals(other.subject); + return EqualsUtil.equals(this, obj, (e, o) -> + e.append(language, o.language) + .append(subject, o.subject) + ); } @Override @@ -670,31 +662,22 @@ public final class Message extends Stanza implements TypedCloneable { return message; } + private final HashCode.Cache hashCodeCache = new HashCode.Cache(); + @Override public int hashCode() { - final int prime = 31; - int result = 1; - if (language != null) { - result = prime * result + this.language.hashCode(); - } - result = prime * result + this.message.hashCode(); - return result; + return hashCodeCache.getHashCode(c -> + c.append(language) + .append(message) + ); } @Override public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - Body other = (Body) obj; - // simplified comparison because language and message are always set - return Objects.equals(this.language, other.language) && this.message.equals(other.message); + return EqualsUtil.equals(this, obj, (e, o) -> + e.append(language, o.language) + .append(message, o.message) + ); } @Override diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/EqualsUtil.java b/smack-core/src/main/java/org/jivesoftware/smack/util/EqualsUtil.java new file mode 100644 index 000000000..9d786cc8f --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/EqualsUtil.java @@ -0,0 +1,243 @@ +/** + * + * Copyright 2019 Florian Schmaus. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.smack.util; + +public final class EqualsUtil { + + private EqualsUtil() { + } + + public static boolean equals(T thisObject, Object other, EqualsComperator equalsComperator) { + if (other == null) { + return false; + } + if (thisObject == other) { + return true; + } + @SuppressWarnings("unchecked") + Class thisObjectClass = (Class) thisObject.getClass(); + if (thisObjectClass != other.getClass()) { + return false; + } + + EqualsUtil.Builder equalsBuilder = new EqualsUtil.Builder(); + + equalsComperator.compare(equalsBuilder, thisObjectClass.cast(other)); + + return equalsBuilder.isEquals; + } + + @FunctionalInterface + public interface EqualsComperator { + void compare(EqualsUtil.Builder equalsBuilder, T other); + } + + public static final class Builder { + private boolean isEquals = true; + + private Builder() { + } + + private void nullSafeCompare(Object left, Object right, Runnable runnable) { + if (!isEquals) { + return; + } + + if (left == right) { + return; + } + + if (left == null || right == null) { + isEquals = false; + return; + } + + runnable.run(); + } + + public Builder append(O left, O right) { + if (!isEquals) { + return this; + } + + if (left == right) { + return this; + } + + if (left == null || right == null) { + isEquals = false; + return this; + } + + isEquals = left.equals(right); + return this; + } + + public Builder append(boolean left, boolean right) { + if (!isEquals) { + return this; + } + + isEquals = left == right; + return this; + } + + public Builder append(boolean[] left, boolean[] right) { + nullSafeCompare(left, right, () -> { + if (left.length != right.length) { + isEquals = false; + return; + } + for (int i = 0; i < left.length && isEquals; i++) { + append(left[i], right[i]); + } + }); + return this; + } + + public Builder append(byte left, byte right) { + if (!isEquals) { + return this; + } + + isEquals = left == right; + return this; + } + + public Builder append(byte[] left, byte[] right) { + nullSafeCompare(left, right, () -> { + if (left.length != right.length) { + isEquals = false; + return; + } + for (int i = 0; i < left.length && isEquals; i++) { + append(left[i], right[i]); + } + }); + return this; + } + + public Builder append(char left, char right) { + if (!isEquals) { + return this; + } + + isEquals = left == right; + return this; + } + + public Builder append(char[] left, char[] right) { + nullSafeCompare(left, right, () -> { + if (left.length != right.length) { + isEquals = false; + return; + } + for (int i = 0; i < left.length && isEquals; i++) { + append(left[i], right[i]); + } + }); + return this; + } + + public Builder append(double left, double right) { + if (!isEquals) { + return this; + } + + return append(Double.doubleToLongBits(left), Double.doubleToLongBits(right)); + } + + public Builder append(double[] left, double[] right) { + nullSafeCompare(left, right, () -> { + if (left.length != right.length) { + isEquals = false; + return; + } + for (int i = 0; i < left.length && isEquals; i++) { + append(left[i], right[i]); + } + }); + return this; + } + + public Builder append(float left, float right) { + if (!isEquals) { + return this; + } + + return append(Float.floatToIntBits(left), Float.floatToIntBits(right)); + } + + public Builder append(float[] left, float[] right) { + nullSafeCompare(left, right, () -> { + if (left.length != right.length) { + isEquals = false; + return; + } + for (int i = 0; i < left.length && isEquals; i++) { + append(left[i], right[i]); + } + }); + return this; + } + + public Builder append(int left, int right) { + if (!isEquals) { + return this; + } + + isEquals = left == right; + return this; + } + + public Builder append(int[] left, int[] right) { + nullSafeCompare(left, right, () -> { + if (left.length != right.length) { + isEquals = false; + return; + } + for (int i = 0; i < left.length && isEquals; i++) { + append(left[i], right[i]); + } + }); + return this; + } + + public Builder append(long left, long right) { + if (!isEquals) { + return this; + } + + isEquals = left == right; + return this; + } + + public Builder append(long[] left, long[] right) { + nullSafeCompare(left, right, () -> { + if (left.length != right.length) { + isEquals = false; + return; + } + for (int i = 0; i < left.length && isEquals; i++) { + append(left[i], right[i]); + } + }); + return this; + } + } + +} diff --git a/smack-core/src/main/java/org/jivesoftware/smack/util/HashCode.java b/smack-core/src/main/java/org/jivesoftware/smack/util/HashCode.java new file mode 100644 index 000000000..79065850d --- /dev/null +++ b/smack-core/src/main/java/org/jivesoftware/smack/util/HashCode.java @@ -0,0 +1,211 @@ +/** + * + * Copyright 2019 Florian Schmaus. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * 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.smack.util; + +public class HashCode { + + private static final int MULTIPLIER_VALUE = 37; + + public static class Cache { + private boolean calculated; + private int hashcode; + + public int getHashCode(Calculator hashCodeCalculator) { + if (calculated) { + return hashcode; + } + + HashCode.Builder hashCodeBuilder = new HashCode.Builder(); + hashCodeCalculator.calculateHash(hashCodeBuilder); + + calculated = true; + hashcode = hashCodeBuilder.hashcode; + + return hashcode; + } + + } + + @FunctionalInterface + public interface Calculator { + void calculateHash(HashCode.Builder hashCodeBuilder); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private int hashcode = 17; + + private void applyHash() { + applyHash(0); + } + + private void applyHash(int hash) { + hashcode = MULTIPLIER_VALUE * hashcode + hash; + } + + public Builder append(Object object) { + if (object == null) { + applyHash(); + return this; + } + + if (object.getClass().isArray()) { + if (object instanceof int[]) { + append((int[]) object); + } else if (object instanceof long[]) { + append((long[]) object); + } else if (object instanceof boolean[]) { + append((boolean[]) object); + } else if (object instanceof double[]) { + append((double[]) object); + } else if (object instanceof float[]) { + append((float[]) object); + } else if (object instanceof short[]) { + append((short[]) object); + } else if (object instanceof char[]) { + append((char[]) object); + } else if (object instanceof byte[]) { + append((byte[]) object); + } else { + append((Object[]) object); + } + } + applyHash(object.hashCode()); + return this; + } + + public Builder append(boolean value) { + applyHash(value ? 0 : 1); + return this; + } + + public Builder append(boolean[] array) { + if (array == null) { + applyHash(); + return this; + } + + for (boolean bool : array) { + append(bool); + } + return this; + } + + public Builder append(byte value) { + applyHash(value); + return this; + } + + public Builder append(byte[] array) { + if (array == null) { + applyHash(); + return this; + } + + for (byte b : array) { + append(b); + } + return this; + } + + public Builder append(char value) { + applyHash(value); + return this; + } + + public Builder append(char[] array) { + if (array == null) { + applyHash(); + return this; + } + + for (char c : array) { + append(c); + } + return this; + } + + public Builder append(double value) { + return append(Double.doubleToLongBits(value)); + } + + public Builder append(double[] array) { + if (array == null) { + applyHash(); + return this; + } + + for (double d : array) { + append(d); + } + return this; + } + + public Builder append(float value) { + return append(Float.floatToIntBits(value)); + } + + public Builder append(float[] array) { + if (array == null) { + applyHash(); + return this; + } + + for (float f : array) { + append(f); + } + return this; + } + + public Builder append(long value) { + applyHash((int) (value ^ (value >>> 32))); + return this; + } + + public Builder append(long[] array) { + if (array == null) { + applyHash(); + return this; + } + + for (long l : array) { + append(l); + } + return this; + } + + public Builder append(Object[] array) { + if (array == null) { + applyHash(); + return this; + } + + for (Object element : array) { + append(element); + } + return this; + } + + public int build() { + return hashcode; + } + } + +} diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java index c31a45aad..374f8bdbb 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/disco/packet/DiscoverInfo.java @@ -25,6 +25,8 @@ import java.util.List; import java.util.Set; import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.util.EqualsUtil; +import org.jivesoftware.smack.util.HashCode; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.TypedCloneable; import org.jivesoftware.smack.util.XmlStringBuilder; @@ -397,37 +399,22 @@ public class DiscoverInfo extends IQ implements TypedCloneable { */ @Override public boolean equals(Object obj) { - if (obj == null) - return false; - if (obj == this) - return true; - if (obj.getClass() != getClass()) - return false; - - DiscoverInfo.Identity other = (DiscoverInfo.Identity) obj; - if (!this.key.equals(other.key)) - return false; - - String otherLang = other.lang == null ? "" : other.lang; - String thisLang = lang == null ? "" : lang; - if (!otherLang.equals(thisLang)) - return false; - - String otherName = other.name == null ? "" : other.name; - String thisName = name == null ? "" : other.name; - if (!thisName.equals(otherName)) - return false; - - return true; + return EqualsUtil.equals(this, obj, (e, o) -> { + e.append(key, o.key) + .append(lang, o.lang) + .append(name, o.name); + }); } + private final HashCode.Cache hashCodeCache = new HashCode.Cache(); + @Override public int hashCode() { - int result = 1; - result = 37 * result + key.hashCode(); - result = 37 * result + (lang == null ? 0 : lang.hashCode()); - result = 37 * result + (name == null ? 0 : name.hashCode()); - return result; + return hashCodeCache.getHashCode(c -> + c.append(key) + .append(lang) + .append(name) + ); } /** @@ -522,15 +509,9 @@ public class DiscoverInfo extends IQ implements TypedCloneable { @Override public boolean equals(Object obj) { - if (obj == null) - return false; - if (obj == this) - return true; - if (obj.getClass() != getClass()) - return false; - - DiscoverInfo.Feature other = (DiscoverInfo.Feature) obj; - return variable.equals(other.variable); + return EqualsUtil.equals(this, obj, (e, o) -> { + e.append(variable, o.variable); + }); } @Override diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSession.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSession.java index 79d6cfe70..db402c7ea 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSession.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/jingle/JingleSession.java @@ -23,6 +23,8 @@ import java.util.concurrent.Future; import org.jivesoftware.smack.XMPPConnection; import org.jivesoftware.smack.packet.IQ; +import org.jivesoftware.smack.util.EqualsUtil; +import org.jivesoftware.smack.util.HashCode; import org.jivesoftware.smackx.jingle.element.Jingle; import org.jivesoftware.smackx.jingle.element.JingleContent; @@ -112,22 +114,20 @@ public abstract class JingleSession implements JingleSessionHandler { @Override public int hashCode() { - int hashCode = 31 + getInitiator().hashCode(); - hashCode = 31 * hashCode + getResponder().hashCode(); - hashCode = 31 * hashCode + getSessionId().hashCode(); - return hashCode; + return HashCode.builder() + .append(getInitiator()) + .append(getResponder()) + .append(getSessionId()) + .build(); } @Override public boolean equals(Object other) { - if (!(other instanceof JingleSession)) { - return false; - } - - JingleSession otherJingleSession = (JingleSession) other; - return getInitiator().equals(otherJingleSession.getInitiator()) - && getResponder().equals(otherJingleSession.getResponder()) - && sid.equals(otherJingleSession.sid); + return EqualsUtil.equals(this, other, (e, o) -> + e.append(getInitiator(), o.getInitiator()) + .append(getResponder(), o.getResponder()) + .append(sid, o.sid) + ); } @Override diff --git a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java index 08433bccb..6eca9b39e 100644 --- a/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java +++ b/smack-extensions/src/main/java/org/jivesoftware/smackx/xdata/FormField.java @@ -30,6 +30,8 @@ import javax.xml.namespace.QName; import org.jivesoftware.smack.packet.FullyQualifiedElement; import org.jivesoftware.smack.packet.XmlEnvironment; import org.jivesoftware.smack.util.CollectionUtil; +import org.jivesoftware.smack.util.EqualsUtil; +import org.jivesoftware.smack.util.HashCode; import org.jivesoftware.smack.util.MultiMap; import org.jivesoftware.smack.util.XmlStringBuilder; @@ -770,33 +772,20 @@ public final class FormField implements FullyQualifiedElement { @Override public boolean equals(Object obj) { - if (obj == null) - return false; - if (obj == this) - return true; - if (obj.getClass() != getClass()) - return false; - - Option other = (Option) obj; - - if (!value.equals(other.value)) - return false; - - String thisLabel = label == null ? "" : label; - String otherLabel = other.label == null ? "" : other.label; - - if (!thisLabel.equals(otherLabel)) - return false; - - return true; + return EqualsUtil.equals(this, obj, (e, o) -> { + e.append(value, o.value) + .append(label, o.label); + }); } + private final HashCode.Cache hashCodeCache = new HashCode.Cache(); + @Override public int hashCode() { - int result = 1; - result = 37 * result + value.hashCode(); - result = 37 * result + (label == null ? 0 : label.hashCode()); - return result; + return hashCodeCache.getHashCode(c -> + c.append(value) + .append(label) + ); } } @@ -917,5 +906,15 @@ public final class FormField implements FullyQualifiedElement { xml.escape(value); return xml.closeElement(this); } + + @Override + public boolean equals(Object other) { + return EqualsUtil.equals(this, other, (e, o) -> e.append(this.value, o.value)); + } + + @Override + public int hashCode() { + return value.hashCode(); + } } } diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterEntry.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterEntry.java index df59e379a..a75a024ac 100644 --- a/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterEntry.java +++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/RosterEntry.java @@ -31,6 +31,7 @@ import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.Presence; import org.jivesoftware.smack.packet.Presence.Type; import org.jivesoftware.smack.roster.packet.RosterPacket; +import org.jivesoftware.smack.util.EqualsUtil; import org.jxmpp.jid.BareJid; @@ -251,15 +252,9 @@ public final class RosterEntry extends Manager { @Override public boolean equals(Object object) { - if (this == object) { - return true; - } - if (object != null && object instanceof RosterEntry) { - return getJid().equals(((RosterEntry) object).getJid()); - } - else { - return false; - } + return EqualsUtil.equals(this, object, (e, o) -> + e.append(getJid(), o.getJid()) + ); } /** @@ -272,14 +267,9 @@ public final class RosterEntry extends Manager { * otherwise. */ public boolean equalsDeep(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - RosterEntry other = (RosterEntry) obj; - return other.item.equals(this.item); + return EqualsUtil.equals(this, obj, (e, o) -> + e.append(item, o.item) + ); } /** diff --git a/smack-im/src/main/java/org/jivesoftware/smack/roster/packet/RosterPacket.java b/smack-im/src/main/java/org/jivesoftware/smack/roster/packet/RosterPacket.java index 12a751aec..430fa3434 100644 --- a/smack-im/src/main/java/org/jivesoftware/smack/roster/packet/RosterPacket.java +++ b/smack-im/src/main/java/org/jivesoftware/smack/roster/packet/RosterPacket.java @@ -27,6 +27,8 @@ import java.util.concurrent.CopyOnWriteArraySet; import org.jivesoftware.smack.packet.IQ; import org.jivesoftware.smack.packet.NamedElement; import org.jivesoftware.smack.packet.Stanza; +import org.jivesoftware.smack.util.EqualsUtil; +import org.jivesoftware.smack.util.HashCode; import org.jivesoftware.smack.util.Objects; import org.jivesoftware.smack.util.StringUtils; import org.jivesoftware.smack.util.XmlStringBuilder; @@ -292,51 +294,26 @@ public final class RosterPacket extends IQ { @Override public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((groupNames == null) ? 0 : groupNames.hashCode()); - result = prime * result + (subscriptionPending ? 0 : 1); - result = prime * result + ((itemType == null) ? 0 : itemType.hashCode()); - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((jid == null) ? 0 : jid.hashCode()); - result = prime * result + ((approved == false) ? 0 : 1); - return result; + return HashCode.builder() + .append(groupNames) + .append(subscriptionPending) + .append(itemType) + .append(name) + .append(jid) + .append(approved) + .build(); } @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Item other = (Item) obj; - if (groupNames == null) { - if (other.groupNames != null) - return false; - } - else if (!groupNames.equals(other.groupNames)) - return false; - if (subscriptionPending != other.subscriptionPending) - return false; - if (itemType != other.itemType) - return false; - if (name == null) { - if (other.name != null) - return false; - } - else if (!name.equals(other.name)) - return false; - if (jid == null) { - if (other.jid != null) - return false; - } - else if (!jid.equals(other.jid)) - return false; - if (approved != other.approved) - return false; - return true; + return EqualsUtil.equals(this, obj, (e, o) -> + e.append(groupNames, o.groupNames) + .append(subscriptionPending, o.subscriptionPending) + .append(itemType, o.itemType) + .append(name, o.name) + .append(jid, o.jid) + .append(approved, o.approved) + ); } }