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 }