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 }