1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.xbean.recipe;
18
19 import java.lang.reflect.Type;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Collections;
23 import java.util.EnumSet;
24 import java.util.LinkedHashMap;
25 import java.util.List;
26 import java.util.Map;
27 import java.util.SortedMap;
28 import java.util.TreeMap;
29 import java.util.Dictionary;
30 import java.util.AbstractMap;
31 import java.util.Set;
32 import java.util.concurrent.ConcurrentHashMap;
33 import java.util.concurrent.ConcurrentMap;
34
35
36
37
38 public class MapRecipe extends AbstractRecipe {
39 private final List<Object[]> entries;
40 private String typeName;
41 private Class typeClass;
42 private final EnumSet<Option> options = EnumSet.noneOf(Option.class);
43
44 public MapRecipe() {
45 entries = new ArrayList<Object[]>();
46 }
47
48 public MapRecipe(String type) {
49 this.typeName = type;
50 entries = new ArrayList<Object[]>();
51 }
52
53 public MapRecipe(Class type) {
54 if (type == null) throw new NullPointerException("type is null");
55 this.typeClass = type;
56 entries = new ArrayList<Object[]>();
57 }
58
59 public MapRecipe(Map<?,?> map) {
60 if (map == null) throw new NullPointerException("map is null");
61
62 entries = new ArrayList<Object[]>(map.size());
63
64
65 if (RecipeHelper.hasDefaultConstructor(map.getClass())) {
66 this.typeClass = map.getClass();
67 } else if (map instanceof SortedMap) {
68 this.typeClass = TreeMap.class;
69 } else if (map instanceof ConcurrentMap) {
70 this.typeClass = ConcurrentHashMap.class;
71 } else {
72 this.typeClass = LinkedHashMap.class;
73 }
74 putAll(map);
75 }
76
77 public MapRecipe(MapRecipe mapRecipe) {
78 if (mapRecipe == null) throw new NullPointerException("mapRecipe is null");
79 this.typeName = mapRecipe.typeName;
80 this.typeClass = mapRecipe.typeClass;
81 entries = new ArrayList<Object[]>(mapRecipe.entries);
82 }
83
84 public void allow(Option option){
85 options.add(option);
86 }
87
88 public void disallow(Option option){
89 options.remove(option);
90 }
91
92 public List<Recipe> getNestedRecipes() {
93 List<Recipe> nestedRecipes = new ArrayList<Recipe>(entries.size() * 2);
94 for (Object[] entry : entries) {
95 Object key = entry[0];
96 if (key instanceof Recipe) {
97 Recipe recipe = (Recipe) key;
98 nestedRecipes.add(recipe);
99 }
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
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
159 if (getName() != null) {
160 ExecutionContext.getContext().addObject(getName(), instance);
161 }
162
163
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
171
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
179
180 instance.put(key, null);
181
182 Reference.Action action = new UpdateValue(instance, key);
183 ((Reference) value).setAction(action);
184 } else {
185
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
205
206 if (type.isAssignableFrom(expectedClass)) {
207 return getMap(expectedClass);
208 } else {
209 return getMap(type);
210 }
211 }
212
213
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 }