blob: 874ecc3e957dcbd7e175a23b012cf2d64c878ee6 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.xbean.recipe;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Dictionary;
import java.util.AbstractMap;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.xbean.propertyeditor.PropertyEditorRegistry;
/**
* @version $Rev: 6687 $ $Date: 2005-12-28T21:08:56.733437Z $
*/
public class MapRecipe extends AbstractRecipe {
private final List<Object[]> entries;
private String typeName;
private Class typeClass;
private PropertyEditorRegistry registry;
private final EnumSet<Option> options = EnumSet.noneOf(Option.class);
public MapRecipe() {
entries = new ArrayList<Object[]>();
}
public MapRecipe(String type) {
this.typeName = type;
entries = new ArrayList<Object[]>();
}
public MapRecipe(Class type) {
if (type == null) throw new NullPointerException("type is null");
this.typeClass = type;
entries = new ArrayList<Object[]>();
}
public MapRecipe(Map<?,?> map) {
if (map == null) throw new NullPointerException("map is null");
entries = new ArrayList<Object[]>(map.size());
// If the specified set has a default constructor we will recreate the set, otherwise we use a LinkedHashMap or TreeMap
if (RecipeHelper.hasDefaultConstructor(map.getClass())) {
this.typeClass = map.getClass();
} else if (map instanceof SortedMap) {
this.typeClass = TreeMap.class;
} else if (map instanceof ConcurrentMap) {
this.typeClass = ConcurrentHashMap.class;
} else {
this.typeClass = LinkedHashMap.class;
}
putAll(map);
}
public MapRecipe(MapRecipe mapRecipe) {
if (mapRecipe == null) throw new NullPointerException("mapRecipe is null");
this.typeName = mapRecipe.typeName;
this.typeClass = mapRecipe.typeClass;
entries = new ArrayList<Object[]>(mapRecipe.entries);
}
public void setRegistry(final PropertyEditorRegistry registry) {
this.registry = registry;
}
public void allow(Option option){
options.add(option);
}
public void disallow(Option option){
options.remove(option);
}
public List<Recipe> getNestedRecipes() {
List<Recipe> nestedRecipes = new ArrayList<Recipe>(entries.size() * 2);
for (Object[] entry : entries) {
Object key = entry[0];
if (key instanceof Recipe) {
Recipe recipe = (Recipe) key;
nestedRecipes.add(recipe);
}
Object value = entry[1];
if (value instanceof Recipe) {
Recipe recipe = (Recipe) value;
nestedRecipes.add(recipe);
}
}
return nestedRecipes;
}
public List<Recipe> getConstructorRecipes() {
if (!options.contains(Option.LAZY_ASSIGNMENT)) {
return getNestedRecipes();
}
return Collections.emptyList();
}
public boolean canCreate(Type type) {
Class myType = getType(type);
return RecipeHelper.isAssignable(type, myType);
}
protected Object internalCreate(Type expectedType, boolean lazyRefAllowed) throws ConstructionException {
Class mapType = getType(expectedType);
if (!RecipeHelper.hasDefaultConstructor(mapType)) {
throw new ConstructionException("Type does not have a default constructor " + mapType.getName());
}
Object o;
try {
o = mapType.newInstance();
} catch (Exception e) {
throw new ConstructionException("Error while creating set instance: " + mapType.getName());
}
Map instance;
if (o instanceof Map) {
instance = (Map) o;
} else if (o instanceof Dictionary) {
instance = new DummyDictionaryAsMap((Dictionary) o);
} else {
throw new ConstructionException("Specified map type does not implement the Map interface: " + mapType.getName());
}
// get component type
Type keyType = Object.class;
Type valueType = Object.class;
Type[] typeParameters = RecipeHelper.getTypeParameters(Map.class, expectedType);
if (typeParameters != null && typeParameters.length == 2) {
if (typeParameters[0] instanceof Class) {
keyType = typeParameters[0];
}
if (typeParameters[1] instanceof Class) {
valueType = typeParameters[1];
}
}
// add to execution context if name is specified
if (getName() != null) {
ExecutionContext.getContext().addObject(getName(), instance);
}
// add map entries
boolean refAllowed = options.contains(Option.LAZY_ASSIGNMENT);
for (Object[] entry : entries) {
Object key = RecipeHelper.convert(keyType, entry[0], refAllowed, registry);
Object value = RecipeHelper.convert(valueType, entry[1], refAllowed, registry);
if (key instanceof Reference) {
// when the key reference and optional value reference are both resolved
// the key/value pair will be added to the map
Reference.Action action = new UpdateMap(instance, key, value);
((Reference) key).setAction(action);
if (value instanceof Reference) {
((Reference) value).setAction(action);
}
} else if (value instanceof Reference) {
// add a null place holder assigned to the key
//noinspection unchecked
instance.put(key, null);
// when value is resolved we will replace the null value with they real value
Reference.Action action = new UpdateValue(instance, key);
((Reference) value).setAction(action);
} else {
//noinspection unchecked
instance.put(key, value);
}
}
return instance;
}
private Class getType(Type expectedType) {
Class expectedClass = RecipeHelper.toClass(expectedType);
if (typeClass != null || typeName != null) {
Class type = typeClass;
if (type == null) {
try {
type = RecipeHelper.loadClass(typeName);
} catch (ClassNotFoundException e) {
throw new ConstructionException("Type class could not be found: " + typeName);
}
}
// if expectedType is a subclass of the assigned type,
// we use it assuming it has a default constructor
if (type.isAssignableFrom(expectedClass)) {
return getMap(expectedClass);
} else {
return getMap(type);
}
}
// no type explicitly set
return getMap(expectedClass);
}
private Class getMap(Class type) {
if (RecipeHelper.hasDefaultConstructor(type)) {
return type;
} else if (SortedMap.class.isAssignableFrom(type)) {
return TreeMap.class;
} else if (ConcurrentMap.class.isAssignableFrom(type)) {
return ConcurrentHashMap.class;
} else {
return LinkedHashMap.class;
}
}
public void put(Object key, Object value) {
if (key == null) throw new NullPointerException("key is null");
entries.add(new Object[] { key, value});
}
public void putAll(Map<?,?> map) {
if (map == null) throw new NullPointerException("map is null");
for (Map.Entry<?,?> entry : map.entrySet()) {
Object key = entry.getKey();
Object value = entry.getValue();
put(key, value);
}
}
private static class UpdateValue implements Reference.Action {
private final Map map;
private final Object key;
public UpdateValue(Map map, Object key) {
this.map = map;
this.key = key;
}
@SuppressWarnings({"unchecked"})
public void onSet(Reference ref) {
map.put(key, ref.get());
}
}
private static class UpdateMap implements Reference.Action {
private final Map map;
private final Object key;
private final Object value;
public UpdateMap(Map map, Object key, Object value) {
this.map = map;
this.key = key;
this.value = value;
}
@SuppressWarnings({"unchecked"})
public void onSet(Reference ignored) {
Object key = this.key;
if (key instanceof Reference) {
Reference reference = (Reference) key;
if (!reference.isResolved()) {
return;
}
key = reference.get();
}
Object value = this.value;
if (value instanceof Reference) {
Reference reference = (Reference) value;
if (!reference.isResolved()) {
return;
}
value = reference.get();
}
map.put(key, value);
}
}
public static class DummyDictionaryAsMap extends AbstractMap {
private final Dictionary dictionary;
public DummyDictionaryAsMap(Dictionary dictionary) {
this.dictionary = dictionary;
}
@Override
public Object put(Object key, Object value) {
return dictionary.put(key, value);
}
public Set entrySet() {
throw new UnsupportedOperationException();
}
}
}