com.evolveum.midpoint.prism.PrismContainerValue.java Source code

Java tutorial

Introduction

Here is the source code for com.evolveum.midpoint.prism.PrismContainerValue.java

Source

/*
 * Copyright (c) 2010-2015 Evolveum
 *
 * Licensed 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 com.evolveum.midpoint.prism;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.xml.namespace.QName;

import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.parser.JaxbDomHack;
import com.evolveum.midpoint.prism.parser.XNodeProcessor;
import com.evolveum.midpoint.prism.path.IdItemPathSegment;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.ItemPathSegment;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
import com.evolveum.midpoint.prism.xnode.MapXNode;
import com.evolveum.midpoint.prism.xnode.XNode;
import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.PrettyPrinter;
import com.evolveum.midpoint.util.QNameUtil;
import com.evolveum.midpoint.util.exception.SchemaException;
import com.evolveum.midpoint.util.exception.SystemException;
import com.evolveum.midpoint.util.logging.Trace;
import com.evolveum.midpoint.util.logging.TraceManager;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.Validate;

/**
 * @author semancik
 *
 */
public class PrismContainerValue<C extends Containerable> extends PrismValue implements DebugDumpable {

    private static final Trace LOGGER = TraceManager.getTrace(PrismContainerValue.class);

    // This is list. We need to maintain the order internally to provide consistent
    // output in DOM and other ordering-sensitive representations
    private List<Item<?, ?>> items = null;
    private Long id;

    // XNode map of all sub-elements in this container value.
    private MapXNode rawXNode = null;
    // The elements are set during a schema-less parsing, e.g. during a dumb JAXB parsing of the object
    // We can't do anything smarter, as we don't have definition nor prism context. We cannot even convert
    // this to XNode because no prism context means no parser.
    // So we store the raw elements here and process them later (e.g. during applyDefinition).
    private List<Object> rawElements = null;

    private C containerable = null;

    /**
     * Concrete type of the containerable value. It is the declared container type or any of its subtypes.
     * If null, it is considered to be the declared type itself.
     *
     * (It is advisable to keep it null when the concrete type is the same as declared type
     * in order to prevent the serialized form from having unnecessary type QName declaration,
     * as currently this information is directly serialized into value's MapXNode representation
     * as type attribute.)
     *
     * Currently this feature is "half-baked" and experimental. If you need it, you have to set it up explicitly
     * when creating your container value.
     */
    private QName concreteType = null;

    private PrismContainerDefinition concreteTypeDefinition = null; // lazily evaluated

    transient private PrismContext prismContext;

    public PrismContainerValue() {
        super();
        // Nothing to do
    }

    public PrismContainerValue(PrismContext prismContext) {
        this();
        setPrismContext(prismContext);
    }

    private void setPrismContext(PrismContext prismContext) {
        //Validate.notNull(prismContext, "No prismContext in PrismContainerValue");             // not yet
        //if (prismContext == null) {
        //    LOGGER.warn("No prismContext in PrismContainerValue");
        //}
        this.prismContext = prismContext;
    }

    private PrismContainerValue(OriginType type, Objectable source, PrismContainerable container, Long id,
            QName concreteType) {
        super(type, source, container);
        this.id = id;
        this.concreteType = concreteType;
    }

    public PrismContainerValue(OriginType type, Objectable source, PrismContainerable container, Long id,
            QName concreteType, PrismContext prismContext) {
        this(type, source, container, id, concreteType);
        setPrismContext(prismContext);
    }

    @Override
    public PrismContext getPrismContext() {
        if (prismContext != null) {
            return prismContext;
        }
        if (getParent() != null) {
            return getParent().getPrismContext();
        }
        return null;
    }

    /**
     * Returns a set of items that the property container contains. The items may be properties or inner property containers.
     * <p/>
     * The set may be null. In case there are no properties an empty set is
     * returned.
     * <p/>
     * Returned set is mutable. Live object is returned.
     *
     * @return set of items that the property container contains.
     */

    public List<Item<?, ?>> getItems() {
        return items;
    }

    public Item<?, ?> getNextItem(Item<?, ?> referenceItem) {
        if (items == null) {
            return null;
        }
        Iterator<Item<?, ?>> iterator = items.iterator();
        while (iterator.hasNext()) {
            Item<?, ?> item = iterator.next();
            if (item == referenceItem) {
                if (iterator.hasNext()) {
                    return iterator.next();
                } else {
                    return null;
                }
            }
        }
        throw new IllegalArgumentException("Item " + referenceItem + " is not part of " + this);
    }

    public Item<?, ?> getPreviousItem(Item<?, ?> referenceItem) {
        if (items == null) {
            return null;
        }
        Item<?, ?> lastItem = null;
        Iterator<Item<?, ?>> iterator = items.iterator();
        while (iterator.hasNext()) {
            Item<?, ?> item = iterator.next();
            if (item == referenceItem) {
                return lastItem;
            }
            lastItem = item;
        }
        throw new IllegalArgumentException("Item " + referenceItem + " is not part of " + this);
    }

