001 /**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements. See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License. You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.apache.xbean.recipe;
018
019 import java.lang.reflect.Type;
020 import java.util.ArrayList;
021 import java.util.Collection;
022 import java.util.Collections;
023 import java.util.EnumSet;
024 import java.util.LinkedHashSet;
025 import java.util.List;
026 import java.util.Set;
027 import java.util.SortedSet;
028 import java.util.TreeSet;
029
030 /**
031 * @version $Rev: 6685 $ $Date: 2005-12-28T00:29:37.967210Z $
032 */
033 public class CollectionRecipe extends AbstractRecipe {
034 private final List<Object> list;
035 private String typeName;
036 private Class typeClass;
037 private final EnumSet<Option> options = EnumSet.noneOf(Option.class);
038
039 public CollectionRecipe() {
040 list = new ArrayList<Object>();
041 }
042
043 public CollectionRecipe(String type) {
044 list = new ArrayList<Object>();
045 this.typeName = type;
046 }
047
048 public CollectionRecipe(Class type) {
049 if (type == null) throw new NullPointerException("type is null");
050 if (!RecipeHelper.hasDefaultConstructor(type)) throw new IllegalArgumentException("Collection type does not have a default constructor " + type);
051
052 this.list = new ArrayList<Object>();
053 this.typeClass = type;
054 }
055
056 public CollectionRecipe(Collection<?> collection) {
057 if (collection == null) throw new NullPointerException("collection is null");
058
059 this.list = new ArrayList<Object>(collection);
060
061 // If the specified collection has a default constructor we will recreate the collection, otherwise we use a the default
062 if (RecipeHelper.hasDefaultConstructor(collection.getClass())) {
063 this.typeClass = collection.getClass();
064 } else if (collection instanceof SortedSet) {
065 this.typeClass = SortedSet.class;
066 } else if (collection instanceof Set) {
067 this.typeClass = Set.class;
068 } else if (collection instanceof List) {
069 this.typeClass = List.class;
070 } else {
071 this.typeClass = Collection.class;
072 }
073 }
074
075 public CollectionRecipe(CollectionRecipe collectionRecipe) {
076 if (collectionRecipe == null) throw new NullPointerException("setRecipe is null");
077 this.typeName = collectionRecipe.typeName;
078 this.typeClass = collectionRecipe.typeClass;
079 list = new ArrayList<Object>(collectionRecipe.list);
080 }
081
082 public void allow(Option option) {
083 options.add(option);
084 }
085
086 public void disallow(Option option) {
087 options.remove(option);
088 }
089
090 public List<Recipe> getNestedRecipes() {
091 List<Recipe> nestedRecipes = new ArrayList<Recipe>(list.size());
092 for (Object o : list) {
093 if (o instanceof Recipe) {
094 Recipe recipe = (Recipe) o;
095 nestedRecipes.add(recipe);
096 }
097 }
098 return nestedRecipes;
099 }
100
101 public List<Recipe> getConstructorRecipes() {
102 if (!options.contains(Option.LAZY_ASSIGNMENT)) {
103 return getNestedRecipes();
104 }
105 return Collections.emptyList();
106 }
107
108 public boolean canCreate(Type expectedType) {
109 Class myType = getType(expectedType);
110 return RecipeHelper.isAssignable(expectedType, myType);
111 }
112
113 protected Object internalCreate(Type expectedType, boolean lazyRefAllowed) throws ConstructionException {
114 Class type = getType(expectedType);
115
116 if (!RecipeHelper.hasDefaultConstructor(type)) {
117 throw new ConstructionException("Type does not have a default constructor " + type.getName());
118 }
119
120 // create collection instance
121 Object o;
122 try {
123 o = type.newInstance();
124 } catch (Exception e) {
125 throw new ConstructionException("Error while creating collection instance: " + type.getName());
126 }
127 if (!(o instanceof Collection)) {
128 throw new ConstructionException("Specified collection type does not implement the Collection interface: " + type.getName());
129 }
130 Collection instance = (Collection) o;
131
132 // add to execution context if name is specified
133 if (getName() != null) {
134 ExecutionContext.getContext().addObject(getName(), instance);
135 }
136
137 // get component type
138 Type[] typeParameters = RecipeHelper.getTypeParameters(Collection.class, expectedType);
139 Type componentType = Object.class;
140 if (typeParameters != null && typeParameters.length == 1 && typeParameters[0] instanceof Class) {
141 componentType = typeParameters[0];
142 }
143
144 boolean refAllowed = options.contains(Option.LAZY_ASSIGNMENT);
145
146 int index = 0;
147 for (Object value : list) {
148 value = RecipeHelper.convert(componentType, value, refAllowed);
149
150 if (value instanceof Reference) {
151 Reference reference = (Reference) value;
152 if (instance instanceof List) {
153 // add a null place holder in the list that will be updated later
154 //noinspection unchecked
155 instance.add(null);
156 reference.setAction(new UpdateList((List) instance, index));
157 } else {
158 reference.setAction(new UpdateCollection(instance));
159 }
160 } else {
161 //noinspection unchecked
162 instance.add(value);
163 }
164 index++;
165 }
166 return instance;
167 }
168
169 private Class getType(Type expectedType) {
170 Class expectedClass = RecipeHelper.toClass(expectedType);
171 Class type = expectedClass;
172 if (typeClass != null || typeName != null) {
173 type = typeClass;
174 if (type == null) {
175 try {
176 type = RecipeHelper.loadClass(typeName);
177 } catch (ClassNotFoundException e) {
178 throw new ConstructionException("Type class could not be found: " + typeName);
179 }
180 }
181
182 // if expectedType is a subclass of the assigned type,
183 // we use it assuming it has a default constructor
184 if (type.isAssignableFrom(expectedClass) && RecipeHelper.hasDefaultConstructor(expectedClass)) {
185 type = expectedClass;
186 }
187 }
188
189 // no type explicitly set
190 if (RecipeHelper.hasDefaultConstructor(type)) {
191 return expectedClass;
192 } else if (expectedClass.isAssignableFrom(SortedSet.class)) {
193 return TreeSet.class;
194 } else if (expectedClass.isAssignableFrom(Set.class)) {
195 return LinkedHashSet.class;
196 } else if (expectedClass.isAssignableFrom(List.class)) {
197 return ArrayList.class;
198 } else {
199 return ArrayList.class;
200 }
201 }
202
203 public void add(Object value) {
204 list.add(value);
205 }
206
207 public void addAll(Collection<?> value) {
208 list.addAll(value);
209 }
210
211 public void remove(Object value) {
212 list.remove(value);
213 }
214
215 public void removeAll(Object value) {
216 list.remove(value);
217 }
218
219 public List<Object> getAll() {
220 return Collections.unmodifiableList(list);
221 }
222
223 private static class UpdateCollection implements Reference.Action {
224 private final Collection collection;
225
226 public UpdateCollection(Collection collection) {
227 this.collection = collection;
228 }
229
230 @SuppressWarnings({"unchecked"})
231 public void onSet(Reference ref) {
232 collection.add(ref.get());
233 }
234 }
235
236 private static class UpdateList implements Reference.Action {
237 private final List list;
238 private final int index;
239
240 public UpdateList(List list, int index) {
241 this.list = list;
242 this.index = index;
243 }
244
245 @SuppressWarnings({"unchecked"})
246 public void onSet(Reference ref) {
247 list.set(index, ref.get());
248 }
249 }
250 }