Compare commits

...

1 Commits

Author SHA1 Message Date
Florian Hussonnois
9ca58283f9 chore(system): add utility Pair class
Adds the Pair class to standardize the code
and avoid any unnecessary dependencies on third-party libraries
for this type of utility.
2025-08-06 18:14:02 +02:00
2 changed files with 314 additions and 0 deletions

View File

@@ -0,0 +1,176 @@
package io.kestra.core.utils;
import java.util.Map;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Stream;
/**
* An immutable tuple of two elements.
*
* @param <T1> the type of the 1st element.
* @param <T2> the type of the 2nd element.
*/
public record Pair<T1, T2>(T1 _1, T2 _2) {
/**
* Creates a {@code Pair} from a Map.Entry.
*
* @param entry the Map.Entry to convert
* @param <T1> the key type
* @param <T2> the value type
* @return a new Pair with the key and value from the entry
*/
public static <T1, T2> Pair<T1, T2> of(Map.Entry<T1, T2> entry) {
return new Pair<>(entry.getKey(), entry.getValue());
}
/**
* Creates a {@code Pair} from two values.
*
* @param o1 the first value
* @param o2 the second value
* @param <T1> the type of the first value
* @param <T2> the type of the second value
* @return a new Pair of the two values
*/
public static <T1, T2> Pair<T1, T2> of(T1 o1, T2 o2) {
return new Pair<>(o1, o2);
}
/**
* Creates a tuple of two elements.
*
* @param _1 the 1st element
* @param _2 the 2nd element
*/
public Pair {
}
/**
* Alias for {@link #_1()}.
*
* @return the first element
*/
public T1 key() {
return this._1;
}
/**
* Alias for {@link #_2()}.
*
* @return the second element
*/
public T2 value() {
return this._2;
}
/**
* Alias for {@link #_1()}.
*
* @return the first element
*/
public T1 left() {
return this._1;
}
/**
* Alias for {@link #_2()}.
*
* @return the second element
*/
public T2 right() {
return this._2;
}
/**
* Transforms the first element of the pair.
*
* @param mapper function to transform the first element
* @param <R> the result type of the transformation
* @return a new Pair with the transformed first element
*/
public <R> Pair<R, T2> mapLeft(Function<? super T1, ? extends R> mapper) {
return new Pair<>(mapper.apply(_1), _2);
}
/**
* Transforms the second element of the pair.
*
* @param mapper function to transform the second element
* @param <R> the result type of the transformation
* @return a new Pair with the transformed second element
*/
public <R> Pair<T1, R> mapRight(Function<? super T2, ? extends R> mapper) {
return new Pair<>(_1, mapper.apply(_2));
}
/**
* Transforms both elements of the pair.
*
* @param leftMapper function to transform the first element
* @param rightMapper function to transform the second element
* @param <R1> result type of first element
* @param <R2> result type of second element
* @return a new Pair with both elements transformed
*/
public <R1, R2> Pair<R1, R2> mapBoth(Function<? super T1, ? extends R1> leftMapper,
Function<? super T2, ? extends R2> rightMapper) {
return new Pair<>(leftMapper.apply(_1), rightMapper.apply(_2));
}
/**
* Applies a bi-function to both elements.
*
* @param mapper function to apply to both elements
* @param <R> result type
* @return the result of applying the function to the pair
*/
public <R> R reduce(BiFunction<? super T1, ? super T2, ? extends R> mapper) {
return mapper.apply(_1, _2);
}
/**
* Returns a {@link Stream} containing only this pair.
*
* @return a singleton stream of this pair
*/
public Stream<Pair<T1, T2>> stream() {
return Stream.of(this);
}
/**
* Converts this pair into a {@link Map.Entry}.
*
* @return a Map.Entry representing the pair
*/
public Map.Entry<T1, T2> asMapEntry() {
return Map.entry(this._1, this._2);
}
/**
* Swaps the elements of the pair.
*
* @return a new Pair with the elements swapped
*/
public Pair<T2, T1> swap() {
return new Pair<>(_2, _1);
}
/**
* Checks if both elements are null.
*
* @return true if both elements are null
*/
public boolean isEmpty() {
return _1 == null && _2 == null;
}
/**
* {@inheritDoc}
**/
@Override
public String toString() {
return "(" + _1 + "," + _2 + ')';
}
}

View File

@@ -0,0 +1,138 @@
package io.kestra.core.utils;
import org.junit.jupiter.api.Test;
import java.util.AbstractMap;
import java.util.Map;
import static org.assertj.core.api.AssertionsForClassTypes.*;
class PairTest {
@Test
void shouldCreatesPairGivenTwoValues() {
// Given
String key = "foo";
Integer value = 123;
// When
Pair<String, Integer> pair = Pair.of(key, value);
// Then
assertThat(pair._1()).isEqualTo("foo");
assertThat(pair._2()).isEqualTo(123);
}
@Test
void shouldCreatesPairGivenMapEntry() {
// Given
Map.Entry<String, Integer> entry = new AbstractMap.SimpleEntry<>("bar", 42);
// When
Pair<String, Integer> pair = Pair.of(entry);
// Then
assertThat(pair.key()).isEqualTo("bar");
assertThat(pair.value()).isEqualTo(42);
}
@Test
void shouldTransformsFirstElement() {
// Given
Pair<String, Integer> pair = Pair.of("test", 10);
// When
Pair<Integer, Integer> result = pair.mapLeft(String::length);
// Then
assertThat(result._1()).isEqualTo(4);
assertThat(result._2()).isEqualTo(10);
}
@Test
void shouldTransformsSecondElement() {
// Given
Pair<String, Integer> pair = Pair.of("hello", 5);
// When
Pair<String, String> result = pair.mapRight(Object::toString);
// Then
assertThat(result._1()).isEqualTo("hello");
assertThat(result._2()).isEqualTo("5");
}
@Test
void shouldSwapElements() {
// Given
Pair<String, Integer> pair = Pair.of("left", 99);
// When
Pair<Integer, String> swapped = pair.swap();
// Then
assertThat(swapped._1()).isEqualTo(99);
assertThat(swapped._2()).isEqualTo("left");
}
@Test
void shouldGetStreamOfOneElement() {
// Given
Pair<String, Integer> pair = Pair.of("stream", 1);
// When
long count = pair.stream().count();
// Then
assertThat(count).isEqualTo(1);
}
@Test
void shouldGetAsMapEntry() {
// Given
Pair<String, Integer> pair = Pair.of("key", 888);
// When
Map.Entry<String, Integer> entry = pair.asMapEntry();
// Then
assertThat(entry.getKey()).isEqualTo("key");
assertThat(entry.getValue()).isEqualTo(888);
}
@Test
void shouldCheckIfBothElementsAreNull() {
// Given
Pair<String, Integer> pair = Pair.of(null, null);
// When
boolean isEmpty = pair.isEmpty();
// Then
assertThat(isEmpty).isTrue();
}
@Test
void shouldTransformsBothElements() {
// Given
Pair<String, Integer> pair = Pair.of("abc", 7);
// When
Pair<Integer, String> result = pair.mapBoth(String::length, Object::toString);
// Then
assertThat(result).isEqualTo(Pair.of(3, "7"));
}
@Test
void shouldReduceElements() {
// Given
Pair<String, Integer> pair = Pair.of("val", 2);
// When
String result = pair.reduce((s, i) -> s + "-" + i);
// Then
assertThat(result).isEqualTo("val-2");
}
}