001/*
002 * Copyright 2002-2018 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *      https://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package org.springframework.util;
018
019import java.io.Serializable;
020import java.lang.reflect.InvocationTargetException;
021import java.lang.reflect.Modifier;
022import java.util.ArrayList;
023import java.util.Collection;
024import java.util.Iterator;
025import java.util.List;
026import java.util.ListIterator;
027
028import org.springframework.lang.Nullable;
029
030/**
031 * Simple {@link List} wrapper class that allows for elements to be
032 * automatically populated as they are requested. This is particularly
033 * useful for data binding to {@link List Lists}, allowing for elements
034 * to be created and added to the {@link List} in a "just in time" fashion.
035 *
036 * <p>Note: This class is not thread-safe. To create a thread-safe version,
037 * use the {@link java.util.Collections#synchronizedList} utility methods.
038 *
039 * <p>Inspired by {@code LazyList} from Commons Collections.
040 *
041 * @author Rob Harrop
042 * @author Juergen Hoeller
043 * @since 2.0
044 * @param <E> the element type
045 */
046@SuppressWarnings("serial")
047public class AutoPopulatingList<E> implements List<E>, Serializable {
048
049        /**
050         * The {@link List} that all operations are eventually delegated to.
051         */
052        private final List<E> backingList;
053
054        /**
055         * The {@link ElementFactory} to use to create new {@link List} elements
056         * on demand.
057         */
058        private final ElementFactory<E> elementFactory;
059
060
061        /**
062         * Creates a new {@code AutoPopulatingList} that is backed by a standard
063         * {@link ArrayList} and adds new instances of the supplied {@link Class element Class}
064         * to the backing {@link List} on demand.
065         */
066        public AutoPopulatingList(Class<? extends E> elementClass) {
067                this(new ArrayList<>(), elementClass);
068        }
069
070        /**
071         * Creates a new {@code AutoPopulatingList} that is backed by the supplied {@link List}
072         * and adds new instances of the supplied {@link Class element Class} to the backing
073         * {@link List} on demand.
074         */
075        public AutoPopulatingList(List<E> backingList, Class<? extends E> elementClass) {
076                this(backingList, new ReflectiveElementFactory<>(elementClass));
077        }
078
079        /**
080         * Creates a new {@code AutoPopulatingList} that is backed by a standard
081         * {@link ArrayList} and creates new elements on demand using the supplied {@link ElementFactory}.
082         */
083        public AutoPopulatingList(ElementFactory<E> elementFactory) {
084                this(new ArrayList<>(), elementFactory);
085        }
086
087        /**
088         * Creates a new {@code AutoPopulatingList} that is backed by the supplied {@link List}
089         * and creates new elements on demand using the supplied {@link ElementFactory}.
090         */
091        public AutoPopulatingList(List<E> backingList, ElementFactory<E> elementFactory) {
092                Assert.notNull(backingList, "Backing List must not be null");
093                Assert.notNull(elementFactory, "Element factory must not be null");
094                this.backingList = backingList;
095                this.elementFactory = elementFactory;
096        }
097
098
099        @Override
100        public void add(int index, E element) {
101                this.backingList.add(index, element);
102        }
103
104        @Override
105        public boolean add(E o) {
106                return this.backingList.add(o);
107        }
108
109        @Override
110        public boolean addAll(Collection<? extends E> c) {
111                return this.backingList.addAll(c);
112        }
113
114        @Override
115        public boolean addAll(int index, Collection<? extends E> c) {
116                return this.backingList.addAll(index, c);
117        }
118
119        @Override
120        public void clear() {
121                this.backingList.clear();
122        }
123
124        @Override
125        public boolean contains(Object o) {
126                return this.backingList.contains(o);
127        }
128
129        @Override
130        public boolean containsAll(Collection<?> c) {
131                return this.backingList.containsAll(c);
132        }
133
134        /**
135         * Get the element at the supplied index, creating it if there is
136         * no element at that index.
137         */
138        @Override
139        public E get(int index) {
140                int backingListSize = this.backingList.size();
141                E element = null;
142                if (index < backingListSize) {
143                        element = this.backingList.get(index);
144                        if (element == null) {
145                                element = this.elementFactory.createElement(index);
146                                this.backingList.set(index, element);
147                        }
148                }
149                else {
150                        for (int x = backingListSize; x < index; x++) {
151                                this.backingList.add(null);
152                        }
153                        element = this.elementFactory.createElement(index);
154                        this.backingList.add(element);
155                }
156                return element;
157        }
158
159        @Override
160        public int indexOf(Object o) {
161                return this.backingList.indexOf(o);
162        }
163
164        @Override
165        public boolean isEmpty() {
166                return this.backingList.isEmpty();
167        }
168
169        @Override
170        public Iterator<E> iterator() {
171                return this.backingList.iterator();
172        }
173
174        @Override
175        public int lastIndexOf(Object o) {
176                return this.backingList.lastIndexOf(o);
177        }
178
179        @Override
180        public ListIterator<E> listIterator() {
181                return this.backingList.listIterator();
182        }
183
184        @Override
185        public ListIterator<E> listIterator(int index) {
186                return this.backingList.listIterator(index);
187        }
188
189        @Override
190        public E remove(int index) {
191                return this.backingList.remove(index);
192        }
193
194        @Override
195        public boolean remove(Object o) {
196                return this.backingList.remove(o);
197        }
198
199        @Override
200        public boolean removeAll(Collection<?> c) {
201                return this.backingList.removeAll(c);
202        }
203
204        @Override
205        public boolean retainAll(Collection<?> c) {
206                return this.backingList.retainAll(c);
207        }
208
209        @Override
210        public E set(int index, E element) {
211                return this.backingList.set(index, element);
212        }
213
214        @Override
215        public int size() {
216                return this.backingList.size();
217        }
218
219        @Override
220        public List<E> subList(int fromIndex, int toIndex) {
221                return this.backingList.subList(fromIndex, toIndex);
222        }
223
224        @Override
225        public Object[] toArray() {
226                return this.backingList.toArray();
227        }
228
229        @Override
230        public <T> T[] toArray(T[] a) {
231                return this.backingList.toArray(a);
232        }
233
234
235        @Override
236        public boolean equals(@Nullable Object other) {
237                return this.backingList.equals(other);
238        }
239
240        @Override
241        public int hashCode() {
242                return this.backingList.hashCode();
243        }
244
245
246        /**
247         * Factory interface for creating elements for an index-based access
248         * data structure such as a {@link java.util.List}.
249         *
250         * @param <E> the element type
251         */
252        @FunctionalInterface
253        public interface ElementFactory<E> {
254
255                /**
256                 * Create the element for the supplied index.
257                 * @return the element object
258                 * @throws ElementInstantiationException if the instantiation process failed
259                 * (any exception thrown by a target constructor should be propagated as-is)
260                 */
261                E createElement(int index) throws ElementInstantiationException;
262        }
263
264
265        /**
266         * Exception to be thrown from ElementFactory.
267         */
268        public static class ElementInstantiationException extends RuntimeException {
269
270                public ElementInstantiationException(String msg) {
271                        super(msg);
272                }
273
274                public ElementInstantiationException(String message, Throwable cause) {
275                        super(message, cause);
276                }
277        }
278
279
280        /**
281         * Reflective implementation of the ElementFactory interface, using
282         * {@code Class.getDeclaredConstructor().newInstance()} on a given element class.
283         */
284        private static class ReflectiveElementFactory<E> implements ElementFactory<E>, Serializable {
285
286                private final Class<? extends E> elementClass;
287
288                public ReflectiveElementFactory(Class<? extends E> elementClass) {
289                        Assert.notNull(elementClass, "Element class must not be null");
290                        Assert.isTrue(!elementClass.isInterface(), "Element class must not be an interface type");
291                        Assert.isTrue(!Modifier.isAbstract(elementClass.getModifiers()), "Element class cannot be an abstract class");
292                        this.elementClass = elementClass;
293                }
294
295                @Override
296                public E createElement(int index) {
297                        try {
298                                return ReflectionUtils.accessibleConstructor(this.elementClass).newInstance();
299                        }
300                        catch (NoSuchMethodException ex) {
301                                throw new ElementInstantiationException(
302                                                "No default constructor on element class: " + this.elementClass.getName(), ex);
303                        }
304                        catch (InstantiationException ex) {
305                                throw new ElementInstantiationException(
306                                                "Unable to instantiate element class: " + this.elementClass.getName(), ex);
307                        }
308                        catch (IllegalAccessException ex) {
309                                throw new ElementInstantiationException(
310                                                "Could not access element constructor: " + this.elementClass.getName(), ex);
311                        }
312                        catch (InvocationTargetException ex) {
313                                throw new ElementInstantiationException(
314                                                "Failed to invoke element constructor: " + this.elementClass.getName(), ex.getTargetException());
315                        }
316                }
317        }
318
319}