Skip to content

Commit 933234f

Browse files
JavaSaBrCopilot
andauthored
Extend library API part 9 (#55)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 18e9a3e commit 933234f

File tree

11 files changed

+326
-7
lines changed

11 files changed

+326
-7
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ repositories {
1414
}
1515
1616
ext {
17-
rlibVersion = "10.0.alpha9"
17+
rlibVersion = "10.0.alpha10"
1818
}
1919
2020
dependencies {

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
rootProject.version = "10.0.alpha9"
1+
rootProject.version = "10.0.alpha10"
22
group = 'javasabr.rlib'
33

44
allprojects {

rlib-collections/src/main/java/javasabr/rlib/collections/array/MutableArray.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package javasabr.rlib.collections.array;
22

33
import java.util.Collection;
4+
import java.util.Comparator;
45
import java.util.Iterator;
56
import java.util.function.Consumer;
67
import java.util.function.IntFunction;
@@ -48,4 +49,8 @@ default void forEach(Consumer<? super E> action) {
4849

4950
@Override
5051
UnsafeMutableArray<E> asUnsafe();
52+
53+
void sort();
54+
55+
void sort(Comparator<E> comparator);
5156
}

rlib-collections/src/main/java/javasabr/rlib/collections/array/impl/AbstractMutableArray.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.Arrays;
44
import java.util.Collection;
5+
import java.util.Comparator;
56
import java.util.Iterator;
67
import java.util.Objects;
78
import java.util.Spliterator;
@@ -10,6 +11,7 @@
1011
import java.util.stream.StreamSupport;
1112
import javasabr.rlib.collections.array.Array;
1213
import javasabr.rlib.collections.array.UnsafeMutableArray;
14+
import javasabr.rlib.common.util.ObjectUtils;
1315
import lombok.AccessLevel;
1416
import lombok.experimental.FieldDefaults;
1517
import org.jspecify.annotations.Nullable;
@@ -226,6 +228,31 @@ public UnsafeMutableArray<E> asUnsafe() {
226228
return this;
227229
}
228230

231+
@Override
232+
233+
public void sort() {
234+
sortInternalArray(wrapped(), size());
235+
}
236+
237+
@SuppressWarnings({
238+
"rawtypes",
239+
"unchecked"
240+
})
241+
protected void sortInternalArray(@Nullable E[] array, int size) {
242+
if (Comparable.class.isAssignableFrom(type())) {
243+
Comparable[] wrapped = (Comparable[]) array;
244+
Arrays.sort(wrapped, 0, size, Comparator.naturalOrder());
245+
} else {
246+
throw new IllegalStateException(
247+
"Cannot sort array of non-Comparable elements without an explicit comparator");
248+
}
249+
}
250+
251+
@Override
252+
public void sort(Comparator<E> comparator) {
253+
Arrays.sort(wrapped(), 0, size(), comparator);
254+
}
255+
229256
protected static void validateCapacity(int capacity) {
230257
if (capacity < 0) {
231258
throw new IllegalArgumentException("Capacity cannot be negative");

rlib-collections/src/main/java/javasabr/rlib/collections/array/impl/CopyOnWriteMutableArray.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.Arrays;
44
import java.util.Collection;
5+
import java.util.Comparator;
56
import java.util.ConcurrentModificationException;
67
import java.util.concurrent.atomic.AtomicReference;
78
import javasabr.rlib.collections.array.Array;
@@ -208,4 +209,30 @@ protected int getAndIncrementSize() {
208209
protected int decrementAnGetSize() {
209210
return 0;
210211
}
212+
213+
@Override
214+
public void sort() {
215+
for (int i = 0; i < LIMIT_ATTEMPTS; i++) {
216+
@Nullable E[] original = wrapped.get();
217+
@Nullable E[] copy = Arrays.copyOf(original, original.length);
218+
sortInternalArray(copy, copy.length);
219+
if (wrapped.compareAndSet(original, copy)) {
220+
return;
221+
}
222+
}
223+
throw new ConcurrentModificationException("Cannot successfully sort this array");
224+
}
225+
226+
@Override
227+
public void sort(Comparator<E> comparator) {
228+
for (int i = 0; i < LIMIT_ATTEMPTS; i++) {
229+
@Nullable E[] original = wrapped.get();
230+
@Nullable E[] copy = Arrays.copyOf(original, original.length);
231+
Arrays.sort(copy, comparator);
232+
if (wrapped.compareAndSet(original, copy)) {
233+
return;
234+
}
235+
}
236+
throw new ConcurrentModificationException("Cannot successfully sort this array");
237+
}
211238
}

rlib-collections/src/test/java/javasabr/rlib/collections/array/MutableArrayTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package javasabr.rlib.collections.array;
22

3+
import java.util.Comparator;
34
import java.util.List;
45
import java.util.stream.Stream;
56
import org.junit.jupiter.api.Assertions;
@@ -253,6 +254,36 @@ void shouldRenderToStringCorrectly(MutableArray<String> mutableArray) {
253254
mutableArray.toString());
254255
}
255256

257+
@ParameterizedTest
258+
@MethodSource("generateMutableArrays")
259+
@DisplayName("should sort array correctly")
260+
void shouldSortArrayCorrectly(MutableArray<String> mutableArray) {
261+
// given:
262+
mutableArray.addAll(Array.of("10", "99", "5", "3", "77", "45", "25", "56"));
263+
264+
// when:
265+
mutableArray.sort();
266+
267+
// then:
268+
var expected = Array.of("10", "25", "3", "45", "5", "56", "77", "99");
269+
Assertions.assertEquals(expected, mutableArray);
270+
}
271+
272+
@ParameterizedTest
273+
@MethodSource("generateMutableArrays")
274+
@DisplayName("should sort array using comparator correctly")
275+
void shouldSortArrayUsingComparatorCorrectly(MutableArray<String> mutableArray) {
276+
// given:
277+
mutableArray.addAll(Array.of("10", "99", "5", "3", "77", "45", "25", "56"));
278+
279+
// when:
280+
mutableArray.sort(Comparator.comparingInt(Integer::parseInt));
281+
282+
// then:
283+
var expected = Array.of("3", "5", "10", "25", "45", "56", "77", "99");
284+
Assertions.assertEquals(expected, mutableArray);
285+
}
286+
256287
private static Stream<Arguments> generateMutableArrays() {
257288
return Stream.of(
258289
Arguments.of(ArrayFactory.mutableArray(String.class)),
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package javasabr.rlib.common;
2+
3+
import java.util.Collection;
4+
5+
/**
6+
* Marks an {@link Enum} whose constants expose one or more string aliases.
7+
* <p>
8+
* Implementing enums can provide alternative string representations for each constant,
9+
* which can be used, for example, for parsing user input, configuration values,
10+
* or external identifiers that do not necessarily match the constant {@code name()}.
11+
*
12+
* @param <T> the concrete enum type implementing this interface
13+
*/
14+
public interface AliasedEnum<T extends Enum<T>> {
15+
16+
/**
17+
* Returns the string aliases associated with this enum constant.
18+
* <p>
19+
* An alias is an alternative textual identifier for the constant, such as a
20+
* short name, legacy name, or external code, that can be used for lookup
21+
* or serialization instead of the enum's {@link Enum#name()}.
22+
* <p>
23+
* Implementations should never return {@code null}; an empty collection
24+
* indicates that the constant has no aliases. Callers should not modify
25+
* the returned collection unless the implementation explicitly documents
26+
* that it is mutable.
27+
*
28+
* @return a non-{@code null} collection of aliases for this constant
29+
*/
30+
Collection<String> aliases();
31+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package javasabr.rlib.common.util;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import javasabr.rlib.common.AliasedEnum;
6+
import lombok.AccessLevel;
7+
import lombok.CustomLog;
8+
import lombok.experimental.FieldDefaults;
9+
import org.jspecify.annotations.Nullable;
10+
11+
@CustomLog
12+
@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true)
13+
public class AliasedEnumMap<T extends Enum<T> & AliasedEnum<T>> {
14+
15+
Map<String, T> aliasToValue;
16+
17+
public AliasedEnumMap(Class<T> enumClass) {
18+
var enumConstants = enumClass.getEnumConstants();
19+
var aliasToValue = new HashMap<String, T>();
20+
21+
for (T enumConstant : enumConstants) {
22+
for (String alias : enumConstant.aliases()) {
23+
T previous = aliasToValue.put(alias, enumConstant);
24+
if (previous != null) {
25+
throw new IllegalArgumentException("Detected duplicated alias:[%s] for [%s] and [%s]".formatted(
26+
alias,
27+
previous.name(),
28+
enumConstant.name()));
29+
}
30+
}
31+
}
32+
this.aliasToValue = Map.copyOf(aliasToValue);
33+
}
34+
35+
@Nullable
36+
public T resolve(String alias) {
37+
return aliasToValue.get(alias);
38+
}
39+
40+
public T resolve(String alias, T def) {
41+
T resolved = resolve(alias);
42+
return resolved == null ? def : resolved;
43+
}
44+
45+
public T require(String alias) {
46+
T constant = resolve(alias);
47+
if (constant == null) {
48+
throw new IllegalArgumentException("Unknown enum constant for alias:[%s]".formatted(alias));
49+
}
50+
return constant;
51+
}
52+
}

rlib-common/src/main/java/javasabr/rlib/common/util/StringUtils.java

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,27 @@ public static String emptyIfNull(@Nullable String string) {
3737
}
3838

3939
/**
40-
* Return the another string if the received string is empty.
40+
* Return the another string if the received string is empty or null.
4141
*
4242
* @param string the string.
4343
* @param another the another string.
44-
* @return the another string if the received string is empty.
44+
* @return the another string if the received string is empty or null.
4545
*/
4646
public static String ifEmpty(@Nullable String string, String another) {
4747
return isEmpty(string) ? another : string;
4848
}
4949

50+
/**
51+
* Return the another string if the received string is blank or null.
52+
*
53+
* @param string the string.
54+
* @param another the another string.
55+
* @return the another string if the received string is blank or null.
56+
*/
57+
public static String ifBlank(@Nullable String string, String another) {
58+
return isBlank(string) ? another : string;
59+
}
60+
5061
/**
5162
* Check a string email.
5263
*
@@ -212,7 +223,7 @@ private static MessageDigest getHashMD5() {
212223
}
213224

214225
/**
215-
* Returns true if the string empty or null.
226+
* Returns true if the string is empty or null.
216227
*
217228
* @param string the string.
218229
* @return true if the string is null or empty.
@@ -221,6 +232,16 @@ public static boolean isEmpty(@Nullable String string) {
221232
return string == null || string.isEmpty();
222233
}
223234

235+
/**
236+
* Returns true if the string is blank or null.
237+
*
238+
* @param string the string.
239+
* @return true if the string is null or blank.
240+
*/
241+
public static boolean isBlank(@Nullable String string) {
242+
return string == null || string.isBlank();
243+
}
244+
224245
/**
225246
* Returns true if the string isn't empty.
226247
*
@@ -231,6 +252,16 @@ public static boolean isNotEmpty(@Nullable String string) {
231252
return !isEmpty(string);
232253
}
233254

255+
/**
256+
* Returns true if the string isn't blank.
257+
*
258+
* @param string the string.
259+
* @return true if the string isn't blank.
260+
*/
261+
public static boolean isNotBlank(@Nullable String string) {
262+
return !isBlank(string);
263+
}
264+
234265
/**
235266
* Gets the length of the string.
236267
*
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package javasabr.rlib.common.util;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
5+
6+
import java.util.Collection;
7+
import java.util.List;
8+
import java.util.Set;
9+
import javasabr.rlib.common.AliasedEnum;
10+
import org.jspecify.annotations.NonNull;
11+
import org.junit.jupiter.api.Test;
12+
13+
class AliasedEnumMapTest {
14+
15+
public enum TestEnum implements AliasedEnum<@NonNull TestEnum> {
16+
CONSTANT1(Set.of("cnst1", "constant1", "CONSTANT1")),
17+
CONSTANT2(Set.of("cnst2", "constant2", "CONSTANT2")),
18+
CONSTANT3(Set.of("cnst3", "constant3", "CONSTANT3")),
19+
CONSTANT4(Set.of("cnst4", "constant4", "CONSTANT4")),
20+
CONSTANT5(Set.of("cnst5", "constant5", "CONSTANT5"));
21+
22+
private static final AliasedEnumMap<TestEnum> MAP = new AliasedEnumMap<>(TestEnum.class);
23+
24+
private final Collection<String> aliases;
25+
26+
TestEnum(Collection<String> aliases) {
27+
this.aliases = aliases;
28+
}
29+
30+
@Override
31+
public Collection<String> aliases() {
32+
return aliases;
33+
}
34+
}
35+
36+
@Test
37+
void shouldResolveEnumByAlias() {
38+
// when\then:
39+
assertThat(TestEnum.MAP.resolve("cnst1")).isEqualTo(TestEnum.CONSTANT1);
40+
assertThat(TestEnum.MAP.resolve("constant1")).isEqualTo(TestEnum.CONSTANT1);
41+
assertThat(TestEnum.MAP.resolve("CONSTANT1")).isEqualTo(TestEnum.CONSTANT1);
42+
assertThat(TestEnum.MAP.resolve("cnst2")).isEqualTo(TestEnum.CONSTANT2);
43+
assertThat(TestEnum.MAP.resolve("CONSTANT2")).isEqualTo(TestEnum.CONSTANT2);
44+
assertThat(TestEnum.MAP.resolve("constant3")).isEqualTo(TestEnum.CONSTANT3);
45+
assertThat(TestEnum.MAP.resolve("CONSTANT4")).isEqualTo(TestEnum.CONSTANT4);
46+
assertThat(TestEnum.MAP.resolve("unknown")).isNull();
47+
assertThat(TestEnum.MAP.resolve("")).isNull();
48+
assertThat(TestEnum.MAP.resolve("unknown", TestEnum.CONSTANT4)).isEqualTo(TestEnum.CONSTANT4);
49+
}
50+
51+
@Test
52+
void shouldRequireEnumByAlias() {
53+
// when\then:
54+
assertThat(TestEnum.MAP.require("cnst1")).isEqualTo(TestEnum.CONSTANT1);
55+
assertThat(TestEnum.MAP.require("CONSTANT2")).isEqualTo(TestEnum.CONSTANT2);
56+
assertThat(TestEnum.MAP.require("cnst3")).isEqualTo(TestEnum.CONSTANT3);
57+
assertThat(TestEnum.MAP.require("constant4")).isEqualTo(TestEnum.CONSTANT4);
58+
assertThatThrownBy(() -> TestEnum.MAP.require("unknown"))
59+
.isInstanceOf(IllegalArgumentException.class);
60+
assertThatThrownBy(() -> TestEnum.MAP.require(""))
61+
.isInstanceOf(IllegalArgumentException.class);
62+
}
63+
}

0 commit comments

Comments
 (0)