View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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   * @version $Rev: 6687 $ $Date: 2005-12-28T21:08:56.733437Z $
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          // If the specified set has a default constructor we will recreate the set, otherwise we use a LinkedHashMap or TreeMap
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         // 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 }