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.Dictionary; 024 import java.util.EnumSet; 025 import java.util.LinkedHashSet; 026 import java.util.List; 027 import java.util.Map; 028 import java.util.Set; 029 import java.util.SortedSet; 030 import java.util.TreeSet; 031 032 /** 033 * @version $Rev: 6685 $ $Date: 2005-12-28T00:29:37.967210Z $ 034 */ 035 public 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 }