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