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