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 */
017package org.apache.xbean.recipe;
018
019import java.lang.reflect.Type;
020import java.util.ArrayList;
021import java.util.Collection;
022import java.util.Collections;
023import java.util.Dictionary;
024import java.util.EnumSet;
025import java.util.LinkedHashSet;
026import java.util.List;
027import java.util.Map;
028import java.util.Set;
029import java.util.SortedSet;
030import java.util.TreeSet;
031
032/**
033 * @version $Rev: 6685 $ $Date: 2005-12-28T00:29:37.967210Z $
034 */
035public 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}