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 }