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