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.Dictionary;
024    import java.util.EnumSet;
025    import java.util.LinkedHashSet;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.Set;
029    import java.util.SortedSet;
030    import java.util.TreeSet;
031    
032    /**
033     * @version $Rev: 6685 $ $Date: 2005-12-28T00:29:37.967210Z $
034     */
035    public class CollectionRecipe extends AbstractRecipe {
036        private final List<Object> list;
037        private String typeName;
038        private Class typeClass;
039        private final EnumSet<Option> options = EnumSet.noneOf(Option.class);
040    
041        public CollectionRecipe() {
042            list = new ArrayList<Object>();
043        }
044    
045        public CollectionRecipe(String type) {
046            list = new ArrayList<Object>();
047            this.typeName = type;
048        }
049    
050        public CollectionRecipe(Class type) {
051            if (type == null) throw new NullPointerException("type is null");
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            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    }