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