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.LinkedHashMap; 025 import java.util.List; 026 import java.util.Map; 027 import java.util.SortedMap; 028 import java.util.TreeMap; 029 import java.util.concurrent.ConcurrentHashMap; 030 import java.util.concurrent.ConcurrentMap; 031 032 /** 033 * @version $Rev: 6687 $ $Date: 2005-12-28T21:08:56.733437Z $ 034 */ 035 public class MapRecipe extends AbstractRecipe { 036 private final List<Object[]> entries; 037 private String typeName; 038 private Class typeClass; 039 private final EnumSet<Option> options = EnumSet.noneOf(Option.class); 040 041 public MapRecipe() { 042 entries = new ArrayList<Object[]>(); 043 } 044 045 public MapRecipe(String type) { 046 this.typeName = type; 047 entries = new ArrayList<Object[]>(); 048 } 049 050 public MapRecipe(Class type) { 051 this.typeClass = type; 052 if (!RecipeHelper.hasDefaultConstructor(type)) throw new IllegalArgumentException("Type does not have a default constructor " + type); 053 entries = new ArrayList<Object[]>(); 054 } 055 056 public MapRecipe(Map<?,?> map) { 057 if (map == null) throw new NullPointerException("map is null"); 058 059 entries = new ArrayList<Object[]>(map.size()); 060 061 // If the specified set has a default constructor we will recreate the set, otherwise we use a LinkedHashMap or TreeMap 062 if (RecipeHelper.hasDefaultConstructor(map.getClass())) { 063 this.typeClass = map.getClass(); 064 } else if (map instanceof SortedMap) { 065 this.typeClass = TreeMap.class; 066 } else if (map instanceof ConcurrentMap) { 067 this.typeClass = ConcurrentHashMap.class; 068 } else { 069 this.typeClass = LinkedHashMap.class; 070 } 071 putAll(map); 072 } 073 074 public MapRecipe(MapRecipe mapRecipe) { 075 if (mapRecipe == null) throw new NullPointerException("mapRecipe is null"); 076 this.typeName = mapRecipe.typeName; 077 this.typeClass = mapRecipe.typeClass; 078 entries = new ArrayList<Object[]>(mapRecipe.entries); 079 } 080 081 public void allow(Option option){ 082 options.add(option); 083 } 084 085 public void disallow(Option option){ 086 options.remove(option); 087 } 088 089 public List<Recipe> getNestedRecipes() { 090 List<Recipe> nestedRecipes = new ArrayList<Recipe>(entries.size() * 2); 091 for (Object[] entry : entries) { 092 Object key = entry[0]; 093 if (key instanceof Recipe) { 094 Recipe recipe = (Recipe) key; 095 nestedRecipes.add(recipe); 096 } 097 098 Object value = entry[1]; 099 if (value instanceof Recipe) { 100 Recipe recipe = (Recipe) value; 101 nestedRecipes.add(recipe); 102 } 103 } 104 return nestedRecipes; 105 } 106 107 public List<Recipe> getConstructorRecipes() { 108 if (!options.contains(Option.LAZY_ASSIGNMENT)) { 109 return getNestedRecipes(); 110 } 111 return Collections.emptyList(); 112 } 113 114 public boolean canCreate(Type type) { 115 Class myType = getType(type); 116 return RecipeHelper.isAssignable(type, myType); 117 } 118 119 protected Object internalCreate(Type expectedType, boolean lazyRefAllowed) throws ConstructionException { 120 Class mapType = getType(expectedType); 121 122 if (!RecipeHelper.hasDefaultConstructor(mapType)) { 123 throw new ConstructionException("Type does not have a default constructor " + mapType.getName()); 124 } 125 126 Object o; 127 try { 128 o = mapType.newInstance(); 129 } catch (Exception e) { 130 throw new ConstructionException("Error while creating set instance: " + mapType.getName()); 131 } 132 133 if(!(o instanceof Map)) { 134 throw new ConstructionException("Specified map type does not implement the Map interface: " + mapType.getName()); 135 } 136 Map instance = (Map) o; 137 138 // get component type 139 Type keyType = Object.class; 140 Type valueType = Object.class; 141 Type[] typeParameters = RecipeHelper.getTypeParameters(Collection.class, expectedType); 142 if (typeParameters != null && typeParameters.length == 2) { 143 if (typeParameters[0] instanceof Class) { 144 keyType = typeParameters[0]; 145 } 146 if (typeParameters[1] instanceof Class) { 147 valueType = typeParameters[1]; 148 } 149 } 150 151 // add to execution context if name is specified 152 if (getName() != null) { 153 ExecutionContext.getContext().addObject(getName(), instance); 154 } 155 156 // add map entries 157 boolean refAllowed = options.contains(Option.LAZY_ASSIGNMENT); 158 for (Object[] entry : entries) { 159 Object key = RecipeHelper.convert(keyType, entry[0], refAllowed); 160 Object value = RecipeHelper.convert(valueType, entry[1], refAllowed); 161 162 if (key instanceof Reference) { 163 // when the key reference and optional value reference are both resolved 164 // the key/value pair will be added to the map 165 Reference.Action action = new UpdateMap(instance, key, value); 166 ((Reference) key).setAction(action); 167 if (value instanceof Reference) { 168 ((Reference) value).setAction(action); 169 } 170 } else if (value instanceof Reference) { 171 // add a null place holder assigned to the key 172 //noinspection unchecked 173 instance.put(key, null); 174 // when value is resolved we will replace the null value with they real value 175 Reference.Action action = new UpdateValue(instance, key); 176 ((Reference) value).setAction(action); 177 } else { 178 //noinspection unchecked 179 instance.put(key, value); 180 } 181 } 182 return instance; 183 } 184 185 private Class getType(Type expectedType) { 186 Class expectedClass = RecipeHelper.toClass(expectedType); 187 if (typeClass != null || typeName != null) { 188 Class type = typeClass; 189 if (type == null) { 190 try { 191 type = RecipeHelper.loadClass(typeName); 192 } catch (ClassNotFoundException e) { 193 throw new ConstructionException("Type class could not be found: " + typeName); 194 } 195 } 196 197 // if expectedType is a subclass of the assigned type, 198 // we use it assuming it has a default constructor 199 if (type.isAssignableFrom(expectedClass) && RecipeHelper.hasDefaultConstructor(expectedClass)) { 200 return expectedClass; 201 } 202 } 203 204 // no type explicitly set 205 if (RecipeHelper.hasDefaultConstructor(expectedClass)) { 206 return expectedClass; 207 } else if (expectedClass.isAssignableFrom(SortedMap.class)) { 208 return TreeMap.class; 209 } else if (expectedClass.isAssignableFrom(ConcurrentMap.class)) { 210 return ConcurrentHashMap.class; 211 } else { 212 return LinkedHashMap.class; 213 } 214 } 215 216 217 public void put(Object key, Object value) { 218 if (key == null) throw new NullPointerException("key is null"); 219 entries.add(new Object[] { key, value}); 220 } 221 222 public void putAll(Map<?,?> map) { 223 if (map == null) throw new NullPointerException("map is null"); 224 for (Map.Entry<?,?> entry : map.entrySet()) { 225 Object key = entry.getKey(); 226 Object value = entry.getValue(); 227 put(key, value); 228 } 229 } 230 231 private static class UpdateValue implements Reference.Action { 232 private final Map map; 233 private final Object key; 234 235 public UpdateValue(Map map, Object key) { 236 this.map = map; 237 this.key = key; 238 } 239 240 @SuppressWarnings({"unchecked"}) 241 public void onSet(Reference ref) { 242 map.put(key, ref.get()); 243 } 244 } 245 246 247 private static class UpdateMap implements Reference.Action { 248 private final Map map; 249 private final Object key; 250 private final Object value; 251 252 public UpdateMap(Map map, Object key, Object value) { 253 this.map = map; 254 this.key = key; 255 this.value = value; 256 } 257 258 @SuppressWarnings({"unchecked"}) 259 public void onSet(Reference ignored) { 260 Object key = this.key; 261 if (key instanceof Reference) { 262 Reference reference = (Reference) key; 263 if (!reference.isResolved()) { 264 return; 265 } 266 key = reference.get(); 267 } 268 Object value = this.value; 269 if (value instanceof Reference) { 270 Reference reference = (Reference) value; 271 if (!reference.isResolved()) { 272 return; 273 } 274 value = reference.get(); 275 } 276 map.put(key, value); 277 } 278 } 279 280 }