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