[COLLECTIONS-694] Support Transformer for LazyList (#52)

Squashing and merging. @stovocor @kinow
This commit is contained in:
Stephan Windmüller 2019-07-05 17:03:18 +02:00 committed by Gary Gregory
parent 7b101411e5
commit d88810bd10
4 changed files with 219 additions and 17 deletions

View File

@ -471,6 +471,37 @@ public class ListUtils {
return LazyList.lazyList(list, factory);
}
/**
* Returns a "lazy" list whose elements will be created on demand.
* <p>
* When the index passed to the returned list's {@link List#get(int) get}
* method is greater than the list's size, then the transformer will be used
* to create a new object and that object will be inserted at that index.
* <p>
* For instance:
*
* <pre>
* List&lt;Integer&gt; hours = Arrays.asList(7, 5, 8, 2);
* Transformer&lt;Integer,Date&gt; transformer = input -&gt; LocalDateTime.now().withHour(hours.get(input));
* List&lt;LocalDateTime&gt; lazy = ListUtils.lazyList(new ArrayList&lt;LocalDateTime&gt;(), transformer);
* Date date = lazy.get(3);
* </pre>
*
* After the above code is executed, <code>date</code> will refer to
* a new <code>Date</code> instance. Furthermore, that <code>Date</code>
* instance is the fourth element in the list. The first, second,
* and third element are all set to <code>null</code>.
*
* @param <E> the element type
* @param list the list to make lazy, must not be null
* @param transformer the transformer for creating new objects, must not be null
* @return a lazy list backed by the given list
* @throws NullPointerException if the List or Transformer is null
*/
public static <E> List<E> lazyList(final List<E> list, final Transformer<Integer, ? extends E> transformer) {
return LazyList.lazyList(list, transformer);
}
/**
* Returns a fixed-sized list backed by the given list.
* Elements may not be added or removed from the returned list, but

View File

@ -17,18 +17,20 @@
package org.apache.commons.collections4.list;
import java.util.List;
import java.util.Objects;
import org.apache.commons.collections4.Factory;
import org.apache.commons.collections4.Transformer;
/**
* Decorates another <code>List</code> to create objects in the list on demand.
* <p>
* When the {@link #get(int)} method is called with an index greater than
* the size of the list, the list will automatically grow in size and return
* a new object from the specified factory. The gaps will be filled by null.
* If a get method call encounters a null, it will be replaced with a new
* object from the factory. Thus this list is unsuitable for storing null
* objects.
* a new object from the specified factory or transformer. The gaps will be
* filled by null. If a get method call encounters a null, it will be replaced
* with a new object from the factory. Thus this list is unsuitable for
* storing null objects.
* </p>
* <p>
* For instance:
@ -65,11 +67,14 @@ import org.apache.commons.collections4.Factory;
public class LazyList<E> extends AbstractSerializableListDecorator<E> {
/** Serialization version */
private static final long serialVersionUID = -1708388017160694542L;
private static final long serialVersionUID = -3677737457567429713L;
/** The factory to use to lazily instantiate the objects */
private final Factory<? extends E> factory;
/** The transformer to use to lazily instantiate the objects */
private final Transformer<Integer, ? extends E> transformer;
/**
* Factory method to create a lazily instantiating list.
*
@ -84,6 +89,20 @@ public class LazyList<E> extends AbstractSerializableListDecorator<E> {
return new LazyList<>(list, factory);
}
/**
* Transformer method to create a lazily instantiating list.
*
* @param <E> the type of the elements in the list
* @param list the list to decorate, must not be null
* @param transformer the transformer to use for creation, must not be null
* @return a new lazy list
* @throws NullPointerException if list or transformer is null
* @since 4.4
*/
public static <E> LazyList<E> lazyList(final List<E> list, final Transformer<Integer, ? extends E> transformer) {
return new LazyList<>(list, transformer);
}
//-----------------------------------------------------------------------
/**
* Constructor that wraps (not copies).
@ -94,10 +113,21 @@ public class LazyList<E> extends AbstractSerializableListDecorator<E> {
*/
protected LazyList(final List<E> list, final Factory<? extends E> factory) {
super(list);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
this.factory = Objects.requireNonNull(factory);
this.transformer = null;
}
this.factory = factory;
/**
* Constructor that wraps (not copies).
*
* @param list the list to decorate, must not be null
* @param transformer the transformer to use for creation, must not be null
* @throws NullPointerException if list or transformer is null
*/
protected LazyList(final List<E> list, final Transformer<Integer, ? extends E> transformer) {
super(list);
this.factory = null;
this.transformer = Objects.requireNonNull(transformer);
}
//-----------------------------------------------------------------------
@ -105,9 +135,10 @@ public class LazyList<E> extends AbstractSerializableListDecorator<E> {
* Decorate the get method to perform the lazy behaviour.
* <p>
* If the requested index is greater than the current size, the list will
* grow to the new size and a new object will be returned from the factory.
* Indexes in-between the old size and the requested size are left with a
* placeholder that is replaced with a factory object when requested.
* grow to the new size and a new object will be returned from the factory
* or transformer. Indexes in-between the old size and the requested size
* are left with a placeholder that is replaced with a factory or
* transformer object when requested.
*
* @param index the index to retrieve
* @return the element at the given index
@ -120,7 +151,7 @@ public class LazyList<E> extends AbstractSerializableListDecorator<E> {
E object = decorated().get(index);
if (object == null) {
// item is a place holder, create new one, set and return
object = factory.create();
object = element(index);
decorated().set(index, object);
return object;
}
@ -132,7 +163,7 @@ public class LazyList<E> extends AbstractSerializableListDecorator<E> {
decorated().add(null);
}
// create our last object, set and return
final E object = factory.create();
final E object = element(index);
decorated().add(object);
return object;
}
@ -140,7 +171,23 @@ public class LazyList<E> extends AbstractSerializableListDecorator<E> {
@Override
public List<E> subList(final int fromIndex, final int toIndex) {
final List<E> sub = decorated().subList(fromIndex, toIndex);
if (factory != null) {
return new LazyList<>(sub, factory);
} else if (transformer != null) {
return new LazyList<>(sub, transformer);
} else {
throw new IllegalStateException("Factory and Transformer are both null!");
}
}
private E element(final int index) {
if (factory != null) {
return factory.create();
} else if (transformer != null) {
return transformer.transform(index);
} else {
throw new IllegalStateException("Factory and Transformer are both null!");
}
}
}

View File

@ -145,7 +145,7 @@ public class ListUtilsTest {
}
@Test
public void testLazyList() {
public void testLazyFactoryList() {
final List<Integer> list = ListUtils.lazyList(new ArrayList<Integer>(), new Factory<Integer>() {
private int index;
@ -164,6 +164,27 @@ public class ListUtilsTest {
assertEquals(6, list.size());
}
@Test
public void testLazyTransformerList() {
final List<Integer> offsets = Arrays.asList(3, 5, 1, 5, 3, 6);
final List<Integer> list = ListUtils.lazyList(new ArrayList<>(), new Transformer<Integer, Integer>() {
private int index;
@Override
public Integer transform(Integer input) {
return offsets.get(input) + index++;
}
});
assertNotNull(list.get(5));
assertEquals(6, list.size());
assertNotNull(list.get(5));
assertEquals(6, list.size());
}
@Test
public void testEmptyIfNull() {
assertTrue(ListUtils.emptyIfNull(null).isEmpty());

View File

@ -0,0 +1,103 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.collections4.list;
import org.apache.commons.collections4.AbstractObjectTest;
import org.apache.commons.collections4.Factory;
import org.apache.commons.collections4.Transformer;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class LazyListTest extends AbstractObjectTest {
public LazyListTest(String testName) {
super(testName);
}
@Override
public Object makeObject() {
final Factory<LocalDateTime> dateFactory = LocalDateTime::now;
return new LazyList<>(new ArrayList<>(), dateFactory);
}
@Override
public void testSimpleSerialization() {
// Factory and Transformer are not serializable
}
@Override
public void testSerializeDeserializeThenCompare() {
// Factory and Transformer are not serializable
}
@Override
public void testCanonicalEmptyCollectionExists() {
// Factory and Transformer are not serializable
}
@Override
public void testCanonicalFullCollectionExists() {
// Factory and Transformer are not serializable
}
public void testElementCreationWithFactory() {
final Factory<LocalDateTime> dateFactory = LocalDateTime::now;
final List<LocalDateTime> list = new LazyList<>(new ArrayList<>(), dateFactory);
assertTrue(list.isEmpty());
final LocalDateTime firstElement = list.get(0);
assertNotNull(firstElement);
assertFalse(list.isEmpty());
}
public void testElementCreationWithTransformer() {
final Factory<LocalDateTime> dateFactory = LocalDateTime::now;
final List<LocalDateTime> list = new LazyList<>(new ArrayList<>(), dateFactory);
assertTrue(list.isEmpty());
final LocalDateTime firstElement = list.get(0);
assertNotNull(firstElement);
assertFalse(list.isEmpty());
}
public void testCreateNullGapsWithFactory() {
final Factory<LocalDateTime> dateFactory = LocalDateTime::now;
final List<LocalDateTime> list = new LazyList<>(new ArrayList<>(), dateFactory);
final LocalDateTime fourthElement = list.get(3);
assertFalse(list.isEmpty());
assertNotNull(fourthElement);
}
public void testCreateNullGapsWithTransformer() {
final List<Integer> hours = Arrays.asList(7, 5, 8, 2);
final Transformer<Integer, LocalDateTime> dateFactory = input -> LocalDateTime.now().withHour(hours.get(input));
final List<LocalDateTime> list = new LazyList<>(new ArrayList<>(), dateFactory);
final LocalDateTime fourthElement = list.get(3);
assertFalse(list.isEmpty());
assertNotNull(fourthElement);
}
}