11package redis .clients .jedis ;
22
3- import java .util .ArrayList ;
4- import java .util .Arrays ;
5- import java .util .Collection ;
6- import java .util .Collections ;
7- import java .util .Iterator ;
8- import java .util .List ;
3+ import java .util .*;
94
105import redis .clients .jedis .annots .Experimental ;
116import redis .clients .jedis .annots .Internal ;
149import redis .clients .jedis .commands .ProtocolCommand ;
1510import redis .clients .jedis .params .IParams ;
1611import redis .clients .jedis .search .RediSearchUtil ;
12+ import redis .clients .jedis .util .JedisClusterCRC16 ;
1713
1814public class CommandArguments implements Iterable <Rawable > {
1915
16+ /**
17+ * Default initial capacity for the keys list. Most Redis commands have 1-3 keys,
18+ * so a small initial capacity avoids reallocations for common cases.
19+ */
20+ private static final int DEFAULT_KEYS_CAPACITY = 4 ;
21+
2022 private CommandKeyArgumentPreProcessor keyPreProc = null ;
2123 private final ArrayList <Rawable > args ;
2224
23- private List <Object > keys ;
25+ /**
26+ * Pre-allocated list for storing keys. Using ArrayList directly avoids the
27+ * memory reallocation overhead of transitioning from emptyList -> singletonList -> ArrayList.
28+ */
29+ private final ArrayList <Object > keys ;
30+
31+ /**
32+ * Cached hash slots computed from keys. Null indicates the cache is invalid
33+ * and needs to be recomputed. The cache is invalidated when keys are added.
34+ */
35+ private Set <Integer > cachedHashSlots ;
2436
2537 private boolean blocking ;
2638
@@ -32,7 +44,8 @@ public CommandArguments(ProtocolCommand command) {
3244 args = new ArrayList <>();
3345 args .add (command );
3446
35- keys = Collections .emptyList ();
47+ keys = new ArrayList <>(DEFAULT_KEYS_CAPACITY );
48+ cachedHashSlots = null ;
3649 }
3750
3851 public ProtocolCommand getCommand () {
@@ -120,36 +133,36 @@ public CommandArguments key(Object key) {
120133
121134 if (key instanceof Rawable ) {
122135 Rawable raw = (Rawable ) key ;
123- processKey (raw .getRaw ());
124136 args .add (raw );
137+ // Extract raw bytes for hash slot computation to avoid ClassCastException in getKeyHashSlots()
138+ addHashSlotKey (raw .getRaw ());
125139 } else if (key instanceof byte []) {
126140 byte [] raw = (byte []) key ;
127- processKey (raw );
128141 args .add (RawableFactory .from (raw ));
142+ addHashSlotKey (raw );
129143 } else if (key instanceof String ) {
130144 String raw = (String ) key ;
131- processKey (raw );
132145 args .add (RawableFactory .from (raw ));
146+ addHashSlotKey (raw );
133147 } else {
134148 throw new IllegalArgumentException ("\" " + key .toString () + "\" is not a valid argument." );
135149 }
136150
137- addKeyInKeys (key );
151+ return this ;
152+ }
138153
154+ final CommandArguments addHashSlotKey (String key ) {
155+ keys .add (key );
156+ // Invalidate cached hash slots since keys have changed
157+ cachedHashSlots = null ;
139158 return this ;
140159 }
141160
142- private void addKeyInKeys (Object key ) {
143- if (keys .isEmpty ()) {
144- keys = Collections .singletonList (key );
145- } else if (keys .size () == 1 ) {
146- List oldKeys = keys ;
147- keys = new ArrayList ();
148- keys .addAll (oldKeys );
149- keys .add (key );
150- } else {
151- keys .add (key );
152- }
161+ final CommandArguments addHashSlotKey (byte [] key ) {
162+ keys .add (key );
163+ // Invalidate cached hash slots since keys have changed
164+ cachedHashSlots = null ;
165+ return this ;
153166 }
154167
155168 public final CommandArguments keys (Object ... keys ) {
@@ -167,26 +180,16 @@ public final CommandArguments addParams(IParams params) {
167180 return this ;
168181 }
169182
170- protected CommandArguments processKey (byte [] key ) {
171- // do nothing
172- return this ;
173- }
174-
175- protected final CommandArguments processKeys (byte []... keys ) {
183+ protected final CommandArguments addHashSlotKeys (byte []... keys ) {
176184 for (byte [] key : keys ) {
177- processKey (key );
185+ addHashSlotKey (key );
178186 }
179187 return this ;
180188 }
181189
182- protected CommandArguments processKey (String key ) {
183- // do nothing
184- return this ;
185- }
186-
187- protected final CommandArguments processKeys (String ... keys ) {
190+ protected final CommandArguments addHashSlotKeys (String ... keys ) {
188191 for (String key : keys ) {
189- processKey (key );
192+ addHashSlotKey (key );
190193 }
191194 return this ;
192195 }
@@ -210,9 +213,57 @@ public Iterator<Rawable> iterator() {
210213 return args .iterator ();
211214 }
212215
216+ /**
217+ * Returns the keys used in this command.
218+ * <p>
219+ * <b>Internal API:</b> This method is internal and should not be used by external code.
220+ * It is exposed for internal use by caching ({@link redis.clients.jedis.csc.CacheKey#getRedisKeys()})
221+ * and cluster operations.
222+ * <p>
223+ * <b>Supported types:</b> Keys are stored as either {@link String} or {@code byte[]} depending on
224+ * how they were added via {@link #key(Object)} or {@link #addHashSlotKey(String)}/{@link #addHashSlotKey(byte[])}.
225+ * Only {@link String} and {@code byte[]} are guaranteed to be supported by downstream consumers.
226+ * <p>
227+ * <b>Type safety:</b> Consumers must handle both {@link String} and {@code byte[]} types.
228+ * Passing other types may cause {@link IllegalArgumentException} when used with caching
229+ * (see {@link redis.clients.jedis.csc.AbstractCache#makeKeyForRedisKeysToCacheKeys(Object)})
230+ * or cluster operations.
231+ * <p>
232+ * The returned list is unmodifiable to prevent external modification of the internal key tracking.
233+ *
234+ * @return unmodifiable list of keys ({@link String} or {@code byte[]})
235+ */
213236 @ Internal
214237 public List <Object > getKeys () {
215- return keys ;
238+ return Collections .unmodifiableList (keys );
239+ }
240+
241+ @ Internal
242+ public Set <Integer > getKeyHashSlots () {
243+ // Return cached slots if available (cache is invalidated when keys are added)
244+ if (cachedHashSlots != null ) {
245+ return cachedHashSlots ;
246+ }
247+
248+ // Compute hash slots and cache the result
249+ Set <Integer > slots = new HashSet <>();
250+ for (Object key : keys ) {
251+ if (key instanceof byte []) {
252+ slots .add (JedisClusterCRC16 .getSlot ((byte []) key ));
253+ } else {
254+ slots .add (JedisClusterCRC16 .getSlot ((String ) key ));
255+ }
256+ }
257+ // Cache as unmodifiable set to prevent external modification
258+ cachedHashSlots = Collections .unmodifiableSet (slots );
259+ return cachedHashSlots ;
260+ }
261+
262+ /**
263+ * @return true if this command has no keys, false otherwise
264+ */
265+ public boolean isKeyless () {
266+ return keys .isEmpty ();
216267 }
217268
218269 public boolean isBlocking () {
0 commit comments