    /**
     * Returns a set of properties that the property container contains.
     * <p/>
     * The set must not be null. In case there are no properties an empty set is
     * returned.
     * <p/>
     * Returned set is immutable! Any change to it will be ignored.
     *
     * @return set of properties that the property container contains.
     */
    public Set<PrismProperty<?>> getProperties() {
        Set<PrismProperty<?>> properties = new HashSet<PrismProperty<?>>();
        if (items != null) {
            for (Item<?, ?> item : getItems()) {
                if (item instanceof PrismProperty) {
                    properties.add((PrismProperty<?>) item);
                }
            }
        }
        return properties;
    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @SuppressWarnings("unchecked")
    public PrismContainerable<C> getParent() {
        Itemable parent = super.getParent();
        if (parent == null) {
            return null;
        }
        if (!(parent instanceof PrismContainerable)) {
            throw new IllegalStateException(
                    "Expected that parent of " + PrismContainerValue.class.getName() + " will be "
                            + PrismContainerable.class.getName() + ", but it is " + parent.getClass().getName());
        }
        return (PrismContainerable<C>) parent;
    }

    void setParent(PrismContainerable container) {
        super.setParent(container);
    }

    @SuppressWarnings("unchecked")
    public PrismContainer<C> getContainer() {
        Itemable parent = super.getParent();
        if (parent == null) {
            return null;
        }
        if (!(parent instanceof PrismContainer)) {
            throw new IllegalStateException("Expected that parent of " + PrismContainerValue.class.getName()
                    + " will be " + PrismContainer.class.getName() + ", but it is " + parent.getClass().getName());
        }
        return (PrismContainer<C>) super.getParent();
    }

    void setParent(PrismContainer<C> container) {
        super.setParent(container);
    }

    public ItemPath getPath() {
        Itemable parent = getParent();
        ItemPath parentPath = ItemPath.EMPTY_PATH;
        if (parent != null) {
            parentPath = parent.getPath();
        }
        if (getId() != null) {
            return ItemPath.subPath(parentPath, new IdItemPathSegment(getId()));
        } else {
            return parentPath;
        }
    }

    // For compatibility with other PrismValue types
    public C getValue() {
        return asContainerable();
    }

    private List<Object> createElement() {
        return new ArrayList<Object>();
    }

    public C asContainerable() {
        PrismContainerable parent = getParent();
        if (parent == null) {
            throw new IllegalStateException(
                    "Cannot represent container value without a parent as containerable; value: " + this);
        }

        Class<C> clazz = null;
        if (concreteType != null) {
            clazz = resolveConcreteClass(parent);
        }
        if (clazz == null) {
            clazz = parent.getCompileTimeClass();
        }
        if (clazz == null) {
            throw new SystemException("Unknown compile time class of container '" + parent.getElementName() + "'.");
        }
        if (Modifier.isAbstract(clazz.getModifiers())) {
            throw new SystemException(
                    "Can't create instance of class '" + clazz.getSimpleName() + "', it's abstract.");
        }
        return asContainerableInternal(clazz);
    }

    private Class<C> resolveConcreteClass(PrismContainerable parent) {
        Class<C> clazz;
        PrismContainerDefinition containerDefinition = parent.getPrismContext().getSchemaRegistry()
                .findContainerDefinitionByType(concreteType);
        if (containerDefinition == null) {
            throw new IllegalStateException("A definition for an explicit type " + concreteType + " of a container "
                    + parent.getElementName() + " value couldn't be found");
        }
        clazz = containerDefinition.getCompileTimeClass();
        if (clazz == null) {
            throw new IllegalStateException("A definition for an explicit type " + concreteType + " of a container "
                    + parent.getElementName() + " value has no compile-time class specified");
        }
        return clazz;
    }

    public C asContainerable(Class<C> defaultClazz) {
        Class<C> clazz = defaultClazz;
        if (concreteType != null) {
            PrismContainerable parent = getParent();
            if (parent != null) {
                clazz = resolveConcreteClass(parent);
            } else {
                throw new IllegalStateException("Cannot determine compile-time class for concrete type "
                        + concreteType + " because the respective prism container value has no parent");
            }
        }
        return asContainerableInternal(clazz);
    }

    private C asContainerableInternal(Class<C> clazz) {
        if (containerable != null) {
            return containerable;
        }
        try {
            containerable = clazz.newInstance();
            containerable.setupContainerValue(this);
            return containerable;
        } catch (SystemException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new SystemException("Couldn't create jaxb object instance of '" + clazz + "': " + ex.getMessage(),
                    ex);
        }
    }

    public Collection<QName> getPropertyNames() {
        Collection<QName> names = new HashSet<QName>();
        for (PrismProperty<?> prop : getProperties()) {
            names.add(prop.getElementName());
        }
        return names;
    }

    /**
     * Adds an item to a property container.
     *
     * @param item item to add.
     * @throws SchemaException 
     * @throws IllegalArgumentException an attempt to add value that already exists
     */
    public <IV extends PrismValue, ID extends ItemDefinition> boolean add(Item<IV, ID> item)
            throws SchemaException {
        if (item.getElementName() == null) {
            throw new IllegalArgumentException(
                    "Cannot add item without a name to value of container " + getParent());
        }
        if (findItem(item.getElementName(), Item.class) != null) {
            throw new IllegalArgumentException(
                    "Item " + item.getElementName() + " is already present in " + this.getClass().getSimpleName());
        }
        item.setParent(this);
        PrismContext prismContext = getPrismContext();
        if (prismContext != null) {
            item.setPrismContext(prismContext);
        }
        if (getActualDefinition() != null && item.getDefinition() == null) {
            item.applyDefinition((ID) determineItemDefinition(item.getElementName(), getActualDefinition()), false);
        }
        if (items == null) {
            items = new ArrayList<Item<?, ?>>();
        }
        return items.add(item);
    }

    /**
     * Merges the provided item into this item. The values are joined together.
     * Returns true if new item or value was added.
     */
    public <IV extends PrismValue, ID extends ItemDefinition> boolean merge(Item<IV, ID> item)
            throws SchemaException {
        Item<IV, ID> exisingItem = findItem(item.getElementName(), Item.class);
        if (exisingItem == null) {
            return add(item);
        } else {
            boolean changed = false;
            for (IV newVal : item.getValues()) {
                if (exisingItem.add((IV) newVal.clone())) {
                    changed = true;
                }
            }
            return changed;
        }
    }

    /**
      * Substract the provided item from this item. The values of the provided item are deleted
      * from this item.
      * Returns true if this item was changed.
      */
    public <IV extends PrismValue, ID extends ItemDefinition> boolean substract(Item<IV, ID> item)
            throws SchemaException {
        Item<IV, ID> exisingItem = findItem(item.getElementName(), Item.class);
        if (exisingItem == null) {
            return false;
        } else {
            boolean changed = false;
            for (IV newVal : item.getValues()) {
                if (exisingItem.remove(newVal)) {
                    changed = true;
                }
            }
            return changed;
        }
    }

    /**
     * Adds an item to a property container. Existing value will be replaced.
     *
     * @param item item to add.
     */
    public <IV extends PrismValue, ID extends ItemDefinition> void addReplaceExisting(Item<IV, ID> item)
            throws SchemaException {
        if (item == null) {
            return;
        }
        Item<IV, ID> existingItem = findItem(item.getElementName(), Item.class);
        if (existingItem != null && items != null) {
            items.remove(existingItem);
            existingItem.setParent(null);
        }
        add(item);
    }

    public <IV extends PrismValue, ID extends ItemDefinition> void remove(Item<IV, ID> item) {
        Validate.notNull(item, "Item must not be null.");

        Item<IV, ID> existingItem = findItem(item.getElementName(), Item.class);
        if (existingItem != null && items != null) {
            items.remove(existingItem);
            existingItem.setParent(null);
        }
    }

    public void removeAll() {
        if (items == null) {
            return;
        }
        Iterator<Item<?, ?>> iterator = items.iterator();
        while (iterator.hasNext()) {
            Item<?, ?> item = iterator.next();
            item.setParent(null);
            iterator.remove();
        }
    }

    /**
     * Adds a collection of items to a property container.
     *
     * @param itemsToAdd items to add
     * @throws IllegalArgumentException an attempt to add value that already exists
     */
    public void addAll(Collection<? extends Item<?, ?>> itemsToAdd) throws SchemaException {
        for (Item<?, ?> item : itemsToAdd) {
            add(item);
        }
    }

    /**
     * Adds a collection of items to a property container. Existing values will be replaced.
     *
     * @param itemsToAdd items to add
     */
    public void addAllReplaceExisting(Collection<? extends Item<?, ?>> itemsToAdd) throws SchemaException {
        // Check for conflicts, remove conflicting values
        for (Item<?, ?> item : itemsToAdd) {
            Item<?, ?> existingItem = findItem(item.getElementName(), Item.class);
            if (existingItem != null && items != null) {
                items.remove(existingItem);
            }
        }
        addAll(itemsToAdd);
    }

    public <IV extends PrismValue, ID extends ItemDefinition> void replace(Item<IV, ID> oldItem,
            Item<IV, ID> newItem) throws SchemaException {
        remove(oldItem);
        add(newItem);
    }

    // Expects that the "self" path segment is already included in the basePath
    // todo treat unqualified names
    // is this method used anywhere?
    //    void addItemPathsToList(ItemPath basePath, Collection<ItemPath> list) {
    //       if (items != null) {
    //          for (Item<?> item: items) {
    //             if (item instanceof PrismProperty) {
    //                list.add(basePath.subPath(item.getElementName()));
    //             } else if (item instanceof PrismContainer) {
    //                ((PrismContainer<?>)item).addItemPathsToList(basePath, list);
    //             }
    //          }
    //       }
    //    }

    public void clear() {
        if (items != null) {
            items.clear();
        }
    }

    public boolean contains(Item item) {
        if (items != null) {
            return items.contains(item);
        }
        return false;
    }

    public boolean contains(QName itemName) {
        return findItem(itemName) != null;
    }

    @Override
    public Object find(ItemPath path) {
        if (path == null || path.isEmpty()) {
            return this;
        }
        ItemPathSegment first = path.first();
        if (!(first instanceof NameItemPathSegment)) {
            throw new IllegalArgumentException(
                    "Attempt to lookup item using a non-name path " + path + " in " + this);
        }
        QName subName = ((NameItemPathSegment) first).getName();
        ItemPath rest = path.rest();
        Item<?, ?> subItem = findItem(subName);
        if (subItem == null) {
            return null;
        }
        return subItem.find(rest);
    }

    @Override
    public <IV extends PrismValue, ID extends ItemDefinition> PartiallyResolvedItem<IV, ID> findPartial(
            ItemPath path) {
        if (path == null || path.isEmpty()) {
            // Incomplete path
            return null;
        }
        ItemPathSegment first = path.first();
        if (!(first instanceof NameItemPathSegment)) {
            throw new IllegalArgumentException(
                    "Attempt to lookup item using a non-name path " + path + " in " + this);
        }
        QName subName = ((NameItemPathSegment) first).getName();
        ItemPath rest = path.rest();
        Item<?, ?> subItem = findItem(subName);
        if (subItem == null) {
            return null;
        }
        return subItem.findPartial(rest);
    }

    @SuppressWarnings("unchecked")
    public <X> PrismProperty<X> findProperty(QName propertyQName) {
        return findItem(propertyQName, PrismProperty.class);
    }

    public <X> PrismProperty<X> findProperty(ItemPath propertyPath) {
        return (PrismProperty) findItem(propertyPath);
    }

    /**
     * Finds a specific property in the container by definition.
     * <p/>
     * Returns null if nothing is found.
     *
     * @param propertyDefinition property definition to find.
     * @return found property or null
     */
    public <X> PrismProperty<X> findProperty(PrismPropertyDefinition<X> propertyDefinition) {
        if (propertyDefinition == null) {
            throw new IllegalArgumentException("No property definition");
        }
        return findProperty(propertyDefinition.getName());
    }

    public <X extends Containerable> PrismContainer<X> findContainer(QName containerName) {
        return findItem(containerName, PrismContainer.class);
    }

    public PrismReference findReference(QName elementName) {
        return findItem(elementName, PrismReference.class);
    }

    public PrismReference findReferenceByCompositeObjectElementName(QName elementName) {
        if (items == null) {
            return null;
        }
        for (Item item : items) {
            if (item instanceof PrismReference) {
                PrismReference ref = (PrismReference) item;
                PrismReferenceDefinition refDef = ref.getDefinition();
                if (refDef != null) {
                    if (elementName.equals(refDef.getCompositeObjectElementName())) {
                        return ref;
                    }
                }
            }
        }
        return null;
    }

    public <IV extends PrismValue, ID extends ItemDefinition, I extends Item<IV, ID>> I findItem(QName itemName,
            Class<I> type) {
        try {
            return findCreateItem(itemName, type, null, false);
        } catch (SchemaException e) {
            // This should not happen
            throw new SystemException("Internal Error: " + e.getMessage(), e);
        }
    }

    public <IV extends PrismValue, ID extends ItemDefinition> Item<IV, ID> findItem(QName itemName) {
        try {
            return findCreateItem(itemName, Item.class, null, false);
        } catch (SchemaException e) {
            // This should not happen
            throw new SystemException("Internal Error: " + e.getMessage(), e);
        }
    }

    public <IV extends PrismValue, ID extends ItemDefinition> Item<IV, ID> findItem(ItemPath itemPath) {
        try {
            return findCreateItem(itemPath, Item.class, null, false);
        } catch (SchemaException e) {
            // This should not happen
            throw new SystemException("Internal Error: " + e.getMessage(), e);
        }
    }

    @SuppressWarnings("unchecked")
    <IV extends PrismValue, ID extends ItemDefinition, I extends Item<IV, ID>> I findCreateItem(QName itemName,
            Class<I> type, ID itemDefinition, boolean create) throws SchemaException {
        Item<IV, ID> item = findItemByQName(itemName);
        if (item != null) {
            if (type.isAssignableFrom(item.getClass())) {
                return (I) item;
            } else {
                if (create) {
                    throw new IllegalStateException("The " + type.getSimpleName() + " cannot be created because "
                            + item.getClass().getSimpleName() + " with the same name exists ("
                            + item.getElementName() + ")");
                } else {
                    return null;
                }
            }
        }
        if (create) { // todo treat unqualified names
            return createSubItem(itemName, type, itemDefinition);
        } else {
            return null;
        }
    }

    public <IV extends PrismValue, ID extends ItemDefinition, I extends Item<IV, ID>> I findItem(
            ItemDefinition itemDefinition, Class<I> type) {
        if (itemDefinition == null) {
            throw new IllegalArgumentException("No item definition");
        }
        return findItem(itemDefinition.getName(), type);
    }

    // Expects that "self" path is NOT present in propPath
    @SuppressWarnings("unchecked")
    <IV extends PrismValue, ID extends ItemDefinition, I extends Item<IV, ID>> I findCreateItem(ItemPath propPath,
            Class<I> type, ID itemDefinition, boolean create) throws SchemaException {
        ItemPathSegment first = propPath.first();
        if (!(first instanceof NameItemPathSegment)) {
            throw new IllegalArgumentException(
                    "Attempt to lookup item using a non-name path " + propPath + " in " + this);
        }
        QName subName = ((NameItemPathSegment) first).getName();
        ItemPath rest = propPath.rest();
        I item = (I) findItemByQName(subName);
        if (item != null) {
            if (rest.isEmpty()) {
                if (type.isAssignableFrom(item.getClass())) {
                    return (I) item;
                } else {
                    if (create) {
                        throw new SchemaException("The " + type.getSimpleName() + " cannot be created because "
                                + item.getClass().getSimpleName() + " with the same name exists ("
                                + item.getElementName() + ")");
                    } else {
                        return null;
                    }
                }
            } else {
                // Go deeper
                if (item instanceof PrismContainer) {
                    return ((PrismContainer<?>) item).findCreateItem(rest, type, itemDefinition, create);
                } else {
                    if (create) {
                        throw new SchemaException("The " + type.getSimpleName() + " cannot be created because "
                                + item.getClass().getSimpleName() + " with the same name exists ("
                                + item.getElementName() + ")");
                    } else {
                        // Return the item for non-container even if the path is non-empty
                        // FIXME: This is not the best solution but it is needed to be able to look inside properties
                        // such as PolyString
                        if (type.isAssignableFrom(item.getClass())) {
                            return item;
                        } else {
                            return null;
                        }
                    }
                }
            }
        }

        if (create) { // todo treat unqualified names
            if (rest.isEmpty()) {
                return createSubItem(subName, type, itemDefinition);
            } else {
                // Go deeper
                PrismContainer<?> subItem = createSubItem(subName, PrismContainer.class, null);
                return subItem.findCreateItem(rest, type, itemDefinition, create);
            }
        } else {
            return null;
        }
    }

    private <IV extends PrismValue, ID extends ItemDefinition> Item<IV, ID> findItemByQName(QName subName)
            throws SchemaException {
        if (items == null) {
            return null;
        }
        Item<IV, ID> matching = null;
        for (Item<?, ?> item : items) {
            if (QNameUtil.match(subName, item.getElementName())) {
                if (matching != null) {
                    String containerName = getParent() != null
                            ? DebugUtil.formatElementName(getParent().getElementName())
                            : "";
                    throw new SchemaException(
                            "More than one items matching " + subName + " in container " + containerName);
                } else {
                    matching = (Item<IV, ID>) item;
                }
            }
        }
        return matching;
    }

    private <IV extends PrismValue, ID extends ItemDefinition, I extends Item<IV, ID>> I createSubItem(QName name,
            Class<I> type, ID itemDefinition) throws SchemaException {
        // the item with specified name does not exist, create it now
        I newItem = null;

        if (itemDefinition == null && getActualDefinition() != null) {
            itemDefinition = determineItemDefinition(name, getActualDefinition());
            if (itemDefinition == null) {
                throw new SchemaException("No definition for item " + name + " in " + getParent());
            }
        }

        if (itemDefinition != null) {
            if (StringUtils.isNotBlank(name.getNamespaceURI())) {
                newItem = (I) itemDefinition.instantiate(name);
            } else {
                QName computed = new QName(itemDefinition.getNamespace(), name.getLocalPart());
                newItem = (I) itemDefinition.instantiate(computed);
            }
            if (newItem instanceof PrismObject) {
                throw new IllegalStateException(
                        "PrismObject instantiated as a subItem in " + this + " from definition " + itemDefinition);
            }
        } else {
            newItem = Item.createNewDefinitionlessItem(name, type, prismContext);
            if (newItem instanceof PrismObject) {
                throw new IllegalStateException("PrismObject instantiated as a subItem in " + this
                        + " as definitionless instance of class " + type);
            }
        }

        if (type.isAssignableFrom(newItem.getClass())) {
            add(newItem);
            return newItem;
        } else {
            throw new IllegalStateException(
                    "The " + type.getSimpleName() + " cannot be created because the item should be of type "
                            + newItem.getClass().getSimpleName() + " (" + newItem.getElementName() + ")");
        }
    }

    public <T extends Containerable> PrismContainer<T> findOrCreateContainer(QName containerName)
            throws SchemaException {
        return findCreateItem(containerName, PrismContainer.class, null, true);
    }

    public PrismReference findOrCreateReference(QName referenceName) throws SchemaException {
        return findCreateItem(referenceName, PrismReference.class, null, true);
    }

    public <IV extends PrismValue, ID extends ItemDefinition> Item<IV, ID> findOrCreateItem(QName containerName)
            throws SchemaException {
        return findCreateItem(containerName, Item.class, null, true);
    }

    public <IV extends PrismValue, ID extends ItemDefinition, I extends Item<IV, ID>> I findOrCreateItem(
            QName containerName, Class<I> type) throws SchemaException {
        return findCreateItem(containerName, type, null, true);
    }

    public <IV extends PrismValue, ID extends ItemDefinition, I extends Item<IV, ID>> I findOrCreateItem(
            ItemPath path, Class<I> type, ID definition) throws SchemaException {
        return findCreateItem(path, type, definition, true);
    }

    public <X> PrismProperty<X> findOrCreateProperty(QName propertyQName) throws SchemaException {
        PrismProperty<X> property = findItem(propertyQName, PrismProperty.class);
        if (property != null) {
            return property;
        }
        return createProperty(propertyQName);
    }

    public <X> PrismProperty<X> findOrCreateProperty(PrismPropertyDefinition propertyDef) throws SchemaException {
        PrismProperty<X> property = findItem(propertyDef.getName(), PrismProperty.class);
        if (property != null) {
            return property;
        }
        return createProperty(propertyDef);
    }

    //    public PrismProperty findOrCreateProperty(PropertyPath parentPath, QName propertyQName, Class<?> valueClass) {
    //        PrismContainer container = findOrCreatePropertyContainer(parentPath);
    //        if (container == null) {
    //            throw new IllegalArgumentException("No container");
    //        }
    //        return container.findOrCreateProperty(propertyQName, valueClass);
    //    }

    public <X extends Containerable> PrismContainer<X> createContainer(QName containerName) throws SchemaException {
        if (getActualDefinition() == null) {
            throw new IllegalStateException("No definition of container " + containerName);
        }
        PrismContainerDefinition containerDefinition = getActualDefinition().findContainerDefinition(containerName);
        if (containerDefinition == null) {
            throw new IllegalArgumentException(
                    "No definition of container '" + containerName + "' in " + getActualDefinition());
        }
        PrismContainer<X> container = containerDefinition.instantiate();
        add(container);
        return container;
    }

    public <X> PrismProperty<X> createProperty(QName propertyName) throws SchemaException {
        PrismPropertyDefinition propertyDefinition = null;
        if (getActualDefinition() != null) {
            propertyDefinition = getActualDefinition().findPropertyDefinition(propertyName);
            if (propertyDefinition == null) {
                // container has definition, but there is no property definition. This is either runtime schema
                // or an error
                if (getParent().getDefinition().isRuntimeSchema) {
                    // TODO: create opportunistic runtime definition
                    //propertyDefinition = new PrismPropertyDefinition(propertyName, propertyName, typeName, container.prismContext);
                } else {
                    throw new IllegalArgumentException(
                            "No definition for property " + propertyName + " in " + getActualDefinition());
                }
            }
        }
        PrismProperty<X> property = null;
        if (propertyDefinition == null) {
            // Definitionless
            property = new PrismProperty<X>(propertyName, prismContext);
        } else {
            property = propertyDefinition.instantiate();
        }
        add(property);
        return property;
    }

    public <X> PrismProperty<X> createProperty(PrismPropertyDefinition propertyDefinition) throws SchemaException {
        PrismProperty<X> property = propertyDefinition.instantiate();
        add(property);
        return property;
    }

    public void removeProperty(QName propertyName) {
        removeProperty(new ItemPath(propertyName));
    }

    public void removeProperty(ItemPath propertyPath) {
        removeItem(propertyPath, PrismProperty.class);
    }

    public void removeContainer(QName containerName) {
        removeContainer(new ItemPath(containerName));
    }

    public void removeContainer(ItemPath itemPath) {
        removeItem(itemPath, PrismContainer.class);
    }

    public void removeReference(QName name) {
        removeReference(new ItemPath(name));
    }

    public void removeReference(ItemPath path) {
        removeItem(path, PrismReference.class);
    }

    // Expects that "self" path is NOT present in propPath
    <IV extends PrismValue, ID extends ItemDefinition, I extends Item<IV, ID>> void removeItem(ItemPath propPath,
            Class<I> itemType) {
        if (items == null) {
            return;
        }
        ItemPathSegment first = propPath.first();
        if (!(first instanceof NameItemPathSegment)) {
            throw new IllegalArgumentException(
                    "Attempt to remove item using a non-name path " + propPath + " in " + this);
        }
        QName subName = ((NameItemPathSegment) first).getName();
        ItemPath rest = propPath.rest();
        Iterator<Item<?, ?>> itemsIterator = items.iterator();
        while (itemsIterator.hasNext()) {
            Item<?, ?> item = itemsIterator.next();
            if (subName.equals(item.getElementName())) {
                if (!rest.isEmpty() && item instanceof PrismContainer) {
                    ((PrismContainer<?>) item).removeItem(propPath, itemType);
                    return;
                } else {
                    if (itemType.isAssignableFrom(item.getClass())) {
                        itemsIterator.remove();
                    } else {
                        throw new IllegalArgumentException(
                                "Attempt to remove item " + subName + " from " + this + " of type " + itemType
                                        + " while the existing item is of incompatible type " + item.getClass());
                    }
                }
            }
        }
    }

    public void setPropertyRealValue(QName propertyName, Object realValue, PrismContext prismContext)
            throws SchemaException {
        PrismProperty<?> property = findOrCreateProperty(propertyName);
        property.setRealValue(realValue);
        if (property.getPrismContext() == null) {
            property.setPrismContext(prismContext);
        }
    }

    public <T> T getPropertyRealValue(QName propertyName, Class<T> type) {
        PrismProperty<T> property = findProperty(propertyName);
        if (property == null) { // when using sql repo, even non-existing properties do not have 'null' here
            return null;
        }
        return property.getRealValue(type);
    }

    @Override
    public void recompute(PrismContext prismContext) {
        // Nothing to do. The subitems should be already recomputed as they are added to this container.
    }

    @Override
    public void accept(Visitor visitor) {
        super.accept(visitor);
        if (items != null) {
            for (Item<?, ?> item : getItems()) {
                item.accept(visitor);
            }
        }
    }

    @Override
    public void accept(Visitor visitor, ItemPath path, boolean recursive) {
        if (path == null || path.isEmpty()) {
            // We are at the end of path, continue with regular visits all the way to the bottom
            if (recursive) {
                accept(visitor);
            } else {
                visitor.visit(this);
            }
        } else {
            ItemPathSegment first = path.first();
            if (!(first instanceof NameItemPathSegment)) {
                throw new IllegalArgumentException(
                        "Attempt to lookup item using a non-name path " + path + " in " + this);
            }
            QName subName = ((NameItemPathSegment) first).getName();
            ItemPath rest = path.rest();
            if (items != null) {
                for (Item<?, ?> item : items) { // todo unqualified names!
                    if (first.isWildcard() || subName.equals(item.getElementName())) {
                        item.accept(visitor, rest, recursive);
                    }
                }
            }
        }
    }

    public boolean hasCompleteDefinition() {
        if (items != null) {
            for (Item<?, ?> item : getItems()) {
                if (!item.hasCompleteDefinition()) {
                    return false;
                }
            }
        }
        return true;
    }

    @Override
    public boolean representsSameValue(PrismValue other) {
        if (other instanceof PrismContainerValue) {
            return representsSameValue((PrismContainerValue<C>) other);
        } else {
            return false;
        }
    }

    public boolean representsSameValue(PrismContainerValue<C> other) {
        if (getParent() != null) {
            PrismContainerDefinition definition = getActualDefinition();
            if (definition != null) {
                if (definition.isSingleValue()) {
                    // There is only one value, therefore it always represents the same thing
                    return true;
                }
            }
        }
        if (other.getParent() != null) {
            PrismContainerDefinition definition = other.getActualDefinition();
            if (definition != null) {
                if (definition.isSingleValue()) {
                    // There is only one value, therefore it always represents the same thing
                    return true;
                }
            }
        }
        if (this.getId() != null && other.getId() != null) {
            return this.getId().equals(other.getId());
        }
        return false;
    }

    @Override
    void diffMatchingRepresentation(PrismValue otherValue, Collection<? extends ItemDelta> deltas,
            boolean ignoreMetadata, boolean isLiteral) {
        if (otherValue instanceof PrismContainerValue) {
            diffRepresentation((PrismContainerValue) otherValue, deltas, ignoreMetadata, isLiteral);
        } else {
            throw new IllegalStateException("Comparing incompatible values " + this + " - " + otherValue);
        }
    }

    void diffRepresentation(PrismContainerValue<C> otherValue, Collection<? extends ItemDelta> deltas,
            boolean ignoreMetadata, boolean isLiteral) {
        PrismContainerValue<C> thisValue = this;
        if (this.isRaw() || otherValue.isRaw()) {
            try {
                if (this.isRaw()) {
                    otherValue = parseRawElementsToNewValue(otherValue, thisValue);
                } else if (otherValue.isRaw()) {
                    thisValue = parseRawElementsToNewValue(thisValue, otherValue);
                }
            } catch (SchemaException e) {
                // TODO: Maybe just return false?
                throw new IllegalArgumentException("Error parsing the value of container " + getParent()
                        + " using the 'other' definition " + "during a compare: " + e.getMessage(), e);
            }
        }
        diffItems(thisValue, otherValue, deltas, ignoreMetadata, isLiteral);
    }

    @Override
    public boolean isRaw() {
        return rawXNode != null || rawElements != null;
    }

    public MapXNode getRawXNode() {
        return rawXNode;
    }

    public List<Object> getRawElements() {
        return rawElements;
    }

    public boolean addRawElement(Object element) throws SchemaException {
        PrismContainerDefinition<C> definition = getDefinition();
        if (definition == null) {
            // We cannot do much better. We do not even have prism context here.
            if (rawElements == null) {
                if (items != null && !items.isEmpty()) {
                    throw new IllegalStateException(
                            "Attempt to add raw element to prism container value which already has items: " + this);
                }
                rawElements = new ArrayList<Object>();
            }
            return rawElements.add(element);
        } else {
            // We have definition here, we can parse it right now
            Item<?, ?> subitem = parseRawElement(element, definition);
            return merge(subitem);
        }
    }

    public boolean deleteRawElement(Object element) throws SchemaException {
        PrismContainerDefinition<C> definition = getDefinition();
        if (definition == null) {
            // We cannot do much better. We do not even have prism context here.
            if (rawElements == null) {
                rawElements = new ArrayList<Object>();
            }
            return rawElements.add(element);
        } else {
            // We have definition here, we can parse it right now
            Item<?, ?> subitem = parseRawElement(element, definition);
            return substract(subitem);
        }
    }

    public boolean removeRawElement(Object element) {
        return rawElements.remove(element);
    }

    private <IV extends PrismValue, ID extends ItemDefinition> Item<IV, ID> parseRawElement(Object element,
            PrismContainerDefinition<C> definition) throws SchemaException {
        JaxbDomHack jaxbDomHack = definition.getPrismContext().getJaxbDomHack();
        return jaxbDomHack.parseRawElement(element, definition);
    }

    private PrismContainerValue<C> parseRawElementsToNewValue(PrismContainerValue<C> origCVal,
            PrismContainerValue<C> definitionSource) throws SchemaException {
        if (definitionSource.getParent() == null || definitionSource.getActualDefinition() == null) {
            throw new IllegalArgumentException("Attempt to use container " + origCVal.getParent()
                    + " values in a raw parsing state (raw elements) with parsed value that has no definition");
        }
        PrismContainerDefinition<C> definition = definitionSource.getActualDefinition();

        XNode origRawXnode = origCVal.rawXNode;
        if (origRawXnode != null) {
            XNodeProcessor xnodeProcessor = definition.getPrismContext().getXnodeProcessor();
            PrismContainerValue<C> newCVal = xnodeProcessor.parsePrismContainerValue(origRawXnode, definition);
            return newCVal;
        }

        List<Object> origRawElements = origCVal.rawElements;
        if (origRawElements != null) {
            PrismContainerValue<C> newCVal = new PrismContainerValue<C>(prismContext);
            for (Object rawElement : origRawElements) {
                Item<?, ?> subitem = parseRawElement(rawElement, definition);
                newCVal.merge(subitem);
            }
            return newCVal;
        }

        return null;
    }

    @SuppressWarnings({ "rawtypes", "unchecked" })
    void diffItems(PrismContainerValue<C> thisValue, PrismContainerValue<C> other,
            Collection<? extends ItemDelta> deltas, boolean ignoreMetadata, boolean isLiteral) {

        if (thisValue.getItems() != null) {
            for (Item<?, ?> thisItem : thisValue.getItems()) {
                Item otherItem = other.findItem(thisItem.getElementName());
                // The "delete" delta will also result from the following diff
                thisItem.diffInternal(otherItem, deltas, ignoreMetadata, isLiteral);
            }
        }

        if (other.getItems() != null) {
            for (Item otherItem : other.getItems()) {
                Item thisItem = thisValue.findItem(otherItem.getElementName());
                if (thisItem == null) {
                    // Other has an item that we don't have, this must be an add
                    ItemDelta itemDelta = otherItem.createDelta();
                    itemDelta.addValuesToAdd(otherItem.getClonedValues());
                    if (!itemDelta.isEmpty()) {
                        ((Collection) deltas).add(itemDelta);
                    }
                }
            }
        }
    }

    @Override
    protected PrismContainerDefinition<C> getDefinition() {
        return (PrismContainerDefinition<C>) super.getDefinition();
    }

    @Override
    public void applyDefinition(ItemDefinition definition, boolean force) throws SchemaException {
        if (!(definition instanceof PrismContainerDefinition)) {
            throw new IllegalArgumentException("Cannot apply " + definition + " to container " + this);
        }
        applyDefinition((PrismContainerDefinition<C>) definition, force);
    }

    public void applyDefinition(PrismContainerDefinition<C> containerDef, boolean force) throws SchemaException {
        PrismContainerDefinition valueDefinition = getConcreteTypeDefinition();
        if (valueDefinition == null) {
            valueDefinition = containerDef;
        }
        if (valueDefinition.isWildcard()) {
            // No point in aplying this. Nothing will change and there may be phantom errors.
            return;
        }
        if (rawElements != null) {
            for (Object rawElement : rawElements) {
                Item<?, ?> subitem = parseRawElement(rawElement, valueDefinition);
                merge(subitem);
            }
            rawElements = null;
        }
        if (rawXNode != null) {
            XNodeProcessor xnodeProcessor = valueDefinition.getPrismContext().getXnodeProcessor();
            PrismContainerValue<C> newCVal = xnodeProcessor.parsePrismContainerValue(rawXNode, valueDefinition);
            // Maybe we need to manually reset parent on items?
            addAll(newCVal.getItems());
            rawXNode = null;
        }
        if (items != null) {
            for (Item item : items) {
                if (item.getDefinition() != null && !force) {
                    // Item has a definition already, no need to apply it
                    continue;
                }
                ItemDefinition itemDefinition = determineItemDefinition(item.getElementName(), valueDefinition);
                if (itemDefinition == null && item.getDefinition() != null && item.getDefinition().isDynamic()) {
                    // We will not apply the null definition here. The item has a dynamic definition that we don't
                    // want to destroy as it cannot be reconstructed later.
                } else {
                    item.applyDefinition(itemDefinition);
                }
            }
        }
    }

    /**
     * This method can both return null and throws exception. It returns null in case there is no definition
     * but it is OK (e.g. runtime schema). It throws exception if there is no definition and it is not OK.
     */
    private <ID extends ItemDefinition> ID determineItemDefinition(QName itemName,
            PrismContainerDefinition<C> containerDefinition) throws SchemaException {
        ID itemDefinition = containerDefinition.findItemDefinition(itemName);
        if (itemDefinition == null) {
            if (containerDefinition.isRuntimeSchema()) {
                // If we have prism context, try to locate global definition. But even if that is not
                // found it is still OK. This is runtime container. We tolerate quite a lot here.
                PrismContext prismContext = getPrismContext();
                if (prismContext != null) {
                    itemDefinition = (ID) prismContext.getSchemaRegistry().resolveGlobalItemDefinition(itemName);
                }
            } else {
                throw new SchemaException("No definition for item " + itemName + " in " + getParent());
            }
        }
        return itemDefinition;
    }

    @Override
    public void revive(PrismContext prismContext) throws SchemaException {
        if (this.prismContext == null) {
            this.prismContext = prismContext;
        }
        super.revive(prismContext);
        if (items != null) {
            for (Item<?, ?> item : items) {
                item.revive(prismContext);
            }
        }
    }

    @Override
    public boolean isEmpty() {
        if (id != null) {
            return false;
        }
        if (items == null) {
            return true;
        }
        return items.isEmpty();
    }

    @Override
    public void normalize() {
        if (items != null) {
            Iterator<Item<?, ?>> iterator = items.iterator();
            while (iterator.hasNext()) {
                Item<?, ?> item = iterator.next();
                item.normalize();
                if (item.isEmpty()) {
                    iterator.remove();
                }
            }
        }
    }

    @Override
    public void checkConsistenceInternal(Itemable rootItem, boolean requireDefinitions, boolean prohibitRaw,
            ConsistencyCheckScope scope) {
        ItemPath myPath = getPath();
        if (scope.isThorough()) {
            if (prohibitRaw && isRaw()) {
                throw new IllegalStateException(
                        "Raw elements in container value " + this + " (" + myPath + " in " + rootItem + ")");
            }
            if (items == null && !isRaw()) {
                // This is normal empty container, isn't it?
                //             throw new IllegalStateException("Neither items nor raw elements specified in container value "+this+" ("+myPath+" in "+rootItem+")");
            }
            if (items != null && isRaw()) {
                throw new IllegalStateException("Both items and raw elements specified in container value " + this
                        + " (" + myPath + " in " + rootItem + ")");
            }
        }
        if (items != null) {
            for (Item<?, ?> item : items) {
                if (scope.isThorough()) {
                    if (item == null) {
                        throw new IllegalStateException(
                                "Null item in container value " + this + " (" + myPath + " in " + rootItem + ")");
                    }
                    if (item.getParent() == null) {
                        throw new IllegalStateException("No parent for item " + item + " in container value " + this
                                + " (" + myPath + " in " + rootItem + ")");
                    }
                    if (item.getParent() != this) {
                        throw new IllegalStateException(
                                "Wrong parent for item " + item + " in container value " + this + " (" + myPath
                                        + " in " + rootItem + "), " + "bad parent: " + item.getParent());
                    }
                }
                item.checkConsistenceInternal(rootItem, requireDefinitions, prohibitRaw, scope);
            }
        }
    }

    public void assertDefinitions(String sourceDescription) throws SchemaException {
        assertDefinitions(false, sourceDescription);
    }

    public void assertDefinitions(boolean tolerateRaw, String sourceDescription) throws SchemaException {
        if (getItems() == null) {
            return;
        }
        for (Item<?, ?> item : getItems()) {
            item.assertDefinitions(tolerateRaw, "value(" + getId() + ") in " + sourceDescription);
        }
    }

    public PrismContainerValue<C> clone() {
        PrismContainerValue<C> clone = new PrismContainerValue<C>(getOriginType(), getOriginObject(), getParent(),
                getId(), this.concreteType, this.prismContext);
        copyValues(clone);
        return clone;
    }

    protected void copyValues(PrismContainerValue<C> clone) {
        super.copyValues(clone);
        clone.id = this.id;
        if (this.items != null) {
            for (Item<?, ?> item : this.items) {
                Item<?, ?> clonedItem = item.clone();
                clonedItem.setParent(clone);
                if (clone.items == null) {
                    clone.items = new ArrayList<>(this.items.size());
                }
                clone.items.add(clonedItem);
            }
        }
        // TODO: deep clonning?
        clone.rawXNode = this.rawXNode;
        clone.rawElements = this.rawElements;
    }

    protected void deepCloneDefinition(boolean ultraDeep, PrismContainerDefinition<C> clonedContainerDef) {
        if (items != null) {
            for (Item<?, ?> item : items) {
                deepCloneDefinitionItem(item, ultraDeep, clonedContainerDef);
            }
        }
    }

    private <IV extends PrismValue, ID extends ItemDefinition, I extends Item<IV, ID>> void deepCloneDefinitionItem(
            I item, boolean ultraDeep, PrismContainerDefinition<C> clonedContainerDef) {
        PrismContainerDefinition<C> oldContainerDef = getDefinition();
        QName itemName = item.getElementName();
        ID oldItemDefFromContainer = oldContainerDef.findItemDefinition(itemName);
        ID oldItemDef = item.getDefinition();
        ID clonedItemDef;
        if (oldItemDefFromContainer == oldItemDef) {
            clonedItemDef = clonedContainerDef.findItemDefinition(itemName);
        } else {
            clonedItemDef = (ID) oldItemDef.deepClone(ultraDeep);
        }
        item.propagateDeepCloneDefinition(ultraDeep, clonedItemDef);
        item.setDefinition(clonedItemDef);
    }

    @Override
    public boolean equalsComplex(PrismValue other, boolean ignoreMetadata, boolean isLiteral) {
        if (other == null || !(other instanceof PrismContainerValue<?>)) {
            return false;
        }
        return equalsComplex((PrismContainerValue<?>) other, ignoreMetadata, isLiteral);
    }

    public boolean equalsComplex(PrismContainerValue<?> other, boolean ignoreMetadata, boolean isLiteral) {
        if (!super.equalsComplex(other, ignoreMetadata, isLiteral)) {
            return false;
        }
        if (!ignoreMetadata) {
            if (this.id == null) {
                if (other.id != null)
                    return false;
            } else if (!this.id.equals(other.id))
                return false;
        }
        if (this.items == null) {
            if (other.items != null)
                return false;
        } else if (!this.equalsItems(this, (PrismContainerValue<C>) other, ignoreMetadata, isLiteral)) {
            return false;
        }
        return true;
    }

    boolean equalsItems(PrismContainerValue<C> other, boolean ignoreMetadata, boolean isLiteral) {
        return equalsItems(this, other, ignoreMetadata, isLiteral);
    }

    boolean equalsItems(PrismContainerValue<C> thisValue, PrismContainerValue<C> other, boolean ignoreMetadata,
            boolean isLiteral) {
        Collection<? extends ItemDelta<?, ?>> deltas = new ArrayList<ItemDelta<?, ?>>();
        // The EMPTY_PATH is a lie. We don't really care if the returned deltas have correct path or not
        // we only care whether some deltas are returned or not.
        diffItems(thisValue, other, deltas, ignoreMetadata, isLiteral);
        return deltas.isEmpty();
    }

    public boolean equivalent(PrismContainerValue<?> other) {
        return equalsRealValue(other);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        PrismContainerValue<?> other = (PrismContainerValue<?>) obj;
        return equalsComplex(other, false, false);
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = prime * result + ((id == null) ? 0 : id.hashCode());
        result = prime * result + ((items == null) ? 0 : MiscUtil.unorderedCollectionHashcode(items));
        return result;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("PCV(");
        sb.append(getId());
        if (isRaw()) {
            sb.append(", raw");
        }
        sb.append("):");
        sb.append(getItems());
        return sb.toString();
    }

    @Override
    public String debugDump() {
        return debugDump(0);
    }

    @Override
    public String debugDump(int indent) {
        StringBuilder sb = new StringBuilder();
        boolean wasIndent = false;
        if (DebugUtil.isDetailedDebugDump()) {
            DebugUtil.indentDebugDump(sb, indent);
            wasIndent = true;
            sb.append("PCV").append(": ");
        }
        boolean multivalue = true;
        PrismContainerable<C> parent = getParent();
        if (parent != null && parent.getDefinition() != null) {
            multivalue = parent.getDefinition().isMultiValue();
        }
        Long id = getId();
        if (multivalue || id != null || DebugUtil.isDetailedDebugDump()) {
            if (!wasIndent) {
                DebugUtil.indentDebugDump(sb, indent);
                wasIndent = true;
            }
            sb.append("id=").append(PrettyPrinter.prettyPrint(getId()));
        }
        appendOriginDump(sb);
        List<Item<?, ?>> items = getItems();
        if (items != null) {
            Iterator<Item<?, ?>> i = getItems().iterator();
            if (wasIndent && i.hasNext()) {
                sb.append("\n");
            }
            while (i.hasNext()) {
                Item<?, ?> item = i.next();
                sb.append(item.debugDump(indent + 1));
                if (i.hasNext()) {
                    sb.append("\n");
                }
            }
        } else {
            if (isRaw()) {
                if (wasIndent) {
                    sb.append("\n");
                }
                DebugUtil.indentDebugDump(sb, indent + 1);
                sb.append("(raw)");
            }
        }
        return sb.toString();
    }

    @Override
    public boolean match(PrismValue otherValue) {
        return equalsRealValue(otherValue);
    }

    @Override
    public String toHumanReadableString() {
        return "id=" + id + ": " + items.size() + " items";
    }

    // copies the definition from original to aClone (created outside of this method)
    // it has to (artifically) create a parent PrismContainer to hold the definition
    //
    // without having a definition, such containers cannot be serialized using
    // PrismJaxbProcessor.marshalContainerableToString (without definition, there is
    // no information on corresponding element name)
    //
    // todo review usefulness and appropriateness of this method and its placement
    public static void copyDefinition(Containerable aClone, Containerable original) {
        try {
            Validate.notNull(original.asPrismContainerValue().getParent(),
                    "original PrismContainerValue has no parent");

            PrismContainerDefinition definition = original.asPrismContainerValue().getActualDefinition();
            Validate.notNull(definition, "original PrismContainer definition is null");

            PrismContainer<?> aCloneParent = definition.instantiate();
            aCloneParent.add(aClone.asPrismContainerValue());
        } catch (SchemaException e) {
            throw new SystemException(
                    "Unexpected SchemaException when copying definition from original object to its clone", e);
        }
    }

    public QName getConcreteType() {
        return concreteType;
    }

    public void setConcreteType(QName concreteType) {
        this.concreteType = concreteType;
        this.concreteTypeDefinition = null;
    }

    // TODO change from PrismContainerDefinition to ComplexTypeDefinition
    // because in current state there should be an element definition for each subtype that is to be resolvable in this way
    public PrismContainerDefinition getConcreteTypeDefinition() {
        if (concreteTypeDefinition != null) {
            return concreteTypeDefinition;
        }
        if (concreteType != null) {
            // First of all, if we have parent definition and it is applicable, just use that
            // (Besides performance aspects, the parent definition might be already converted
            // to contain ResourceAttributeContainerDefinition instead of PrismContainerDefinition
            // - in case of ShadowType.)
            if (getParent() != null && getParent().getDefinition() != null
                    && concreteType.equals(getParent().getDefinition().getTypeName())) {
                concreteTypeDefinition = getParent().getDefinition();
            } else {
                PrismContext prismContext = getPrismContext();
                if (prismContext != null) {
                    concreteTypeDefinition = prismContext.getSchemaRegistry()
                            .findContainerDefinitionByType(concreteType);
                    if (concreteTypeDefinition == null) {
                        throw new IllegalStateException(
                                "Couldn't find a definition for concrete type " + concreteType);
                    }
                }
            }
        }
        return concreteTypeDefinition; // may still be null at this moment
    }

    private PrismContainerDefinition<C> getActualDefinition() {
        if (getParent() != null) {
            PrismContainerDefinition concreteDef = getConcreteTypeDefinition();
            if (concreteDef != null) {
                return concreteDef;
            } else {
                return getParent().getDefinition();
            }
        } else {
            return null;
        }
    }

}