Skip to content

Commit

Permalink
Additions to CollectionUtils.
Browse files Browse the repository at this point in the history
  • Loading branch information
marco-brandizi committed Apr 16, 2024
1 parent b62a6e7 commit aa3f14c
Show file tree
Hide file tree
Showing 3 changed files with 234 additions and 26 deletions.
4 changes: 2 additions & 2 deletions revision-history.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Revision History

*This file was last revised on 2024-01-11*. **Please keep this note updated**.
*This file was last revised on 2024-04-15*. **Please keep this note updated**.

## 14.1-SNAPSHOT
* `ChainExecutor.andThen()` added (to be tested).
* `StreamUtils.sampleStream()` added.
* `uk.ac.ebi.utils.statistics.FisherExact` imported from the USeq project.

* `CollectionUtils`, adding functions to copy collections and make them read-only.

## 14.0.1
* Various dependency upgrades.
Expand Down
139 changes: 115 additions & 24 deletions src/main/java/uk/ac/ebi/utils/collections/CollectionsUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,54 @@
*/
public class CollectionsUtils
{
/**
* Utility for as$Collection( value ) methods.
*
* @return a collection containing the value parameter. If value is null,
* an empty collection, if it's already of type C/collClass, return the
* value itself, if it's another collection, return an instance of C
* with the values copied into it.
*
* @param value
* @param collClass needed to know if value is an instance of C
* @param collCreator how to create a new empty collection when value is a collection, but
* not an instance of C/collClass. If this is null, returns value in this case (WARN: it must
* be compatible).
* @param emptyCollCreator creates and empty collection when value is null
* @param singletonCreator creates a unmodifiable singleton of type C when value is not a collection
* @param unmodifiableWrapper used to wrap the result into an unmodifiable collection of type C
*/
@SuppressWarnings ( "unchecked" )
private static <T, C extends Collection<T>> C asCollection (
Object value,
Class<C> collClass,
Supplier<C> collCreator,
Supplier<C> emptyCollCreator,
Function<Object, C> singletonCreator,
UnaryOperator<C> unmodifiableWrapper
)
{
if ( value == null ) return emptyCollCreator.get ();

C result;
if ( collClass.isInstance ( value ) )
result = (C) value;
else if ( value instanceof Collection coll )
{
if ( collCreator == null )
result = (C) coll;
else {
result = collCreator.get ();
result.addAll ( coll );
}
}
else
return singletonCreator.apply ( value );

return unmodifiableWrapper.apply ( result );
}


/**
* Converts the value into an immutable {@link Collection}.
*
Expand All @@ -33,47 +81,59 @@ public class CollectionsUtils
* If the value is already a collection, returns its
* {@link Collections#unmodifiableCollection(Collection) unmodifiable wrapper}.
*
* Note that, in the latter case, it <b>doesn't copy</b> the original collection.
*
* This is based on {@link #asCollection(Object, Class, Supplier, Supplier, Function, UnaryOperator)}.
*/
@SuppressWarnings ( "unchecked" )
public static <T> Collection<T> asCollection ( Object value )
{
if ( value == null ) return Collections.emptySet ();

if ( value instanceof Collection )
return Collections.unmodifiableCollection ( (Collection<T>) value );

return Collections.singleton ( (T) value );
return asCollection ( value,
Collection.class, // collection class
null, // collCreator
Collections::emptySet, // emptyCollCreator
Collections::singleton, // singletonCreator
Collections::unmodifiableCollection // unmodifiableWrapper
);
}

/**
* Uses {@link #asCollection(Object)} to return an unmodifiable list out of the value.
* Similar to {@link #asCollection(Object)}, returns an unmodifiable
* list out of the value.
*
* If the result from {@link #asCollection(Object)} is a list, returns it, else
* creates a list from such result and returns an unmodifiable wrapper of it.
* This is based on {@link #asCollection(Object, Class, Supplier, Supplier, Function, UnaryOperator)}.
*/
@SuppressWarnings ( { "unchecked", "rawtypes" } )
@SuppressWarnings ( { "unchecked" } )
public static <T> List<T> asList ( Object value )
{
var result = asCollection ( value );
if ( result instanceof List ) return (List<T>) result;

return Collections.unmodifiableList ( new ArrayList ( result ) );
return asCollection (
value,
List.class, // coll class
ArrayList::new, // coll creator
List::of, // empty coll creator
List::of, // singleton creator
Collections::unmodifiableList // unmodifiable wrapper
);
}


/**
* Uses {@link #asCollection(Object)} to return an unmodifiable set out of the value.
* Similar to {@link #asCollection(Object)}, returns an unmodifiable set out of the value.
* if the value is a collection, uses it to create a new set copy.
*
* If the result from {@link #asCollection(Object)} is a set, returns it, else
* creates a set from such result and returns an unmodifiable wrapper of it.
* This is based on {@link #asCollection(Object, Class, Supplier, Supplier, Function, UnaryOperator)}.
*/
@SuppressWarnings ( { "unchecked", "rawtypes" } )
@SuppressWarnings ( { "unchecked" } )
public static <T> Set<T> asSet ( Object value )
{
var result = asCollection ( value );
if ( result instanceof Set ) return (Set<T>) result;

return Collections.unmodifiableSet ( new HashSet ( result ) );
return asCollection (
value,
Set.class, // coll class
HashSet::new, // coll creator
Set::of, // empty coll creator
Set::of, // singleton creator
Collections::unmodifiableSet // unmodifiable wrapper
);
}


Expand All @@ -90,13 +150,14 @@ public static <T> Set<T> asSet ( Object value )
* if failIfMany is true, throws {@link IllegalArgumentException}
* if failIfMany is false, returns the first element in the collection value, which is then
* undetermined
*
* TODO: consider other iterables.
*/
@SuppressWarnings ( "unchecked" )
public static <T> T asValue ( Object value, boolean failIfMany )
{
if ( value == null ) return null;

if ( ! ( value instanceof Collection ) ) return (T) value;
if ( !( value instanceof Collection ) ) return (T) value;

// Deal with a collection
var coll = (Collection<T>) value;
Expand Down Expand Up @@ -156,6 +217,35 @@ public static <K,V> Map<K,V> newMap ( Map<? extends K, ? extends V> map )
}


public static <T, C extends Collection<T>> C
newCollectionIfNull ( C coll, Supplier<C> provider )
{
if ( coll != null ) return coll;
return provider.get ();
}

public static <T> Set<T> newSetIfNull ( Set<T> set )
{
return newCollectionIfNull ( set, HashSet::new );
}

public static <T> List<T> newListIfNull ( List<T> list )
{
return newCollectionIfNull ( list, ArrayList::new );
}

public static <K, V, M extends Map<K,V>> M newMapIfNull ( M map, Supplier<M> provider )
{
if ( map != null && !map.isEmpty () ) return map;
return provider.get ();
}

public static <K, V> Map<K, V> newMapIfNull ( Map<K, V> map )
{
return newMapIfNull ( map, HashMap::new );
}


public static <T, C extends Collection<T>, CP extends Collection<? extends T>>
C unmodifiableCollection (
CP coll,
Expand Down Expand Up @@ -190,4 +280,5 @@ public static <K, V> Map<K, V> unmodifiableMap ( Map<? extends K, ? extends V> m
{
return unmodifiableMap ( map, Map::of );
}

}
117 changes: 117 additions & 0 deletions src/test/java/uk/ac/ebi/utils/collections/CollectionsUtilsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package uk.ac.ebi.utils.collections;

import static java.lang.String.format;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;

import java.util.Collection;
import java.util.List;
import java.util.Set;

import org.junit.Test;

/**
* TODO: comment me!
*
* @author Marco Brandizi
* <dl><dt>Date:</dt><dd>16 Apr 2024</dd></dl>
*
*/
public class CollectionsUtilsTest
{
@Test
public void testAsSet ()
{
String s = "Hello, World";
Set<String> set = CollectionsUtils.asSet ( s );

assertNotNull ( "null result!", set );
assertEquals ( "Bad result size!", 1, set.size () );
assertTrue ( "Bad result value!", set.contains ( s ) );
}

@Test
public void testAsSetWithSet ()
{
Set<Integer> testSet = Set.of ( 1, 2, 3 );
Set<String> set = CollectionsUtils.asSet ( testSet );

assertNotNull ( "null result!", set );
assertEquals ( "Bad result size!", testSet.size (), set.size () );
assertEquals ( "Bad result value!", testSet, set );
}

@Test
public void testAsSetWithNull ()
{
Set<String> set = CollectionsUtils.asSet ( null );

assertNotNull ( "null result!", set );
assertTrue ( "Bad result size!", set.isEmpty () );
}

@Test
public void testAsSetWithCollection ()
{
List<Integer> testList = List.of ( 1, 2, 3 );
Set<Integer> set = CollectionsUtils.asSet ( testList );

assertNotNull ( "null result!", set );
assertEquals ( "Bad result size!", testList.size (), set.size () );

testList.stream ()
.forEach ( e -> assertTrue (
format ( "Bad result content (doesn't contain %s)!", e),
set.contains ( e )
));
}


@Test
public void testAsList ()
{
String s = "Hello, World";
List<String> list = CollectionsUtils.asList ( s );

assertNotNull ( "null result!", list );
assertEquals ( "Bad result size!", 1, list.size () );
assertTrue ( "Bad result value!", list.contains ( s ) );
}

@Test
public void testAsListWithList ()
{
List<Integer> testList = List.of ( 1, 2, 3 );
List<String> list = CollectionsUtils.asList ( testList );

assertNotNull ( "null result!", list );
assertEquals ( "Bad result size!", testList.size (), list.size () );
assertEquals ( "Bad result value!", testList, list );
}

@Test
public void testAsListWithNull ()
{
List<String> list = CollectionsUtils.asList ( null );

assertNotNull ( "null result!", list );
assertTrue ( "Bad result size!", list.isEmpty () );
}

@Test
public void testAsListWithCollection ()
{
Set<Integer> testSet = Set.of ( 1, 2, 3 );
List<Integer> list = CollectionsUtils.asList ( testSet );

assertNotNull ( "null result!", list );
assertEquals ( "Bad result size!", testSet.size (), list.size () );

testSet.stream ()
.forEach ( e -> assertTrue (
format ( "Bad result content (doesn't contain %s)!", e),
list.contains ( e )
));
}
}

0 comments on commit aa3f14c

Please sign in to comment.