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    }