/*
* Rapid Beans Framework: PropertyMap.java
*
* Copyright (C) 2009 Martin Bluemel
*
* Creation Date: 12/13/2007
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by the Free Software Foundation;
* either version 3 of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
* You should have received a copies of the GNU Lesser General Public License and the
* GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
package org.rapidbeans.core.basic;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.rapidbeans.core.common.ReadonlyListCollection;
import org.rapidbeans.core.exception.RapidBeansRuntimeException;
import org.rapidbeans.core.type.TypeProperty;
import org.rapidbeans.core.type.TypePropertyCollection;
import org.rapidbeans.core.type.TypePropertyMap;
/**
* A <b>Collection</b> bean property encapsulates a collection of beans.<br/>
* Map is similar to collection. You can easily define associations between
* beans that then can be accessed via a key.
*
* @author Martin Bluemel
*/
public class PropertyMap extends PropertyAssociationend {
/**
* the map of chosen value. !!! do not initialize here as this would
* overwrite the super classes default constructor's behavior.
*/
private Map<String, Link> value;
/**
* constructor for a new Collection Property.
*
* @param type
* the Property's type
* @param parentBean
* the parent bean
*/
public PropertyMap(final TypeProperty type, final RapidBean parentBean) {
super(type, parentBean);
if (this.value == null && this.getType().getMandatory()) {
this.value = createNewMap();
}
}
/**
* just an empty object array.
*/
private static final Object[] MAP_CONSTR_ARGS = new Object[0];
/**
* internal creation of new collections.
*
* @return the collection
*/
@SuppressWarnings("unchecked")
public Map<String, Link> createNewMap() {
final Class<?> mapClass = ((TypePropertyMap) this.getType()).getMapClass();
// construct standard collection
if (mapClass == HashMap.class) {
return new HashMap<String, Link>();
} else if (mapClass == LinkedHashMap.class) {
return new LinkedHashMap<String, Link>();
}
// use reflective creation for special collection classes
Map<String, Link> newMap = null;
try {
newMap = (Map<String, Link>) ((TypePropertyMap) this.getType()).getMapClassConstructor().newInstance(
MAP_CONSTR_ARGS);
} catch (IllegalAccessException e) {
throw new RapidBeansRuntimeException(e.getClass().getName() + ": " + e.getMessage());
} catch (InstantiationException e) {
throw new RapidBeansRuntimeException(e.getClass().getName() + ": " + e.getMessage());
} catch (InvocationTargetException e) {
throw new RapidBeansRuntimeException(e.getClass().getName() + ": " + e.getMessage());
}
return newMap;
}
/**
* generic value getter. since collections are n o t immutable and we don't
* want to clone a collection that could be very large we just give out the
* iterator.
*
* @return a collection of beans
*/
public Map<String, Link> getValue() {
if (this.value == null) {
return null;
} else {
return this.value;
}
}
/**
* generic value getter. since collections are n o t immutable and we don't
* want to clone a collection that could be very large we just give out the
* iterator.
*
* @return a collection of beans
*/
public ReadonlyListCollection<Link> getValueCollection() {
if (this.value == null) {
return null;
} else {
return (ReadonlyListCollection<Link>) new ReadonlyListCollection<Link>(this.value.values(),
(TypePropertyCollection) this.getType());
}
}
/**
* generic value setter.
*
* @param newValue
* the new value for this property
*/
public void setValue(final Object newValue) {
this.setValue(newValue, true, true, true);
}
/**
* generic value setter absolute experts with fine tuning possibilities.
*
* @param col
* the new value for this property
* @param validate
* switch that allows turning off validation Caution: handle with
* very much care.
* @param touchInverseLinks
* if an inverse link will be added or not
* @param checkContainerLinksToExternalObjects
* determines if links from an object living inside the container
* should be allowed. Be very careful to set this argument to
* false.
*/
public void setValue(final Object col, final boolean validate, final boolean touchInverseLinks,
final boolean checkContainerLinksToExternalObjects) {
final Map<String, Link> newValue = convertValue(col);
if (validate) {
validate(newValue);
}
super.setValueWithEvents(this.getValue(), newValue, new PropertyValueSetter() {
@SuppressWarnings("unchecked")
public void setValue(Object newValue) {
value = (Map<String, Link>) newValue;
}
});
// if (this.value != null) {
// for (Link curLink : this.value.values()) {
// if (touchInverseLinks && curLink instanceof RapidBean) {
// removeInverseLink((RapidBean) curLink, true);
// }
// }
// }
// Collection oldCol = null;
// if (this.value != null) {
// oldCol = this.value.values();
// }
// this.value = newCol;
// if (this.value != null) {
// for (Link curLink : this.value.values()) {
// if (touchInverseLinks && curLink instanceof RapidBean) {
// addInverseLink((RapidBean) curLink, true,
// checkContainerLinksToExternalObjects);
// }
// }
// }
//
// if ((oldCol == null && newCol != null)
// || (oldCol != null && newCol == null)
// || ((oldCol != null && newCol != null)
// && (!oldCol.equals(newCol)))) {
// fireChanged(this, PropertyChangeEventType.set,
// oldCol, newCol, null);
// }
}
/**
* converts a variety of types to the neccessary map.
*
* @param collectionValue
* the value to convert
*
* @return a map of bean links
*/
@SuppressWarnings("unchecked")
public Map<String, Link> convertValue(final Object newValue) {
Map<String, Link> newMapValue = null;
if (newValue != null) {
// if (collectionValue instanceof Collection) {
// final Map argValList = (Collection) collectionValue;
// collection = createNewCollection();
// for (Object obj : argValList) {
// if (obj == null) {
// throw new
// ValidationException("invalid.prop.collection.contentnull",
// "invalid null object");
// } else if (obj instanceof RapidBean || obj instanceof LinkFrozen)
// {
// collection.add((Link) obj);
// } else if (obj instanceof String) {
// collection.add(new LinkFrozen((String) obj));
// } else {
// throw new
// ValidationException("invalid.prop.collection.contenttype",
// "Collection property \"" + this.getType().getPropName() + "\": "
// + " invalid content data type " + obj.getClass().getName()
// +
// ".\nOnly \"RapidBean\", \"LinkFrozen\", and \"String\" are valid types.");
// }
// }
if (newValue instanceof Map) {
newMapValue = (Map<String, Link>) newValue;
} else if (newValue instanceof LinkWithKey) {
newMapValue = createNewMap();
newMapValue.put(((LinkWithKey) newValue).getKey(), ((LinkWithKey) newValue).getLink());
} else if (newValue instanceof String) {
newMapValue = createNewMap();
final String newValueString = (String) newValue;
if (newValueString.length() > 0) {
throw new RuntimeException("not yet implemented");
}
// final String[] frozenLinkDescriptions =
// UtilsString.tokenize((String) collectionValue, ",");
// for (int i = 0; i < frozenLinkDescriptions.length; i++) {
// collection.add(new LinkFrozen(frozenLinkDescriptions[i]));
// }
}
// } else if (collectionValue instanceof String[]) {
// collection = createNewCollection();
// String[] sa = (String[]) collectionValue;
// for (int i = 0; i < sa.length; i++) {
// collection.add(new LinkFrozen(sa[i]));
// }
// } else {
// throw new ValidationException("invalid.prop.collection.type",
// "Collection property \"" + this.getType().getPropName() + "\": "
// + " invalid data type " + collectionValue.getClass().getName()
// + ".\nOnly \"RapidBean\", \"Collection<RapidBean>\","
// + " \"String[]\", and \"String\" are valid types.");
// }
}
return newMapValue;
}
public void putLink(final String key, final Link link) {
if (this.value == null) {
validate(null);
this.value = new HashMap<String, Link>();
}
this.value.put(key, link);
}
// /**
// * adds the inverse link.
// *
// * @param linkedBean the bean linked
// * @param addInverse the switch to prevent from endless recursion
// * @param checkContainerLinksToExternalObjects
// * determines if links from an object living inside the
// * container should be allowed.
// * Be very careful to set this argument to false.
// */
// private void addInverseLink(final RapidBean linkedBean, final boolean
// addInverse,
// final boolean checkContainerLinksToExternalObjects) {
// TypePropertyCollection colType = (TypePropertyCollection) this.getType();
// RapidBean bean = this.getBean();
// ContainerImpl container = null;
// if (bean != null) {
// container = (ContainerImpl) bean.getContainer();
// }
// Container linkedContainer = linkedBean.getContainer();
// if (colType.isComposition()) {
// if (linkedBean.getParentBean() != null
// && linkedBean.getParentBean() != this.getBean()) {
// throw new RapidBeansRuntimeException("tried to add a bean as component"
// + " that already is component of another bean (has a parent)");
// }
// linkedBean.setParentBean(this.getBean());
// if (container == null) {
// if (linkedContainer != null) {
// throw new RapidBeansRuntimeException("tried to compose a bean"
// + " living outside a container"
// + " with one inside a container");
// }
// } else {
// if (linkedContainer == null) {
// // insert a component implicitely into the container
// linkedBean.setContainer(container);
// container.insert(linkedBean, true);
// } else if (container != linkedContainer) {
// if (container == null) {
// throw new
// RapidBeansRuntimeException("tried to associate a bean living outside a container"
// + " with one inside a container");
// } else {
// throw new
// RapidBeansRuntimeException("tried to compose a bean living outside a container"
// + " with one inside a different container");
// }
// }
// }
// } else if (colType.getInverse() != null && addInverse) {
// PropertyMap inverseColProp = (PropertyMap)
// linkedBean.getProperty(colType.getInverse());
// if (inverseColProp != null) {
// // if we have a 1:1 association and the bean to link (linked bean) is
// // already linked to another instance refuse linkage
// if (colType.getMaxmult() == 1) {
// final TypePropertyCollection inverseColPropType =
// (TypePropertyCollection) inverseColProp.getType();
// final Collection<RapidBean> inverseColPropValue = (Collection<RapidBean>)
// inverseColProp.getValue();
// if (inverseColPropType.getMaxmult() == 1
// && inverseColPropValue != null
// && inverseColPropValue.size() == 1
// && inverseColPropValue.iterator().next() instanceof RapidBean) {
// final Object[] oa = {
// this.getBean().getType() + ": " + this.getBean().getIdString(),
// inverseColPropValue.iterator().next().getType().getName() + ": "
// + inverseColPropValue.iterator().next().getIdString()
// };
// throw new
// ValidationException("invalid.prop.collection.add.target.alreadey.added",
// "Bean \"" + this.getBean().getType().getName() + "::"
// + this.getBean().getIdString() + "\":"
// + "Collection property \"" + this.getType().getPropName() + "\": "
// + " failed to add bean "
// + inverseColPropValue.iterator().next().getIdString()
// + ".\n1:1 associated bean already linked.", oa);
// }
// }
// // inverseColProp.addLink(this.getBean(), false,
// checkContainerLinksToExternalObjects, true);
// if (checkContainerLinksToExternalObjects) {
// if (container == null) {
// if (linkedContainer != null) {
// throw new RapidBeansRuntimeException("tried to associate"
// + " a bean living outside a container"
// + " with one inside a container");
// }
// } else {
// if (linkedContainer == null) {
// throw new RapidBeansRuntimeException("tried to associate a"
// + " bean living inside a container"
// + " with one outside a container");
// } else if (container != linkedContainer) {
// throw new RapidBeansRuntimeException("tried to associate a"
// + " bean living outside a container"
// + " with one inside a different container");
// }
// }
// }
// }
// }
// }
//
// /**
// * remove a bean reference from the Collection Property.
// *
// * @param key the bean reference to remove
// * @param removeInverse if the inverse link has to be removed
// * @param throwNotFound throw an exception if the link to remove is not in
// * @param validate validate
// */
// public void removeLink(final Object key, final boolean removeInverse,
// final boolean throwNotFound, final boolean validate) {
// if (!removeInverse && (this.value == null || this.value.size() == 0)) {
// return;
// }
// if (this.value != null && validate && this.value.size()
// <= ((TypePropertyCollection) this.getType()).getMinmult()
// && (this.getBean().getBeanState() != RapidBeanState.deleting)) {
// throw new ValidationException("invalid.prop.collection.remove.minmult",
// "Collection property \"" + this.getType().getPropName() + "\": "
// + " failed to remove bean with key from map property " + key
// + ".\nMinimal multiplicity "
// + ((TypePropertyCollection) this.getType()).getMinmult()
// + " undergone.");
// }
// final Link link = (RapidBean) this.value.get(key);
// if (validate) {
// this.validate(link);
// }
// if (link == null) {
// if (throwNotFound) {
// throw new BeanNotFoundException(
// "Collection property \"" + this.getType().getPropName() + "\": "
// + " failed to remove null bean " + key.toString()
// + ".\nBean is not in this collection.");
// }
// }
// this.value.remove(key);
// if (link instanceof RapidBean) {
// try {
// removeInverseLink((RapidBean) link, removeInverse);
// } catch (BeanNotFoundException e) {
// return;
// }
// }
// fireChanged(this, PropertyChangeEventType.removelink,
// null, null, link);
// }
//
// /**
// * adds the inverse link.
// *
// * @param linkedBean the bean linked
// * @param removeInverse the switch to prevent from endless recursion
// */
// private void removeInverseLink(final RapidBean linkedBean, final boolean
// removeInverse) {
// TypePropertyCollection colType =
// (TypePropertyCollection) this.getType();
// if (colType.isComposition()) {
// linkedBean.setParentBean(null);
// if (linkedBean.getContainer() != null) {
// linkedBean.getContainer().delete(linkedBean);
// linkedBean.setContainer(null);
// }
// } else if (colType.getInverse() != null && removeInverse) {
// PropertyMap inverseColProp = (PropertyMap)
// linkedBean.getProperty(colType.getInverse());
// if (inverseColProp != null) {
// inverseColProp.removeLink(this.getBean(), false, false, false);
// }
// }
// }
//
// /**
// * initialize default value.
// *
// * @param propType the Property type
// * @param parentBean the parent bean
// */
// protected void initDefaultValue(final TypeProperty propType,
// final RapidBean parentBean) {
// try {
// this.setValue(propType.getDefaultValue());
// } catch (ValidationMandatoryException e) {
// this.value = createNewMap();
// }
// }
//
/**
* implementation of toString().
*
* @return the String representation of this collection
*/
public String toString() {
if (this.value == null) {
return null;
}
StringBuffer sb = new StringBuffer();
int i = 0;
for (Link link : this.value.values()) {
if (i > 0) {
sb.append(',');
}
sb.append(link.getIdString());
i++;
}
return sb.toString();
}
// /**
// * validation for Choice Properties.
// *
// * @param newCollection the new value
// *
// * @return the converted value which is the internal representation or if
// a
// * primitive type the corresponding value object
// */
// public Map<Object, Link> validateMap(final Object newCollection) {
// final Map<Object, Link> map = (Map<Object, Link>)
// super.validate(newCollection);
// if (newCollection == null) {
// return map;
// }
// for (Link link : map.values()) {
// if (link instanceof RapidBean) {
// this.validate((RapidBean) link);
// }
// }
// return map;
// }
//
// /**
// * validate a single bean.
// *
// * @param bean the bean to validate
// *
// * @return the converted value which is the internal representation or if
// a
// * primitive type the corresponding value object
// */
// protected Map<String, Link> validateMap(final RapidBean bean) {
// TypePropertyCollection collectionType = (TypePropertyCollection)
// this.getType();
// if (bean != null) {
// if (!TypeRapidBean.isSameOrSubtype(collectionType.getTargetType(),
// bean.getType())) {
// throw new ValidationException("invalid.prop.collection.targettype",
// "Collection property \"" + this.getType().getPropName() + "\": "
// + " invalid bean type \"" + bean.getType().getName()
// + "\".\nType \"" + collectionType.getTargetType().getName()
// + "\" is required.");
// }
// }
// Map<String, Link> map = createNewMap();
// // map.add(bean);
// return map;
// }
// /**
// * Helper function.
// *
// * @param idstr the id string to search for
// *
// * @return if the collection already contains a link with the given
// idstring.
// */
// private boolean containsFrozenLinkWithIdstring(final String idstr) {
// boolean contains = false;
// for (Link link : this.value.values()) {
// if (link instanceof LinkFrozen
// && link.getIdString().equals(idstr)) {
// contains = true;
// break;
// }
// }
// return contains;
// }
public void removeLink(final String key) {
throw new RuntimeException("not yet implemented");
}
}