diff --git a/api/src/main/java/io/jsonwebtoken/lang/Assert.java b/api/src/main/java/io/jsonwebtoken/lang/Assert.java
index 022d58f9a..2c54dec69 100644
--- a/api/src/main/java/io/jsonwebtoken/lang/Assert.java
+++ b/api/src/main/java/io/jsonwebtoken/lang/Assert.java
@@ -347,6 +347,33 @@ public static void notEmpty(Map map) {
notEmpty(map, "[Assertion failed] - this map must not be empty; it must contain at least one entry");
}
+ /**
+ * Assert that an Object is not null
or empty.
+ *
Assert.notEmpty(object, "Object cannot be null or empty");
+ *
+ * @param object the object to check
+ * @param message the exception message to use if the assertion fails
+ * @return the non-null, non-empty object
+ * @throws IllegalArgumentException if the object is null
or empty
+ */
+ public static Object notEmpty(Object object, String message) {
+ if (Objects.isEmpty(object)) {
+ throw new IllegalArgumentException(message);
+ }
+ return object;
+ }
+
+ /**
+ * Assert that an Object is not null
or empty.
+ * Assert.notEmpty(object);
+ *
+ * @param object the object to check
+ * @throws IllegalArgumentException if the object is null
or empty
+ */
+ public static void notEmpty(Object object) {
+ notEmpty(object, "[Assertion failed] - this object must not be null or empty");
+ }
+
/**
* Assert that the provided object is an instance of the provided class.
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/lang/DefaultCollectionMutator.java b/impl/src/main/java/io/jsonwebtoken/impl/lang/DefaultCollectionMutator.java
index cdb7fd8fc..f2f1286f8 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/lang/DefaultCollectionMutator.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/lang/DefaultCollectionMutator.java
@@ -22,6 +22,7 @@
import io.jsonwebtoken.lang.Strings;
import java.util.Collection;
+import java.util.Iterator;
import java.util.LinkedHashSet;
public class DefaultCollectionMutator> implements CollectionMutator {
@@ -48,7 +49,45 @@ private boolean doAdd(E e) {
@Override
public M add(E e) {
- if (doAdd(e)) changed();
+ boolean doReplace = false;
+
+ // Replacement step 1: iterate until element to replace (if any)
+ E item;
+ Iterator it = this.collection.iterator();
+ while (it.hasNext()) {
+ item = it.next();
+
+ // Same item, nothing to do
+ if (item.equals(e))
+ return self();
+
+ boolean bothIdentifiable = e instanceof Identifiable && item instanceof Identifiable;
+ boolean sameId = bothIdentifiable && ((Identifiable) item).getId().equals(((Identifiable) e).getId());
+ if (sameId) {
+ it.remove(); // step 2: remove existing item
+ doReplace = true;
+ break;
+ }
+ }
+
+ if (doReplace) {
+ // Replacement step 3: collect and remove elements after element to replace
+ Collection elementsAfterExisting = new LinkedHashSet<>();
+ while (it.hasNext()) {
+ elementsAfterExisting.add(it.next());
+ it.remove();
+ }
+
+ this.doAdd(e); // step 4: add replacer element (position will be at the existing item)
+ this.collection.addAll(elementsAfterExisting); // step 5: add back the elements found after existing item
+
+ changed(); // trigger changed()
+ }
+ else {
+ // No replacement, do add instead
+ if (doAdd(e)) changed();
+ }
+
return self();
}
diff --git a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java
index e92277aed..11648f187 100644
--- a/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java
+++ b/impl/src/main/java/io/jsonwebtoken/impl/security/DefaultMacAlgorithm.java
@@ -222,4 +222,16 @@ protected boolean doVerify(VerifySecureDigestRequest request) {
byte[] computedSignature = digest(request);
return MessageDigest.isEqual(providedSignature, computedSignature);
}
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj instanceof DefaultMacAlgorithm) {
+ DefaultMacAlgorithm other = (DefaultMacAlgorithm) obj;
+ return super.equals(obj) && this.minKeyBitLength == other.minKeyBitLength;
+ }
+ return false;
+ }
}
diff --git a/impl/src/test/groovy/io/jsonwebtoken/impl/lang/DefaultCollectionMutatorTest.groovy b/impl/src/test/groovy/io/jsonwebtoken/impl/lang/DefaultCollectionMutatorTest.groovy
index e1b6d4021..ce5797d0e 100644
--- a/impl/src/test/groovy/io/jsonwebtoken/impl/lang/DefaultCollectionMutatorTest.groovy
+++ b/impl/src/test/groovy/io/jsonwebtoken/impl/lang/DefaultCollectionMutatorTest.groovy
@@ -16,10 +16,14 @@
package io.jsonwebtoken.impl.lang
import io.jsonwebtoken.Identifiable
+import io.jsonwebtoken.Jwts
import io.jsonwebtoken.lang.Strings
+import io.jsonwebtoken.security.MacAlgorithm
import org.junit.Before
import org.junit.Test
+import java.lang.reflect.Constructor
+
import static org.junit.Assert.*
/**
@@ -48,9 +52,17 @@ class DefaultCollectionMutatorTest {
assertTrue c.isEmpty()
}
+ @Test
+ void addNull() {
+ m.add(null)
+ assertEquals 0, changeCount
+ assertTrue m.getCollection().isEmpty() // wasn't added
+ }
+
@Test
void addEmpty() {
m.add(Strings.EMPTY)
+ assertEquals 0, changeCount
assertTrue m.getCollection().isEmpty() // wasn't added
}
@@ -95,24 +107,48 @@ class DefaultCollectionMutatorTest {
@Test(expected = IllegalArgumentException)
void addIdentifiableWithNullId() {
- def e = new Identifiable() {
- @Override
- String getId() {
- return null
- }
- }
- m.add(e)
+ m.add(new IdentifiableObject(null, null))
}
@Test(expected = IllegalArgumentException)
void addIdentifiableWithEmptyId() {
- def e = new Identifiable() {
- @Override
- String getId() {
- return ' '
- }
- }
- m.add(e)
+ m.add(new IdentifiableObject(' ', null))
+ }
+
+ @Test
+ void addIdentifiableWithSameIdEvictsExisting() {
+ m.add(new IdentifiableObject('sameId', 'foo'))
+ m.add(new IdentifiableObject('sameId', 'bar'))
+ assertEquals 2, changeCount
+ assertEquals 1, m.collection.size() // second 'add' should evict first
+ assertEquals 'bar', ((IdentifiableObject) m.collection.toArray()[0]).obj
+ }
+
+ @Test
+ void addIdentifiableWithSameIdMaintainsOrder() {
+ IdentifiableObject e1 = new IdentifiableObject('1', 'e1')
+ IdentifiableObject e2 = new IdentifiableObject('sameId', 'e2')
+ IdentifiableObject e3 = new IdentifiableObject('3', 'e3')
+ IdentifiableObject eB = new IdentifiableObject('sameId', 'eB')
+
+ m.add([e1, e2, e3])
+ m.add(eB) // replace e2 with eB
+ assertEquals 2, changeCount
+ assertArrayEquals(new Object[] {e1, eB, e3}, m.collection.toArray())
+ }
+
+ @Test
+ void addSecureDigestAlgorithmWithSameIdReplacesExisting() {
+ Class> c = Class.forName("io.jsonwebtoken.impl.security.DefaultMacAlgorithm")
+ Constructor> ctor = c.getDeclaredConstructor(String.class, String.class, int.class)
+ ctor.setAccessible(true)
+ MacAlgorithm custom = (MacAlgorithm) ctor.newInstance('HS512', 'HmacSHA512', 80)
+
+ m.add(Jwts.SIG.HS512)
+ m.add(custom)
+ assertEquals 2, changeCount // replace is count as one change
+ assertEquals 1, m.getCollection().size() // existing is removed as part of replacement
+ assertEquals 80, ((MacAlgorithm) m.getCollection().toArray()[0]).getKeyBitLength()
}
@Test
@@ -135,4 +171,19 @@ class DefaultCollectionMutatorTest {
m.clear()
assertTrue m.getCollection().isEmpty()
}
+
+ private class IdentifiableObject implements Identifiable {
+ String id
+ Object obj
+
+ IdentifiableObject(String id, Object obj) {
+ this.id = id
+ this.obj = obj
+ }
+
+ @Override
+ String getId() {
+ return id
+ }
+ }
}