• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nats-io / nats.java / #1995

19 Jun 2025 06:29PM UTC coverage: 95.69% (+0.03%) from 95.665%
#1995

push

github

web-flow
Merge pull request #1333 from nats-io/api-object-review-and-docs

Annotating API objects with NotNull and Nullable

17 of 19 new or added lines in 6 files covered. (89.47%)

1 existing line in 1 file now uncovered.

11744 of 12273 relevant lines covered (95.69%)

0.96 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.9
/src/main/java/io/nats/client/support/Validator.java
1
// Copyright 2020 The NATS Authors
2
// Licensed under the Apache License, Version 2.0 (the "License");
3
// you may not use this file except in compliance with the License.
4
// You may obtain a copy of the License at:
5
//
6
// http://www.apache.org/licenses/LICENSE-2.0
7
//
8
// Unless required by applicable law or agreed to in writing, software
9
// distributed under the License is distributed on an "AS IS" BASIS,
10
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
// See the License for the specific language governing permissions and
12
// limitations under the License.
13

14
package io.nats.client.support;
15

16
import java.nio.charset.StandardCharsets;
17
import java.time.Duration;
18
import java.util.Collection;
19
import java.util.List;
20
import java.util.Map;
21
import java.util.function.Supplier;
22
import java.util.regex.Pattern;
23

24
import static io.nats.client.support.NatsConstants.DOT;
25
import static io.nats.client.support.NatsJetStreamConstants.MAX_HISTORY_PER_KEY;
26

