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.Array;
020import java.lang.reflect.Type;
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.EnumSet;
025import java.util.List;
026
027/**
028 * @version $Rev: 766039 $ $Date: 2009-04-17 16:55:47 +0200 (Fri, 17 Apr 2009) $
029 */
030public class ArrayRecipe extends AbstractRecipe {
031    private final List<Object> list;
032    private String typeName;
033    private Class typeClass;
034    private final EnumSet<Option> options = EnumSet.noneOf(Option.class);
035
036    public ArrayRecipe() {
037        list = new ArrayList<Object>();
038    }
039
040    public ArrayRecipe(String type) {
041        list = new ArrayList<Object>();
042        this.typeName = type;
043    }
044
045    public ArrayRecipe(Class type) {
046        if (type == null) throw new NullPointerException("type is null");
047
048        this.list = new ArrayList<Object>();
049        this.typeClass = type;
050    }
051
052    public ArrayRecipe(ArrayRecipe collectionRecipe) {
053        if (collectionRecipe == null) throw new NullPointerException("setRecipe is null");
054        this.typeName = collectionRecipe.typeName;
055        this.typeClass = collectionRecipe.typeClass;
056        list = new ArrayList<Object>(collectionRecipe.list);
057    }
058
059    public void allow(Option option) {
060        options.add(option);
061    }
062
063    public void disallow(Option option) {
064        options.remove(option);
065    }
066
067    public List<Recipe> getNestedRecipes() {
068        List<Recipe> nestedRecipes = new ArrayList<Recipe>(list.size());
069        for (Object o : list) {
070            if (o instanceof Recipe) {
071                Recipe recipe = (Recipe) o;
072                nestedRecipes.add(recipe);
073            }
074        }
075        return nestedRecipes;
076    }
077
078    public List<Recipe> getConstructorRecipes() {
079        if (!options.contains(Option.LAZY_ASSIGNMENT)) {
080            return getNestedRecipes();
081        }
082        return Collections.emptyList();
083    }
084
085    public boolean canCreate(Type expectedType) {
086        Class expectedClass = RecipeHelper.toClass(expectedType);
087        Class myType = getType(expectedType);
088        return expectedClass.isArray() && RecipeHelper.isAssignable(expectedClass.getComponentType(), myType);
089    }
090
091    protected Object internalCreate(Type expectedType, boolean lazyRefAllowed) throws ConstructionException {
092        Class type = getType(expectedType);
093
094        // create array instance
095        Object array;
096        try {
097            array = Array.newInstance(type, list.size());
098        } catch (Exception e) {
099            throw new ConstructionException("Error while creating array instance: " + type.getName());
100        }
101
102        // add to execution context if name is specified
103        if (getName() != null) {
104            ExecutionContext.getContext().addObject(getName(), array);
105        }
106
107        boolean refAllowed = options.contains(Option.LAZY_ASSIGNMENT);
108
109        int index = 0;
110        for (Object value : list) {
111            value = RecipeHelper.convert(type, value, refAllowed);
112            
113            if (value instanceof Reference) {
114                Reference reference = (Reference) value;
115                reference.setAction(new UpdateArray(array, index));
116            } else {
117                //noinspection unchecked
118                Array.set(array, index, value);
119            }
120            index++;
121        }
122        
123        return array;
124    }
125
126    private Class getType(Type expectedType) {       
127        Class expectedClass = RecipeHelper.toClass(expectedType);
128        if (expectedClass.isArray()) {
129            expectedClass = expectedClass.getComponentType();
130        }
131        Class type = expectedClass;
132        if (typeClass != null || typeName != null) {
133            type = typeClass;
134            if (type == null) {
135                try {
136                    type = RecipeHelper.loadClass(typeName);
137                } catch (ClassNotFoundException e) {
138                    throw new ConstructionException("Type class could not be found: " + typeName);
139                }
140            }
141            
142            // in case where expectedType is a subclass of the assigned type
143            if (type.isAssignableFrom(expectedClass)) {
144                type = expectedClass;
145            }
146        }
147
148        return type;
149    }
150
151    public void add(Object value) {
152        list.add(value);
153    }
154
155    public void addAll(Collection<?> value) {
156        list.addAll(value);
157    }
158
159    public void remove(Object value) {
160        list.remove(value);
161    }
162
163    public void removeAll(Object value) {
164        list.remove(value);
165    }
166
167    public List<Object> getAll() {
168        return Collections.unmodifiableList(list);
169    }
170
171    private static class UpdateArray implements Reference.Action {
172        private final Object array;
173        private final int index;
174
175        public UpdateArray(Object array, int index) {
176            this.array = array;
177            this.index = index;
178        }
179
180        @SuppressWarnings({"unchecked"})
181        public void onSet(Reference ref) {
182            Array.set(array, index, ref.get());
183        }
184    }
185}