View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.xbean.recipe;
18  
19  import java.lang.reflect.Type;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.Collections;
23  import java.util.Dictionary;
24  import java.util.EnumSet;
25  import java.util.LinkedHashSet;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.Set;
29  import java.util.SortedSet;
30  import java.util.TreeSet;
31  
32  /**
33   * @version $Rev: 6685 $ $Date: 2005-12-28T00:29:37.967210Z $
34   */
35  public class CollectionRecipe extends AbstractRecipe {
36      private final List<Object> list;
37      private String typeName;
38      private Class typeClass;
39      private final EnumSet<Option> options = EnumSet.noneOf(Option.class);
40  
41      public CollectionRecipe() {
42          list = new ArrayList<Object>();
43      }
44  
45      public CollectionRecipe(String type) {
46          list = new ArrayList<Object>();
47          this.typeName = type;
48      }
49  
50      public CollectionRecipe(Class type) {
51          if (type == null) throw new NullPointerException("type is null");
52          this.list = new ArrayList<Object>();
53          this.typeClass = type;
54      }
55  
56      public CollectionRecipe(Collection<?> collection) {
57          if (collection == null) throw new NullPointerException("collection is null");
58  
59          this.list = new ArrayList<Object>(collection);
60  
61          // If the specified collection has a default constructor we will recreate the collection, otherwise we use a the default
62          if (RecipeHelper.hasDefaultConstructor(collection.getClass())) {
63              this.typeClass = collection.getClass();
64          } else if (collection instanceof SortedSet) {
65              this.typeClass = SortedSet.class;
66          } else if (collection instanceof Set) {
67              this.typeClass = Set.class;
68          } else if (collection instanceof List) {
69              this.typeClass = List.class;
70          } else {
71              this.typeClass = Collection.class;
72          }
73      }
74  
75      public CollectionRecipe(CollectionRecipe collectionRecipe) {
76          if (collectionRecipe == null) throw new NullPointerException("setRecipe is null");
77          this.typeName = collectionRecipe.typeName;
78          this.typeClass = collectionRecipe.typeClass;
79          list = new ArrayList<Object>(collectionRecipe.list);
80      }
81  
82      public void allow(Option option) {
83          options.add(option);
84      }
85  
86      public void disallow(Option option) {
87          options.remove(option);
88      }
89  
90      public List<Recipe> getNestedRecipes() {
91          List<Recipe> nestedRecipes = new ArrayList<Recipe>(list.size());
92          for (Object o : list) {
93              if (o instanceof Recipe) {
94                  Recipe recipe = (Recipe) o;
95                  nestedRecipes.add(recipe);
96              }
97          }
98          return nestedRecipes;
99      }
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         if (typeClass != null || typeName != null) {
172             Class type = typeClass;
173             if (type == null) {
174                 try {
175                     type = RecipeHelper.loadClass(typeName);
176                 } catch (ClassNotFoundException e) {
177                     throw new ConstructionException("Type class could not be found: " + typeName);
178                 }
179             }
180 
181             // if expectedType is a subclass of the assigned type,
182             // we use it assuming it has a default constructor
183             if (type.isAssignableFrom(expectedClass)) {
184                 return getCollection(expectedClass);                
185             } else {
186                 return getCollection(type);
187             }
188         }
189         
190         // no type explicitly set
191         return getCollection(expectedClass);
192     }
193 
194     private Class getCollection(Class type) {
195         if (RecipeHelper.hasDefaultConstructor(type)) {
196             return type;
197         } else if (SortedSet.class.isAssignableFrom(type)) {
198             return TreeSet.class;
199         } else if (Set.class.isAssignableFrom(type)) {
200             return LinkedHashSet.class;
201         } else if (List.class.isAssignableFrom(type)) {
202             return ArrayList.class;
203         } else {
204             return ArrayList.class;
205         }
206     }
207     
208     public void add(Object value) {
209         list.add(value);
210     }
211 
212     public void addAll(Collection<?> value) {
213         list.addAll(value);
214     }
215 
216     public void remove(Object value) {
217         list.remove(value);
218     }
219 
220     public void removeAll(Object value) {
221         list.remove(value);
222     }
223 
224     public List<Object> getAll() {
225         return Collections.unmodifiableList(list);
226     }
227 
228     private static class UpdateCollection implements Reference.Action {
229         private final Collection collection;
230 
231         public UpdateCollection(Collection collection) {
232             this.collection = collection;
233         }
234 
235         @SuppressWarnings({"unchecked"})
236         public void onSet(Reference ref) {
237             collection.add(ref.get());
238         }
239     }
240 
241     private static class UpdateList implements Reference.Action {
242         private final List list;
243         private final int index;
244 
245         public UpdateList(List list, int index) {
246             this.list = list;
247             this.index = index;
248         }
249 
250         @SuppressWarnings({"unchecked"})
251         public void onSet(Reference ref) {
252             list.set(index, ref.get());
253         }
254     }
255 }