/* * Rapid Beans Framework: PropertyCollection.java * * Copyright (C) 2009 Martin Bluemel * * Creation Date: 11/27/2005 * * 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.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.TreeSet; import java.util.Vector; import org.rapidbeans.core.common.RapidBeansLocale; import org.rapidbeans.core.common.ReadonlyListCollection; import org.rapidbeans.core.event.PropertyChangeEvent; import org.rapidbeans.core.event.PropertyChangeEventType; import org.rapidbeans.core.event.PropertyChangeListener; import org.rapidbeans.core.exception.BeanDuplicateException; import org.rapidbeans.core.exception.BeanNotFoundException; import org.rapidbeans.core.exception.RapidBeansRuntimeException; import org.rapidbeans.core.exception.UnresolvedLinkException; import org.rapidbeans.core.exception.ValidationException; import org.rapidbeans.core.exception.ValidationInstanceAssocTwiceException; import org.rapidbeans.core.exception.ValidationMandatoryException; import org.rapidbeans.core.type.TypeProperty; import org.rapidbeans.core.type.TypePropertyCollection; import org.rapidbeans.core.type.TypeRapidBean; import org.rapidbeans.core.util.StringHelper; import org.rapidbeans.presentation.Application; import org.rapidbeans.presentation.ApplicationManager; /** * A <b>Collection</b> bean property encapsulates a set of bean links belonging * to a certain associaton. * * @author Martin Bluemel */ public class PropertyCollection extends PropertyAssociationend implements PropertyChangeListener { /** * the collection of beans. !!! do not initialize here as this would * overwrite the superclasses default constructor's behaviour. */ private Collection<Link> 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 */ @SuppressWarnings("unchecked") public Collection<?> getValue() { Collection<Link> val = null; if (getBean() instanceof RapidBeanImplSimple) { // use getValueField here because the collection will be wrapped later on anyway final Object valobj = Property.getValueFieldByReflection(getBean(), getName()); if (valobj != null) { if (valobj instanceof Link) { val = Arrays.asList(new Link[] { (Link) valobj }); } else { val = (Collection<Link>) valobj; } } } else { val = this.value; } if (val == null) { return null; } else { return new ReadonlyListCollection<Link>(val, (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 newValue * the new value for this property * @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 newValue, final boolean touchInverseLinks, final boolean checkContainerLinksToExternalObjects) { this.setValue(newValue, touchInverseLinks, checkContainerLinksToExternalObjects, true); } /** * generic value setter absolute experts with fine tuning possibilities. * * @param col * the new value for this property * @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. * @param validate * turn validation off / on */ @SuppressWarnings("unchecked") public void setValue(final Object col, final boolean touchInverseLinks, final boolean checkContainerLinksToExternalObjects, final boolean validate) { Collection<Link> newCol = null; if (validate) { newCol = validate(col, ValidationMode.set); } else { newCol = convertValue(col); } final TypePropertyCollection proptype = (TypePropertyCollection) this.getType(); final TypeProperty[] propsbefore = BeanSorter.get(); prepareSorting(proptype); try { // remove all inverse links existing so far if (getValue() != null) { for (final Link curLink : (Collection<Link>) getValue()) { if (touchInverseLinks && curLink instanceof RapidBean) { removeInverseLink((RapidBean) curLink, true); } } } // Especially for x to 1 associations: // remove also inverse links pointing to the object // that is going to be linked if (touchInverseLinks && newCol != null && newCol.size() > 0 && proptype.getInverse() != null) { final TypePropertyCollection inverseProptype = (TypePropertyCollection) proptype.getTargetType() .getPropertyType(proptype.getInverse()); if (inverseProptype == null) { throw new RapidBeansRuntimeException("could not find inverse property type: \"" + proptype.getTargetType().getName() + "\", property: \"" + proptype.getInverse() + "\""); } if (inverseProptype.getMaxmult() != TypePropertyCollection.INFINITE) { for (final Link newLink : newCol) { if (newLink instanceof RapidBean) { final RapidBean beanToLink = (RapidBean) newLink; final Collection<?> alreadyLinkedBeans = (Collection<?>) beanToLink .getPropValue(inverseProptype.getPropName()); if (alreadyLinkedBeans != null && alreadyLinkedBeans.size() >= inverseProptype.getMaxmult()) { // if // (alreadyLinkedBeans.contains(this.getBean())) // { // final PropertyCollection inverseColProp = // (PropertyCollection) // beanToLink.getProperty(inverseProptype.getPropName()); // inverseColProp.removeLink(this.getBean()); // } else { // throw new ValidationException("xxx", "yyy"); // } switch (alreadyLinkedBeans.size()) { case 0: // do nothing break; case 1: final PropertyCollection colProp = (PropertyCollection) beanToLink .getProperty(inverseProptype.getPropName()); colProp.setValue(new ArrayList<RapidBean>(), true, true, false); break; default: throw new RapidBeansRuntimeException("did not expect bean \"" + beanToLink.getType() + "::" + beanToLink.getIdString() + "\" to be already linked with more than one bean (" + "property \"" + inverseProptype.getPropName() + "\")."); } } } } } } final Collection<Link> oldCol = (Collection<Link>) getValue(); if ((oldCol == null && newCol != null) || (oldCol != null && newCol == null) || ((oldCol != null && newCol != null) && (!oldCol.equals(newCol)))) { fireChangePre(this, PropertyChangeEventType.set, oldCol, newCol, null); } if (getBean() instanceof RapidBeanImplSimple) { if (proptype.getMaxmult() == 1) { if (newCol != null && newCol.iterator() != null) { Property.setValueByReflection(getBean(), getName(), newCol.iterator().next()); } else { Property.setValueByReflection(getBean(), getName(), null); } } else { Property.setValueByReflection(getBean(), getName(), newCol); } } else { this.value = newCol; } if ((oldCol == null && newCol != null) || (oldCol != null && newCol == null) || ((oldCol != null && newCol != null) && (!oldCol.equals(newCol)))) { fireChanged(this, PropertyChangeEventType.set, oldCol, newCol, null); } if (getValue() != null) { for (final Link curLink : (Collection<Link>) getValue()) { if (touchInverseLinks && curLink instanceof RapidBean) { addInverseLink((RapidBean) curLink, true, checkContainerLinksToExternalObjects, true); } } } removePropertyChangeListeners(oldCol); } finally { cleanupSorting(proptype, propsbefore); } } /** * add a bean reference to the Collection Property. * * @param link * the bean to add */ public void addLink(final Link link) { this.addLink(link, true, true, true); } /** * add a bean reference to the Collection Property. * * @param link * the bean to add * @param addInverse * if an inverse link should be added * @param checkContainerLinksToExternalObjects * determines if links from an object living inside the container * should be allowed. Be very careful to set this argument to * false. * @param checkContainerAlreadyContains * determines if the bean already is contained by the container */ @SuppressWarnings("unchecked") public void addLink(final Link link, final boolean addInverse, final boolean checkContainerLinksToExternalObjects, final boolean checkContainerAlreadyContains) { // Validation for add link checks null on mandatory, target type, ... // The check on the multiplicity with one single instance always // succeeds // but will be done separately for addLink afterwards. validate(link, ValidationMode.add); ContainerImpl doc = null; boolean parentSet = false; RapidBean prevParent = null; final TypePropertyCollection proptype = (TypePropertyCollection) this.getType(); final TypeProperty[] propsbefore = BeanSorter.get(); prepareSorting(proptype); try { if (link instanceof RapidBean && proptype.isComposition()) { doc = (ContainerImpl) this.getBean().getContainer(); if (doc != null && checkContainerAlreadyContains) { final TypeRapidBean linkType = proptype.getTargetType(); if ((linkType.getIdtype() == IdType.keyprops || linkType.getIdtype() == IdType.keypropswithparentscope) && link instanceof RapidBean && ((RapidBean) link).getParentBean() != this.getBean()) { prevParent = ((RapidBean) link).getParentBean(); ((RapidBean) link).setParentBean(this.getBean()); parentSet = true; } if (this.getBean().getContainer().contains((RapidBean) link)) { if (parentSet) { ((RapidBean) link).setParentBean(prevParent); parentSet = false; } final Application app = ApplicationManager.getApplication(); String message; RapidBeansLocale locale = null; if (app != null) { locale = app.getCurrentLocale(); } if (locale != null) { final String locGuiId = ((RapidBean) link).toStringGuiId(locale); message = "Bean \"" + locGuiId + "\" already exists in document \"" + getBean().getContainer().getName() + "\""; throw new BeanDuplicateException("messagedialog.create.duplicate", this.getBean(), message, new Object[] { locGuiId }); } else { final String sid = ((RapidBean) link).getIdString(); message = "Bean \"" + sid + "\" already exists in document \"" + getBean().getContainer().getName() + "\""; throw new BeanDuplicateException("messagedialog.create.duplicate", this.getBean(), message, new Object[] { sid }); } } } } if (getValue() == null) { if (getBean() instanceof RapidBeanImplSimple) { if (proptype.getMaxmult() != 1) { Property.setValueByReflection(getBean(), getName(), createNewCollection()); } } else { this.value = createNewCollection(); } } else { final int maxmult = proptype.getMaxmult(); if (maxmult != TypePropertyCollection.INFINITE && getValue().size() > maxmult - 1) { // special handling for inverse frozen links // do not throw an exception if value contains a frozen // link that equals the id string of the new link which // is a real bean. final Object[] oa = { ((RapidBean) link).getType().getName() + ": " + ((RapidBean) link).toString(), proptype.getMaxmult() }; if (ThreadLocalValidationSettings.getValidation()) { if (!(link instanceof RapidBean) || (!containsFrozenLinkWithIdstring(link.getIdString()))) { throw new ValidationException("invalid.prop.collection.add.maxmult", link, "Bean \"" + this.getBean().getType().getName() + "::" + this.getBean().getIdString() + "\":" + "Collection property \"" + this.getType().getPropName() + "\": " + " failed to add bean " + link.getIdString() + ".\nMaximal multiplicity " + proptype.getMaxmult() + " exceeded.", oa); } } } } fireChangePre(this, PropertyChangeEventType.addlink, null, null, link); boolean addSuccess = false; if (getBean() instanceof RapidBeanImplSimple) { Object valobj = Property.getValueFieldByReflection(getBean(), getName()); if (valobj == null) { valobj = createNewCollection(); Property.setValueByReflection(getBean(), getName(), createNewCollection()); } else if (valobj instanceof Link) { throw new AssertionError("no collection value"); } addSuccess = ((Collection<Link>) valobj).add(link); } else { addSuccess = this.value.add(link); } if (!addSuccess) { throw new ValidationInstanceAssocTwiceException("invalid.prop.collection.add.already", this, link, "Collection property \"" + this.getType().getPropName() + "\": " + " failed to add bean " + link.getIdString() + ".\nBean already is in this collection which does not permit duplicates.", new String[] { this.getType().getPropName(), link.getIdString() }); } if (addInverse && link instanceof RapidBean) { try { addInverseLink((RapidBean) link, addInverse, checkContainerLinksToExternalObjects, false); if ((link instanceof RapidBean) && proptype.isComposition()) { if (doc != null) { insertComponentsIntoContainer((RapidBean) link, doc); resolveFrozenLinksInComponents((RapidBean) link, doc); } } } catch (BeanDuplicateException e) { if (getBean() instanceof RapidBeanImplSimple) { // use getValueField here because the collection must be modifiable final Object valobj = Property.getValueFieldByReflection(getBean(), getName()); if (valobj == null) { throw new AssertionError("null value"); } if (valobj instanceof Link) { throw new AssertionError("no collection value"); } ((Collection<Link>) valobj).remove(link); } else { this.value.remove(link); } throw e; } } fireChanged(this, PropertyChangeEventType.addlink, null, null, link); if (link instanceof RapidBean && (proptype.getSorting() == SortingType.byPropertyValues || proptype.getSorting() == SortingType.byPropertyValuesAll)) { ((RapidBean) link).addPropertyChangeListener(this); } } finally { cleanupSorting(proptype, propsbefore); } } /** * 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. * @param setValue * true if called from setValue false if called from addValue */ private void addInverseLink(final RapidBean linkedBean, final boolean addInverse, final boolean checkContainerLinksToExternalObjects, final boolean setValue) { 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 implicitly into the container container.insert(linkedBean, true); linkedBean.setContainer(container); } else if (container != linkedContainer) { throw new RapidBeansRuntimeException("tried to compose a bean living outside a container" + " with one inside a different container"); } } } else if (colType.getInverse() != null && addInverse) { PropertyCollection inverseColProp = (PropertyCollection) linkedBean.getProperty(colType.getInverse()); if (inverseColProp != null) { if (setValue) { final TypePropertyCollection inverseColPropType = (TypePropertyCollection) inverseColProp.getType(); if (inverseColPropType.getMaxmult() == 1) { inverseColProp.setValue(this.getBean(), false, false); } else { inverseColProp.addLink(this.getBean(), false, checkContainerLinksToExternalObjects, true); } } else { 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 link * the bean reference to remove */ public void removeLink(final Link link) { this.removeLink(link, true, true, true); } /** * remove a bean reference from the Collection Property. * * @param link * the bean reference to remove * @param removeInverse * if the inverse link has to be removed * @param checkNotFound * throw an exception if the link to remove is not in this * property * @param deleteOrpahnedComponent * unbind orphaned component from all other links. */ @SuppressWarnings("unchecked") public void removeLink(final Link link, final boolean removeInverse, final boolean checkNotFound, final boolean deleteOrpahnedComponent) { if (!removeInverse && (getValue() == null || getValue().size() == 0)) { return; } final TypePropertyCollection proptype = (TypePropertyCollection) this.getType(); final TypeProperty[] propsbefore = BeanSorter.get(); prepareSorting(proptype); try { validate(link, ValidationMode.remove); if (ThreadLocalValidationSettings.getValidation()) { if (getValue() != null && getValue().size() <= proptype.getMinmult() && (this.getBean().getBeanState() != RapidBeanState.deleting)) { throw new ValidationException("invalid.prop.collection.remove.minmult", this.getBean(), "Collection property \"" + this.getType().getPropName() + "\": " + " failed to remove bean " + link.getIdString() + ".\nMinimal multiplicity " + proptype.getMinmult() + " undergone."); } } fireChangePre(this, PropertyChangeEventType.removelink, null, null, link); if (getBean() instanceof RapidBeanImplSimple) { if (getValue() != null) { // use getValueField here because the collection must be modifiable final Object valobj = Property.getValueFieldByReflection(getBean(), getName()); if (valobj == null) { throw new AssertionError("null value"); } if (valobj instanceof Link) { throw new AssertionError("no collection value"); } final boolean removeSuccess = ((Collection<Link>) valobj).remove(link); if (!removeSuccess) { throw new BeanNotFoundException("Collection property \"" + this.getType().getPropName() + "\": " + " failed to remove bean " + link.getIdString() + ".\nBean is not in this collection." + "Collction class: " + getValue().getClass().getName()); } } } else { if (this.value != null && !this.value.remove(link)) { if (checkNotFound) { throw new BeanNotFoundException("Collection property \"" + this.getType().getPropName() + "\": " + " failed to remove bean " + link.getIdString() + ".\nBean is not in this collection." + "Collction class: " + getValue().getClass().getName()); } } } if (removeInverse && link instanceof RapidBean) { try { removeInverseLink((RapidBean) link, removeInverse); } catch (BeanNotFoundException e) { return; } } if (deleteOrpahnedComponent && (link instanceof RapidBean) && proptype.isComposition()) { final RapidBean bean = (RapidBean) link; final ContainerImpl doc = (ContainerImpl) this.getBean().getContainer(); if (doc != null) { bean.delete(); } } if (link instanceof RapidBean && (proptype.getSorting() == SortingType.byPropertyValues || proptype.getSorting() == SortingType.byPropertyValuesAll)) { ((RapidBean) link).removePropertyChangeListener(this); } fireChanged(this, PropertyChangeEventType.removelink, null, null, link); } finally { cleanupSorting(proptype, propsbefore); } } /** * 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) { final 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) { PropertyCollection inverseColProp = (PropertyCollection) linkedBean.getProperty(colType.getInverse()); if (inverseColProp != null) { try { ThreadLocalValidationSettings.validationOff(); inverseColProp.removeLink(this.getBean(), false, false, false); } finally { ThreadLocalValidationSettings.remove(); } } } } /** * add the document to the bean and all child beans. * * @param bean * the bean * @param doc * the container */ private void insertComponentsIntoContainer(final RapidBean bean, final ContainerImpl doc) { if (bean.getContainer() == null) { doc.insert(bean, true); } else if (bean.getContainer() != doc) { throw new RapidBeansRuntimeException("wrong container"); } for (PropertyCollection compColProp : bean.getColPropertiesComposition()) { if (compColProp.value != null) { for (Link childBeanLink : compColProp.value) { if (childBeanLink instanceof RapidBean) { insertComponentsIntoContainer((RapidBean) childBeanLink, doc); } } } } } /** * add the document to the bean and all childs beans. * * @param bean * the bean * @param doc * the container */ private void resolveFrozenLinksInComponents(final RapidBean bean, final Container doc) { final List<PropertyCollection> colProps = bean.getColProperties(); final int size = colProps.size(); for (int i = 0; i < size; i++) { final PropertyCollection colProp = colProps.get(i); final TypePropertyCollection colPropType = (TypePropertyCollection) colProp.getType(); if (colProp.value != null) { if (colPropType.isComposition()) { for (Link childBeanLink : colProp.value) { if (childBeanLink instanceof RapidBean) { resolveFrozenLinksInComponents((RapidBean) childBeanLink, doc); } } } else { boolean resolvedAnyFrozen = false; final Collection<Link> col = colProp.createNewCollection(); for (final Link childBeanLink : colProp.value) { if (childBeanLink instanceof LinkFrozen) { final RapidBean linkedBean = doc.findBean(colPropType.getTargetType().getName(), childBeanLink.getIdString()); if (linkedBean == null) { throw new UnresolvedLinkException("from " + getBean().getType().getName() + "::" + getBean().toString() + " to " + colPropType.getTargetType().getName() + "::" + childBeanLink.getIdString()); } col.add(linkedBean); resolvedAnyFrozen = true; } else { col.add(childBeanLink); } } if (resolvedAnyFrozen) { try { ThreadLocalValidationSettings.validationOff(); colProp.setValue(col); } finally { ThreadLocalValidationSettings.remove(); } } } } } } /** * constructor for a new Collection Property. * * @param type * the Property's type * @param parentBean * the parent bean */ public PropertyCollection(final TypeProperty type, final RapidBean parentBean) { super(type, parentBean); } /** * 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) { if (getBean() instanceof RapidBeanImplSimple) { if (((TypePropertyCollection) propType).getMaxmult() != 1) { Property.setValueByReflection(getBean(), getName(), createNewCollection()); } } else { this.value = createNewCollection(); } } } /** * implementation of toString(). * * @return the String representation of this collection */ @SuppressWarnings("unchecked") public String toString() { final Collection<?> value = getValue(); if (value == null) { return null; } final char sep = ((TypePropertyCollection) this.getType()).getCharSeparator(); final char esc = ((TypePropertyCollection) this.getType()).getCharEscape(); StringBuffer sb = new StringBuffer(); int i = 0; for (final Link link : (Collection<Link>) value) { if (i > 0) { sb.append(sep); } sb.append(StringHelper.escape(link.getIdString(), esc, sep)); i++; } return sb.toString(); } /** * validation for Collection Properties. - delegate validation to all * components. * * @param newValue * the new value to validate * * @return the converted value which is the internal representation or if a * primitive type the corresponding value object */ @SuppressWarnings("unchecked") public Object validate(final Object newValue) { final Collection<Link> ret = validate(newValue, ValidationMode.set); if (((TypePropertyCollection) this.getType()).isComposition() && getValue() != null) { for (final Link link : (Collection<Link>) getValue()) { if (link instanceof RapidBean) { ((RapidBean) link).validate(); } } } return ret; } /** * validation for Collection Properties. * * @param newValue * the new value * @param mode * ( add | remove | set ) * * @return the converted value which is the internal representation or if a * primitive type the corresponding value object */ @SuppressWarnings("unchecked") public Collection<Link> validate(final Object newValue, final ValidationMode mode) { // general validation includes conversion final Collection<Link> collection = (Collection<Link>) super.validate(newValue); if (!ThreadLocalValidationSettings.getValidation()) { return collection; } if (collection == null) { return null; } final TypePropertyCollection type = (TypePropertyCollection) this.getType(); final int size = collection.size(); // check new collection size against maximal multiplicity final int maxmult = type.getMaxmult(); if (maxmult != TypePropertyCollection.INFINITE && size > maxmult) { final Object[] oa = { this.getBean().getType().getName() + "::" + this.getBean().getIdString(), maxmult }; throw new ValidationException("invalid.prop.collection.val.maxmult", this.getBean(), "Bean \"" + this.getBean().getType().getName() + "::" + this.getBean().getIdString() + "\"" + ".\nMaximal multiplicity " + maxmult + " exceeded.", oa); } // check new collection size against maximal multiplicity final int minmult = type.getMinmult(); if (size < minmult) { final Object[] oa = { this.getBean().getType().getName() + "::" + this.getBean().getIdString(), minmult }; throw new ValidationException("invalid.prop.collection.val.minmult", this.getBean(), "Bean \"" + this.getBean().getType().getName() + "::" + this.getBean().getIdString() + "\"" + ".\nMinimal multiplicity " + maxmult + " undergone.", oa); } // check target types of links of new collection for (Link link : collection) { if (link instanceof RapidBean) { final RapidBean bean = (RapidBean) link; if (!TypeRapidBean.isSameOrSubtype(type.getTargetType(), bean.getType())) { throw new ValidationException("invalid.prop.collection.targettype", this.getBean(), "Collection property \"" + this.getType().getPropName() + "\": " + " invalid bean type \"" + bean.getType().getName() + "\".\nType \"" + type.getTargetType().getName() + "\" is required."); } } } return collection; } /** * converts a variety of types to Collection. * * @param collectionValue * the value to convert * * @return a List of beans */ @SuppressWarnings("unchecked") public Collection<Link> convertValue(final Object collectionValue) { if (collectionValue == null) { return null; } final Collection<Link> collection = createNewCollection(); if (collectionValue instanceof Collection) { final Collection<Link> argValList = (Collection<Link>) collectionValue; for (Object obj : argValList) { checkBean(collection, obj); } } else if (collectionValue instanceof Object[]) { final Object[] argValList = (Object[]) collectionValue; for (Object obj : argValList) { checkBean(collection, obj); } } else if (collectionValue instanceof RapidBean) { collection.add((RapidBean) collectionValue); } else if (collectionValue instanceof LinkFrozen) { collection.add((LinkFrozen) collectionValue); } else if (collectionValue instanceof String) { for (String frozenLinkDescription : StringHelper.splitEscaped((String) collectionValue, ((TypePropertyCollection) this.getType()).getCharSeparator(), ((TypePropertyCollection) this.getType()).getCharEscape())) { collection.add(new LinkFrozen(frozenLinkDescription)); } } else if (collectionValue instanceof String[]) { 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", this, "Collection property \"" + this.getType().getPropName() + "\": " + " invalid data type " + collectionValue.getClass().getName() + ".\nOnly \"bean\", \"Collection<RapidBean>\", \"RapidBean[]\"" + " \"String[]\", and \"String\" are valid types."); } return collection; } /** * Check a single bean. * * @param collection * add the instance to this collection if it is ok. * @param obj * the instance to check */ private void checkBean(final Collection<Link> collection, Object obj) { if (obj == null) { throw new ValidationException("invalid.prop.collection.contentnull", this.getBean(), "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", this.getBean(), "Collection property \"" + this.getType().getPropName() + "\": " + " invalid content data type " + obj.getClass().getName() + ".\nOnly \"bean\", \"LinkFrozen\", and \"String\" are valid types."); } } /** * just an empty object array. */ private static final Object[] COLLECTION_CONSTR_ARGS = new Object[0]; /** * internal creation of new collections. * * @return the collection */ @SuppressWarnings("unchecked") public Collection<Link> createNewCollection() { final Class<?> collectionClass = ((TypePropertyCollection) this.getType()).getCollectionClass(); // construct standard collection if (collectionClass == LinkedHashSet.class) { return new LinkedHashSet<Link>(); } else if (collectionClass == HashSet.class) { return new HashSet<Link>(); } else if (collectionClass == TreeSet.class) { return new TreeSet<Link>(); } else if (collectionClass == ArrayList.class) { return new ArrayList<Link>(); } else if (collectionClass == LinkedList.class) { return new LinkedList<Link>(); } else if (collectionClass == Vector.class) { return new Vector<Link>(); } // use reflective creation for special collection classes Collection<Link> newCol = null; try { newCol = (Collection<Link>) ((TypePropertyCollection) this.getType()).getCollectionClassConstructor() .newInstance(COLLECTION_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 newCol; } /** * Helper function. * * @param idstr * the id string to search for * * @return if the collection already contains a link with the given * idstring. */ @SuppressWarnings("unchecked") private boolean containsFrozenLinkWithIdstring(final String idstr) { boolean contains = false; for (Link link : (Collection<Link>) getValue()) { if (link instanceof LinkFrozen && link.getIdString().equals(idstr)) { contains = true; break; } } return contains; } /** * The before property change event handle method to implement by every * listener. * * @param e * the property change event */ public void propertyChangePre(final PropertyChangeEvent e) { // do nothing } /** * The after property change event handle method to implement by every * listener. * * @param e * the property change event */ public void propertyChanged(final PropertyChangeEvent e) { final TypePropertyCollection proptype = (TypePropertyCollection) this.getType(); if (proptype.getSorting() == SortingType.byPropertyValues || proptype.getSorting() == SortingType.byPropertyValuesAll) { sort(); } } @SuppressWarnings("unchecked") public void sort() { final TypePropertyCollection proptype = (TypePropertyCollection) this.getType(); if (proptype.getSorting() == null) { return; } final TypeProperty[] propsbefore = BeanSorter.get(); prepareSorting(proptype); try { final Collection<Link> newCol = createNewCollection(); if (isSorted(newCol)) { for (final Link link : (Collection<Link>) getValue()) { newCol.add(link); } } else { final Link[] links = ((Collection<Link>) getValue()).toArray(new Link[0]); Arrays.sort(links); for (final Link link : links) { newCol.add(link); } } final Collection<Link> oldCol = (Collection<Link>) getValue(); final boolean changed = !collectionsContainSameLinks(oldCol, newCol); if (changed) { fireChangePre(this, PropertyChangeEventType.set, oldCol, newCol, null); } if (getBean() instanceof RapidBeanImplSimple) { if (proptype.getMaxmult() == 1) { Property.setValueByReflection(getBean(), getName(), newCol.iterator().next()); } else { Property.setValueByReflection(getBean(), getName(), newCol); } } else { this.value = newCol; } if (changed) { fireChanged(this, PropertyChangeEventType.set, oldCol, newCol, null); } } finally { cleanupSorting(proptype, propsbefore); } } /** * Compare two Link collections for containing the same links. * * @param col1 * the first collection of Links * @param col2 * the second collection of Links * @return true if both collections contain exactly the same Links in the * same order or if both collections are null<br/> * false otherwise */ public static boolean collectionsContainSameLinks(final Collection<Link> col1, final Collection<Link> col2) { boolean colsEqual = true; if (col1 == null && col2 == null) { colsEqual = true; } else if (col1 != null && col2 == null || col1 == null && col2 != null) { colsEqual = false; } else if (col1.size() != col2.size()) { colsEqual = false; } else { Iterator<Link> it = col2.iterator(); for (final Link link : col1) { final Link oldLink = it.next(); if (!link.equals(oldLink)) { colsEqual = false; break; } } } return colsEqual; } /** * prepare sorting. * * @param proptype * the association end property's type */ public static void prepareSorting(final TypeProperty proptype) { if (!(proptype instanceof TypePropertyCollection)) { return; } final TypePropertyCollection aproptype = (TypePropertyCollection) proptype; if (aproptype.getSorting() != null) { switch (aproptype.getSorting()) { case byPropertyValues: BeanSorter.set(aproptype.getSortingProptypes()); break; } } } /** * prepare sorting. * * @param proptype * the association end property's type */ public static void cleanupSorting(final TypeProperty proptype, final TypeProperty[] propsbefore) { if (!(proptype instanceof TypePropertyCollection)) { return; } final TypePropertyCollection aproptype = (TypePropertyCollection) proptype; if (aproptype.getSorting() != null) { switch (aproptype.getSorting()) { case byPropertyValues: BeanSorter.set(propsbefore); break; } } } /** * The finalizer of any collection property not used anymore. */ @SuppressWarnings("unchecked") @Override protected void finalize() { removePropertyChangeListeners((Collection<Link>) getValue()); } /** * Remove all property change listeners from the collection given. * * @param col * a collection of links */ private void removePropertyChangeListeners(final Collection<Link> col) { final TypePropertyCollection proptype = (TypePropertyCollection) this.getType(); if (col != null && (proptype.getSorting() == SortingType.byPropertyValues || proptype.getSorting() == SortingType.byPropertyValuesAll)) { for (Link link : col) { if (link instanceof RapidBean) { ((RapidBean) link).removePropertyChangeListener(this); } } } } private boolean isSorted(final Collection<?> newCol) { if (newCol instanceof TreeSet<?>) { return true; } if (newCol instanceof LinkedHashSet<?>) { return true; } return false; } public enum ValidationMode { add, remove, set } }