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
032import org.apache.xbean.propertyeditor.PropertyEditorRegistry;
033
034/**
035 * @version $Rev: 6685 $ $Date: 2005-12-28T00:29:37.967210Z $
036 */
037public class CollectionRecipe extends AbstractRecipe {
038    private final List<Object> list;
039    private String typeName;
040    private Class typeClass;
041    private PropertyEditorRegistry registry;
042    private final EnumSet<Option> options = EnumSet.noneOf(Option.class);
043
044    public CollectionRecipe() {
045        list = new ArrayList<Object>();
046    }
047
048    public CollectionRecipe(String type) {
049        list = new ArrayList<Object>();
050        this.typeName = type;
051    }
052
053    public CollectionRecipe(Class type) {
054        if (type == null) throw new NullPointerException("type is null");
055        this.list = new ArrayList<Object>();
056        this.typeClass = type;
057    }
058
059    public CollectionRecipe(Collection<?> collection) {
060        if (collection == null) throw new NullPointerException("collection is null");
061
062        this.list = new ArrayList<Object>(collection);
063
064        // If the specified collection has a default constructor we will recreate the collection, otherwise we use a the default
065        if (RecipeHelper.hasDefaultConstructor(collection.getClass())) {
066            this.typeClass = collection.getClass();
067        } else if (collection instanceof SortedSet) {
068            this.typeClass = SortedSet.class;
069        } else if (collection instanceof Set) {
070            this.typeClass = Set.class;
071        } else if (collection instanceof List) {
072            this.typeClass = List.class;
073        } else {
074            this.typeClass = Collection.class;
075        }
076    }
077
078    public CollectionRecipe(CollectionRecipe collectionRecipe) {
079        if (collectionRecipe == null) throw new NullPointerException("setRecipe is null");
080        this.typeName = collectionRecipe.typeName;
081        this.typeClass = collectionRecipe.typeClass;
082        list = new ArrayList<Object>(collectionRecipe.list);
083    }
084
085    public void setRegistry(final PropertyEditorRegistry registry) {
086        this.registry = registry;
087    }
088
089    public void allow(Option option) {
090        options.add(option);
091    }
092
093    public void disallow(Option option) {
094        options.remove(option);
095    }
096
097    public List<Recipe> getNestedRecipes() {
098        List<Recipe> nestedRecipes = new ArrayList<Recipe>(list.size());
099        for (Object o : list) {
100            if (o instanceof Recipe) {
101                Recipe recipe = (Recipe) o;
102                nestedRecipes.add(recipe);
103            }
104        }
105        return nestedRecipes;
106    }
107
108    public List<Recipe> getConstructorRecipes() {
109        if (!options.contains(Option.LAZY_ASSIGNMENT)) {
110            return getNestedRecipes();
111        }
112        return Collections.emptyList();
113    }
114
115    public boolean canCreate(Type expectedType) {
116        Class myType = getType(expectedType);
117        return RecipeHelper.isAssignable(expectedType, myType);
118    }
119
120    protected Object internalCreate(Type expectedType, boolean lazyRefAllowed) throws ConstructionException {
121        Class type = getType(expectedType);
122
123        if (!RecipeHelper.hasDefaultConstructor(type)) {
124            throw new ConstructionException("Type does not have a default constructor " + type.getName());
125        }
126
127        // create collection instance
128        Object o;
129        try {
130            o = type.newInstance();
131        } catch (Exception e) {
132            throw new ConstructionException("Error while creating collection instance: " + type.getName());
133        }
134        if (!(o instanceof Collection)) {
135            throw new ConstructionException("Specified collection type does not implement the Collection interface: " + type.getName());
136        }
137        Collection instance = (Collection) o;
138
139        // add to execution context if name is specified
140        if (getName() != null) {
141            ExecutionContext.getContext().addObject(getName(), instance);
142        }
143
144        // get component type
145        Type[] typeParameters = RecipeHelper.getTypeParameters(Collection.class, expectedType);
146        Type componentType = Object.class;
147        if (typeParameters != null && typeParameters.length == 1 && typeParameters[0] instanceof Class) {
148            componentType = typeParameters[0];
149        }
150
151        boolean refAllowed = options.contains(Option.LAZY_ASSIGNMENT);
152
153        int index = 0;
154        for (Object value : list) {
155            value = RecipeHelper.convert(componentType, value, refAllowed, registry);
156
157            if (value instanceof Reference) {
158                Reference reference = (Reference) value;
159                if (instance instanceof List) {
160                    // add a null place holder in the list that will be updated later
161                    //noinspection unchecked
162                    instance.add(null);
163                    reference.setAction(new UpdateList((List) instance, index));
164                } else {
165                    reference.setAction(new UpdateCollection(instance));
166                }
167            } else {
168                //noinspection unchecked
169                instance.add(value);
170            }
171            index++;
172        }
173        return instance;
174    }
175
176    private Class getType(Type expectedType) {
177        Class expectedClass = RecipeHelper.toClass(expectedType);
178        if (typeClass != null || typeName != null) {
179            Class type = typeClass;
180            if (type == null) {
181                try {
182                    type = RecipeHelper.loadClass(typeName);
183                } catch (ClassNotFoundException e) {
184                    throw new ConstructionException("Type class could not be found: " + typeName);
185                }
186            }
187
188            // if expectedType is a subclass of the assigned type,
189            // we use it assuming it has a default constructor
190            if (type.isAssignableFrom(expectedClass)) {
191                return getCollection(expectedClass);                
192            } else {
193                return getCollection(type);
194            }
195        }
196        
197        // no type explicitly set
198        return getCollection(expectedClass);
199    }
200
201    private Class getCollection(Class type) {
202        if (RecipeHelper.hasDefaultConstructor(type)) {
203            return type;
204        } else if (SortedSet.class.isAssignableFrom(type)) {
205            return TreeSet.class;
206        } else if (Set.class.isAssignableFrom(type)) {
207            return LinkedHashSet.class;
208        } else if (List.class.isAssignableFrom(type)) {
209            return ArrayList.class;
210        } else {
211            return ArrayList.class;
212        }
213    }
214    
215    public void add(Object value) {
216        list.add(value);
217    }
218
219    public void addAll(Collection<?> value) {
220        list.addAll(value);
221    }
222
223    public void remove(Object value) {
224        list.remove(value);
225    }
226
227    public void removeAll(Object value) {
228        list.remove(value);
229    }
230
231    public List<Object> getAll() {
232        return Collections.unmodifiableList(list);
233    }
234
235    private static class UpdateCollection implements Reference.Action {
236        private final Collection collection;
237
238        public UpdateCollection(Collection collection) {
239            this.collection = collection;
240        }
241
242        @SuppressWarnings({"unchecked"})
243        public void onSet(Reference ref) {
244            collection.add(ref.get());
245        }
246    }
247
248    private static class UpdateList implements Reference.Action {
249        private final List list;
250        private final int index;
251
252        public UpdateList(List list, int index) {
253            this.list = list;
254            this.index = index;
255        }
256
257        @SuppressWarnings({"unchecked"})
258        public void onSet(Reference ref) {
259            list.set(index, ref.get());
260        }
261    }
262}