27
@SuppressWarnings("UnusedReturnValue")
28
public abstract class Validator {
29
    private Validator() {} /* ensures cannot be constructed */
30

31
    /*
32
        cannot contain spaces \r \n \t
33
        cannot start or end with subject token delimiter .
34
        some things don't allow it to end greater
35
    */
36
    public static String validateSubjectTerm(String subject, String label, boolean required) {
37
        subject = emptyAsNull(subject);
1✔
38
        if (subject == null) {
1✔
39
            if (required) {
1✔
40
                throw new IllegalArgumentException(label + " cannot be null or empty.");
1✔
41
            }
42
            return null;
1✔
43
        }
44
        if (subject.endsWith(".")) {
1✔
45
            throw new IllegalArgumentException(label + " cannot end with '.'");
1✔
46
        }
47

48
        String[] segments = subject.split("\\.");
1✔
49
        for (int seg = 0; seg < segments.length; seg++) {
1✔
50
            String segment = segments[seg];
1✔
51
            int sl = segment.length();
1✔
52
            if (sl == 0) {
1✔
53
                if (seg == 0) {
1✔
54
                    throw new IllegalArgumentException(label + " cannot start with '.'");
1✔
55
                }
56
                throw new IllegalArgumentException(label + " segment cannot be empty");
1✔
57
            }
58
            else {
59
                for (int m = 0; m < sl; m++) {
1✔
60
                    char c = segment.charAt(m);
1✔
61
                    switch (c) {
1✔
62
                        case 32:
63
                        case '\r':
64
                        case '\n':
65
                        case '\t':
66
                            throw new IllegalArgumentException(label + " cannot contain space, tab, carriage return or linefeed character");
1✔
67
                        case '*':
68
                            if (sl != 1) {
1✔
69
                                throw new IllegalArgumentException(label + " wildcard improperly placed.");
1✔
70
                            }
71
                            break;
72
                        case '>':
73
                            if (sl != 1 || (seg + 1 != segments.length)) {
1✔
74
                                throw new IllegalArgumentException(label + " wildcard improperly placed.");
1✔
75
                            }
76
                            break;
77
                    }
78
                }
79
            }
80
        }
81
        return subject;
1✔
82
    }
83

84
    public static String validateSubject(String s, boolean required) {
85
        return validateSubjectTerm(s, "Subject", required);
1✔
86
    }
87

88
    public static String validateSubject(String subject, String label, boolean required, boolean cantEndWithGt) {
89
        subject = validateSubjectTerm(subject, label, required);
×
90
        if (subject != null && cantEndWithGt && subject.endsWith(".>")) {
×
91
            throw new IllegalArgumentException(label + " last segment cannot be '>'");
×
92
        }
93
        return subject;
×
94
    }
95

96
    public static String validateReplyTo(String s, boolean required) {
97
        return validatePrintableExceptWildGt(s, "Reply To", required);
1✔
98
    }
99

100
    public static String validateQueueName(String s, boolean required) {
101
        return validateSubjectTerm(s, "QueueName", required);
1✔
102
    }
103

104
    public static String validateStreamName(String s, boolean required) {
105
        return validatePrintableExceptWildDotGtSlashes(s, "Stream", required);
1✔
106
    }
107

108
    public static String validateDurable(String s, boolean required) {
109
        return validatePrintableExceptWildDotGtSlashes(s, "Durable", required);
1✔
110
    }
111

112
    public static String validateConsumerName(String s, boolean required) {
113
        return validatePrintableExceptWildDotGtSlashes(s, "Name", required);
1✔
114
    }
115

116
    public static String validatePrefixOrDomain(String s, String label, boolean required) {
117
        return _validate(s, required, label, () -> {
1✔
118
            if (s.startsWith(DOT)) {
1✔
119
                throw new IllegalArgumentException(label + " cannot start with '.' [" + s + "]");
1✔
120
            }
121
            if (notPrintableOrHasWildGt(s)) {
1✔
122
                throw new IllegalArgumentException(label + " must be in the printable ASCII range and cannot include '*', '>' [" + s + "]");
1✔
123
            }
124
            return s;
1✔
125
        });
126
    }
127

128
    public static List<String> validateKvKeysWildcardAllowedRequired(List<String> keys) {
129
        required(keys, "Key");
1✔
130
        for (String key : keys) {
1✔
131
            validateWildcardKvKey(key, "Key", true);
1✔
132
        }
1✔
133
        return keys;
1✔
134
    }
135

136
    public static String validateKvKeyWildcardAllowedRequired(String s) {
137
        return validateWildcardKvKey(s, "Key", true);
1✔
138
    }
139

140
    public static String validateNonWildcardKvKeyRequired(String s) {
141
        return validateNonWildcardKvKey(s, "Key", true);
1✔
142
    }
143

144
    public static void validateNotSupplied(String s, NatsJetStreamClientError err) {
145
        if (!nullOrEmpty(s)) {
1✔
146
            throw err.instance();
1✔
147
        }
148
    }
1✔
149

150
    public static String validateMustMatchIfBothSupplied(String s1, String s2, NatsJetStreamClientError err) {
151
        // s1   | s2   || result
152
        // ---- | ---- || --------------
153
        // null | null || valid, null s2
154
        // null | y    || valid, y s2
155
        // x    | null || valid, x s1
156
        // x    | x    || valid, x s1
157
        // x    | y    || invalid
158
        s1 = emptyAsNull(s1);
1✔
159
        s2 = emptyAsNull(s2);
1✔
160
        if (s1 == null) {
1✔
161
            return s2; // s2 can be either null or y
1✔
162
        }
163

164
        // x / null or x / x
165
        if (s2 == null || s1.equals(s2)) {
1✔
166
            return s1;
1✔
167
        }
168

169
        throw err.instance();
1✔
170
    }
171

172
    public static String required(String s, String label) {
173
        if (emptyAsNull(s) == null) {
1✔
174
            throw new IllegalArgumentException(label + " cannot be null or empty.");
1✔
175
        }
176
        return s;
1✔
177
    }
178

179
    @Deprecated
180
    public static String required(String s1, String s2, String label) {
181
        if (emptyAsNull(s1) == null || emptyAsNull(s2) == null) {
1✔
182
            throw new IllegalArgumentException(label + " cannot be null or empty.");
1✔
183
        }
184
        return s1;
1✔
185
    }
186

187
    public static <T> T required(T o, String label) {
188
        if (o == null) {
1✔
189
            throw new IllegalArgumentException(label + " cannot be null.");
1✔
190
        }
191
        return o;
1✔
192
    }
193

194
    public static void required(List<?> l, String label) {
195
        if (l == null || l.isEmpty()) {
1✔
196
            throw new IllegalArgumentException(label + " cannot be null or empty.");
1✔
197
        }
198
    }
1✔
199

200
    public static void required(Map<?, ?> m, String label) {
201
        if (m == null || m.isEmpty()) {
1✔
202
            throw new IllegalArgumentException(label + " cannot be null or empty.");
1✔
203
        }
204
    }
1✔
205

206
    public static String _validate(String s, boolean required, String label, Supplier<String> customValidate) {
207
        if (emptyAsNull(s) == null) {
1✔
208
            if (required) {
1✔
209
                throw new IllegalArgumentException(label + " cannot be null or empty.");
1✔
210
            }
211
            return null;
1✔
212
        }
213
        return customValidate.get();
1✔
214
    }
215

216
    public static String validateMaxLength(String s, int maxLength, boolean required, String label) {
217
        return _validate(s, required, label, () -> {
1✔
218
            int len = s.getBytes(StandardCharsets.UTF_8).length;
1✔
219
            if (len > maxLength) {
1✔
220
                throw new IllegalArgumentException(label + " cannot be longer than " + maxLength + " bytes but was " + len + " bytes");
1✔
221
            }
222
            return s;
1✔
223
        });
224
    }
225

226
    public static String validatePrintable(String s, String label, boolean required) {
227
        return _validate(s, required, label, () -> {
1✔
228
            if (notPrintable(s)) {
1✔
229
                throw new IllegalArgumentException(label + " must be in the printable ASCII range [" + s + "]");
1✔
230
            }
231
            return s;
1✔
232
        });
233
    }
234

235
    public static String validatePrintableExceptWildDotGt(String s, String label, boolean required) {
236
        return _validate(s, required, label, () -> {
1✔
237
            if (notPrintableOrHasWildGtDot(s)) {
1✔
238
                throw new IllegalArgumentException(label + " must be in the printable ASCII range and cannot include '*', '.' or '>' [" + s + "]");
1✔
239
            }
240
            return s;
1✔
241
        });
242
    }
243

244
    public static String validatePrintableExceptWildDotGtSlashes(String s, String label, boolean required) {
245
        return _validate(s, required, label, () -> {
1✔
246
            if (notPrintableOrHasWildGtDotSlashes(s)) {
1✔
247
                throw new IllegalArgumentException(label + " must be in the printable ASCII range and cannot include '*', '.', '>', '\\' or  '/' [" + s + "]");
1✔
248
            }
249
            return s;
1✔
250
        });
251
    }
252

253
    public static String validatePrintableExceptWildGt(String s, String label, boolean required) {
254
        return _validate(s, required, label, () -> {
1✔
255
            if (notPrintableOrHasWildGt(s)) {
1✔
256
                throw new IllegalArgumentException(label + " must be in the printable ASCII range and cannot include '*' or '>' [" + s + "]");
1✔
257
            }
258
            return s;
1✔
259
        });
260
    }
261

262
    public static String validateIsRestrictedTerm(String s, String label, boolean required) {
263
        return _validate(s, required, label, () -> {
1✔
264
            if (notRestrictedTerm(s)) {
1✔
265
                throw new IllegalArgumentException(label + " must only contain A-Z, a-z, 0-9, '-' or '_' [" + s + "]");
1✔
266
            }
267
            return s;
1✔
268
        });
269
    }
270

271
    public static String validateBucketName(String s, boolean required) {
272
        return validateIsRestrictedTerm(s, "Bucket Name", required);
1✔
273
    }
274

275
    public static String validateWildcardKvKey(String s, String label, boolean required) {
276
        return _validate(s, required, label, () -> {
1✔
277
            if (notWildcardKvKey(s)) {
1✔
278
                throw new IllegalArgumentException(label + " must only contain A-Z, a-z, 0-9, '*', '-', '_', '/', '=', '>' or '.' and cannot start with '.' [" + s + "]");
1✔
279
            }
280
            return s;
1✔
281
        });
282
    }
283

284
    public static String validateNonWildcardKvKey(String s, String label, boolean required) {
285
        return _validate(s, required, label, () -> {
1✔
286
            if (notNonWildcardKvKey(s)) {
1✔
287
                throw new IllegalArgumentException(label + " must only contain A-Z, a-z, 0-9, '-', '_', '/', '=' or '.' and cannot start with '.' [" + s + "]");
1✔
288
            }
289
            return s;
1✔
290
        });
291
    }
292

293
    public static long validateMaxConsumers(long max) {
294
        return validateGtZeroOrMinus1(max, "Max Consumers");
1✔
295
    }
296

297
    public static long validateMaxMessages(long max) {
298
        return validateGtZeroOrMinus1(max, "Max Messages");
1✔
299
    }
300

301
    public static long validateMaxMessagesPerSubject(long max) {
302
        return validateGtZeroOrMinus1(max, "Max Messages Per Subject");
1✔
303
    }
304

305
    public static int validateMaxHistory(int max) {
306
        if (max < 1 || max > MAX_HISTORY_PER_KEY) {
1✔
307
            throw new IllegalArgumentException("Max History must be from 1 to " + MAX_HISTORY_PER_KEY + " inclusive.");
1✔
308
        }
309
        return max;
1✔
310
    }
311

312
    public static long validateMaxBytes(long max) {
313
        return validateGtZeroOrMinus1(max, "Max Bytes");
1✔
314
    }
315

316
    public static long validateMaxBucketBytes(long max) {
317
        return validateGtZeroOrMinus1(max, "Max Bucket Bytes"); // max bucket bytes is a kv alias to max bytes
1✔
318
    }
319

320
    private static long validateMaxMessageSize(long max, String label) {
321
        long l = validateGtZeroOrMinus1(max, label);
1✔
322
        if (l > Integer.MAX_VALUE) {
1✔
323
            throw new IllegalArgumentException(label + " cannot be larger than " + Integer.MAX_VALUE);
1✔
324
        }
325
        return l;
1✔
326
    }
327

328
    public static long validateMaxMessageSize(long max) {
329
        return validateMaxMessageSize(max, "Max Message Size");
1✔
330
    }
331

332
    public static long validateMaxValueSize(long max) {
333
        return validateMaxMessageSize(max, "Max Value Size"); // max value size is a kv alias to max message size
1✔
334
    }
335

336
    public static int validateNumberOfReplicas(int replicas) {
337
        if (replicas < 1 || replicas > 5) {
1✔
338
            throw new IllegalArgumentException("Replicas must be from 1 to 5 inclusive.");
1✔
339
        }
340
        return replicas;
1✔
341
    }
342

343
    public static Duration validateDurationRequired(Duration d) {
344
        if (d == null || d.isZero() || d.isNegative()) {
1✔
345
            throw new IllegalArgumentException("Duration required and must be greater than 0.");
1✔
346
        }
347
        return d;
1✔
348
    }
349

350
    public static Duration validateDurationNotRequiredGtOrEqZero(Duration d, Duration ifNull) {
351
        if (d == null) {
1✔
352
            return ifNull;
1✔
353
        }
354
        if (d.isNegative()) {
1✔
355
            throw new IllegalArgumentException("Duration must be greater than or equal to 0.");
1✔
356
        }
357
        return d;
1✔
358
    }
359

360
    public static Duration validateDurationNotRequiredGtOrEqZero(long millis) {
361
        if (millis < 0) {
1✔
362
            throw new IllegalArgumentException("Duration must be greater than or equal to 0.");
1✔
363
        }
364
        return Duration.ofMillis(millis);
1✔
365
    }
366

367
    public static Duration validateDurationNotRequiredGtOrEqSeconds(long minSeconds, Duration d, Duration ifNull, String label) {
368
        return d == null ? ifNull : validateDurationGtOrEqSeconds(minSeconds, d.toMillis(), label);
1✔
369
    }
370

371
    public static Duration validateDurationGtOrEqSeconds(long minSeconds, long millis, String label) {
372
        if (millis < (minSeconds * 1000)) {
1✔
373
            throw new IllegalArgumentException(label + " must be greater than or equal to " + minSeconds + " second(s).");
1✔
374
        }
375
        return Duration.ofMillis(millis);
1✔
376
    }
377

378
    public static String validateNotNull(String s, String fieldName) {
379
        if (s == null) {
1✔
380
            throw new IllegalArgumentException(fieldName + " cannot be null");
1✔
381
        }
382
        return s;
1✔
383
    }
384

385
    public static Object validateNotNull(Object o, String fieldName) {
386
        if (o == null) {
1✔
387
            throw new IllegalArgumentException(fieldName + " cannot be null");
1✔
388
        }
389
        return o;
1✔
390
    }
391

392
    public static int validateGtZero(int i, String label) {
393
        if (i < 1) {
1✔
394
            throw new IllegalArgumentException(label + " must be greater than zero");
1✔
395
        }
396
        return i;
1✔
397
    }
398

399
    public static long validateGtZero(long l, String label) {
400
        if (l < 1) {
1✔
401
            throw new IllegalArgumentException(label + " must be greater than zero");
1✔
402
        }
403
        return l;
1✔
404
    }
405

406
    public static long validateGtZeroOrMinus1(long l, String label) {
407
        if (zeroOrLtMinus1(l)) {
1✔
408
            throw new IllegalArgumentException(label + " must be greater than zero or -1 for unlimited");
1✔
409
        }
410
        return l;
1✔
411
    }
412

413
    public static long validateGtEqMinus1(long l, String label) {
414
        if (l < -1) {
1✔
415
            throw new IllegalArgumentException(label + " must be greater than zero or -1 for unlimited");
1✔
416
        }
417
        return l;
1✔
418
    }
419

420
    public static long validateNotNegative(long l, String label) {
421
        if (l < 0) {
1✔
422
            throw new IllegalArgumentException(label + " cannot be negative");
1✔
423
        }
424
        return l;
1✔
425
    }
426

427
    public static boolean isGtEqZero(long l) {
428
        return l >= 0;
1✔
429
    }
430

431
    public static long validateGtEqZero(long l, String label) {
432
        if (l < 0) {
1✔
433
            throw new IllegalArgumentException(label + " must be greater than or equal to zero");
1✔
434
        }
435
        return l;
1✔
436
    }
437

438
    // ----------------------------------------------------------------------------------------------------
439
    // Helpers
440
    // ----------------------------------------------------------------------------------------------------
441
    public static boolean nullOrEmpty(String s) {
442
        return s == null || s.trim().isEmpty();
1✔
443
    }
444

445
    public static boolean nullOrEmpty(String[] a) {
NEW
446
        return a == null || a.length == 0;
×
447
    }
448

449
    public static boolean nullOrEmpty(Collection<?> c) {
NEW
450
        return c == null || c.isEmpty();
×
451
    }
452

453
    public static boolean notPrintable(String s) {
454
        for (int x = 0; x < s.length(); x++) {
1✔
455
            char c = s.charAt(x);
1✔
456
            if (c < 33 || c > 126) {
1✔
457
                return true;
1✔
458
            }
459
        }
460
        return false;
1✔
461
    }
462

463
    public static boolean notPrintableOrHasChars(String s, char[] charsToNotHave) {
464
        for (int x = 0; x < s.length(); x++) {
1✔
465
            char c = s.charAt(x);
1✔
466
            if (c < 33 || c > 126) {
1✔
467
                return true;
1✔
468
            }
469
            for (char cx : charsToNotHave) {
1✔
470
                if (c == cx) {
1✔
471
                    return true;
1✔
472
                }
473
            }
474
        }
475
        return false;
1✔
476
    }
477

478
    // restricted-term  = (A-Z, a-z, 0-9, dash 45, underscore 95)+
479
    public static boolean notRestrictedTerm(String s) {
480
        for (int x = 0; x < s.length(); x++) {
1✔
481
            char c = s.charAt(x);
1✔
482
            if (c < '0') { // before 0
1✔
483
                if (c == '-') { // only dash is accepted
1✔
484
                    continue;
1✔
485
                }
486
                return true; // "not"
1✔
487
            }
488
            if (c < ':') {
1✔
489
                continue; // means it's 0 - 9
1✔
490
            }
491
            if (c < 'A') {
1✔
492
                return true; // between 9 and A is "not restricted"
1✔
493
            }
494
            if (c < '[') {
1✔
495
                continue; // means it's A - Z
1✔
496
            }
497
            if (c < 'a') { // before a
1✔
498
                if (c == '_') { // only underscore is accepted
1✔
499
                    continue;
1✔
500
                }
501
                return true; // "not"
1✔
502
            }
503
            if (c > 'z') { // 122 is z, characters after of them are "not restricted"
1✔
504
                return true;
1✔
505
            }
506
        }
507
        return false;
1✔
508
    }
509

510
    // limited-term = (A-Z, a-z, 0-9, dash 45, dot 46, fwd-slash 47, equals 61, underscore 95)+
511
    // kv-key-name = limited-term (dot limited-term)*
512
    public static boolean notNonWildcardKvKey(String s) {
513
        if (s.charAt(0) == '.') {
1✔
514
            return true; // can't start with dot
1✔
515
        }
516
        for (int x = 0; x < s.length(); x++) {
1✔
517
            char c = s.charAt(x);
1✔
518
            if (c < '0') { // before 0
1✔
519
                if (c == '-' || c == '.' || c == '/') { // only dash dot and fwd slash are accepted
1✔
520
                    continue;
1✔
521
                }
522
                return true; // "not"
1✔
523
            }
524
            if (c < ':') {
1✔
525
                continue; // means it's 0 - 9
1✔
526
            }
527
            if (c < 'A') {
1✔
528
                if (c == '=') { // equals is accepted
1✔
529
                    continue;
1✔
530
                }
531
                return true; // between 9 and A is "not limited"
1✔
532
            }
533
            if (c < '[') {
1✔
534
                continue; // means it's A - Z
1✔
535
            }
536
            if (c < 'a') { // before a
1✔
537
                if (c == '_') { // only underscore is accepted
1✔
538
                    continue;
1✔
539
                }
540
                return true; // "not"
1✔
541
            }
542
            if (c > 'z') { // 122 is z, characters after of them are "not limited"
1✔
543
                return true;
1✔
544
            }
545
        }
546
        return false;
1✔
547
    }
548

549
    // (A-Z, a-z, 0-9, star 42, dash 45, dot 46, fwd-slash 47, equals 61, gt 62, underscore 95)+
550
    public static boolean notWildcardKvKey(String s) {
551
        if (s.charAt(0) == '.') {
1✔
552
            return true; // can't start with dot
1✔
553
        }
554
        for (int x = 0; x < s.length(); x++) {
1✔
555
            char c = s.charAt(x);
1✔
556
            if (c < '0') { // before 0
1✔
557
                if (c == '*' || c == '-' || c == '.' || c == '/') { // only star dash dot and fwd slash are accepted
1✔
558
                    continue;
1✔
559
                }
560
                return true; // "not"
1✔
561
            }
562
            if (c < ':') {
1✔
563
                continue; // means it's 0 - 9
1✔
564
            }
565
            if (c < 'A') {
1✔
566
                if (c == '=' || c == '>') { // equals, gt is accepted
1✔
567
                    continue;
1✔
568
                }
569
                return true; // between 9 and A is "not limited"
1✔
570
            }
571
            if (c < '[') {
1✔
572
                continue; // means it's A - Z
1✔
573
            }
574
            if (c < 'a') { // before a
1✔
575
                if (c == '_') { // only underscore is accepted
1✔
576
                    continue;
1✔
577
                }
578
                return true; // "not"
1✔
579
            }
580
            if (c > 'z') { // 122 is z, characters after of them are "not limited"
1✔
581
                return true;
1✔
582
            }
583
        }
584
        return false;
1✔
585
    }
586

587
    static final char[] WILD_GT = {'*', '>'};
1✔
588
    static final char[] WILD_GT_DOT = {'*', '>', '.'};
1✔
589
    static final char[] WILD_GT_DOT_SLASHES = {'*', '>', '.', '\\', '/'};
1✔
590

591
    private static boolean notPrintableOrHasWildGt(String s) {
592
        return notPrintableOrHasChars(s, WILD_GT);
1✔
593
    }
594

595
    private static boolean notPrintableOrHasWildGtDot(String s) {
596
        return notPrintableOrHasChars(s, WILD_GT_DOT);
1✔
597
    }
598

599
    private static boolean notPrintableOrHasWildGtDotSlashes(String s) {
600
        return notPrintableOrHasChars(s, WILD_GT_DOT_SLASHES);
1✔
601
    }
602

603
    public static String emptyAsNull(String s) {
604
        return nullOrEmpty(s) ? null : s;
1✔
605
    }
606

607
    public static String emptyOrNullAs(String s, String ifEmpty) {
608
        return nullOrEmpty(s) ? ifEmpty : s;
1✔
609
    }
610

611
    public static boolean zeroOrLtMinus1(long l) {
612
        return l == 0 || l < -1;
1✔
613
    }
614

615
    public static Duration ensureNotNullAndNotLessThanMin(Duration provided, Duration minimum, Duration dflt)
616
    {
617
        return provided == null || provided.toNanos() < minimum.toNanos() ? dflt : provided;
1✔
618
    }
619

620
    public static Duration ensureDurationNotLessThanMin(long providedMillis, Duration minimum, Duration dflt)
621
    {
622
        return ensureNotNullAndNotLessThanMin(Duration.ofMillis(providedMillis), minimum, dflt);
1✔
623
    }
624

625
    public static String ensureEndsWithDot(String s) {
626
        return s == null || s.endsWith(DOT) ? s : s + DOT;
1✔
627
    }
628

629
    static final Pattern SEMVER_PATTERN = Pattern.compile("^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?$");
1✔
630

631
    public static String validateSemVer(String s, String label, boolean required) {
632
        return _validate(s, required, label, () -> {
1✔
633
            if (!isSemVer(s)) {
1✔
634
                throw new IllegalArgumentException(label + " must be a valid SemVer");
1✔
635
            }
636
            return s;
1✔
637
        });
638
    }
639

640
    public static boolean isSemVer(String s) {
641
        return SEMVER_PATTERN.matcher(s).find();
1✔
642
    }
643

644
    // This function tests filter subject equivalency
645
    // It does not care what order and also assumes that there are no duplicates.
646
    // From the server: consumer subject filters cannot overlap [10138]
647
    public static <T> boolean listsAreEquivalent(List<T> l1, List<T> l2)
648
    {
649
        if (l1 == null || l1.isEmpty()) {
1✔
650
            return l2 == null || l2.isEmpty();
1✔
651
        }
652

653
        if (l2 == null || l1.size() != l2.size()) {
1✔
654
            return false;
1✔
655
        }
656

657
        for (T t : l1) {
1✔
658
            if (!l2.contains(t)) {
1✔
659
                return false;
1✔
660
            }
661
        }
1✔
662
        return true;
1✔
663
    }
664

665
    public static boolean mapsAreEquivalent(Map<String, String> m1, Map<String, String> m2)
666
    {
667
        int s1 = m1 == null ? 0 : m1.size();
1✔
668
        int s2 = m2 == null ? 0 : m2.size();
1✔
669

670
        if (s1 != s2) {
1✔
671
            return false;
1✔
672
        }
673

674
        if (s1 > 0) {
1✔
675
            for (Map.Entry<String, String> entry : m1.entrySet())
1✔
676
            {
677
                if (!entry.getValue().equals(m2.get(entry.getKey()))) {
1✔
678
                    return false;
1✔
679
                }
680
            }
1✔
681
        }
682

683
        return true;
1✔
684
    }
685

686
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc