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