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

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2010-2013 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 com.evolveum.midpoint.prism.delta.ContainerDelta;
import com.evolveum.midpoint.prism.delta.ItemDelta;
import com.evolveum.midpoint.prism.path.IdItemPathSegment;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.PrettyPrinter;
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.collections4.CollectionUtils;
import org.jetbrains.annotations.NotNull;

import javax.xml.namespace.QName;

import java.lang.reflect.Modifier;
import java.util.*;
import java.util.function.Consumer;

/**
 * <p>
 * Property container groups properties into logical blocks.The reason for
 * grouping may be as simple as better understandability of data structure. But
 * the group usually means different meaning, source or structure of the data.
 * For example, the property container is frequently used to hold properties
 * that are dynamic, not fixed by a static schema. Such grouping also naturally
 * translates to XML and helps to "quarantine" such properties to avoid Unique
 * Particle Attribute problems.
 * </p><p>
 * Property Container contains a set of (potentially multi-valued) properties or inner property containers.
 * The order of properties is not significant, regardless of the fact that it
 * may be fixed in the XML representation. In the XML representation, each
 * element inside Property Container must be either Property or a Property
 * Container.
 * </p><p>
 * Property Container is mutable.
 * </p>
 *
 * @author Radovan Semancik
 */
public class PrismContainer<C extends Containerable>
        extends Item<PrismContainerValue<C>, PrismContainerDefinition<C>> implements PrismContainerable<C> {
    private static final long serialVersionUID = 5206821250098051028L;

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

    protected Class<C> compileTimeClass;

    public PrismContainer(QName name) {
        super(name);
    }

    public PrismContainer(QName name, PrismContext prismContext) {
        super(name, prismContext);
    }

    public PrismContainer(QName name, Class<C> compileTimeClass) {
        super(name);
        if (Modifier.isAbstract(compileTimeClass.getModifiers())) {
            throw new IllegalArgumentException("Can't use class '" + compileTimeClass.getSimpleName()
                    + "' as compile-time class for " + name + "; the class is abstract.");
        }
        this.compileTimeClass = compileTimeClass;
    }

    public PrismContainer(QName name, Class<C> compileTimeClass, PrismContext prismContext) {
        this(name, compileTimeClass);
        this.prismContext = prismContext;
        if (prismContext != null) {
            try {
                prismContext.adopt(this);
            } catch (SchemaException e) {
                throw new SystemException("Schema exception when adopting freshly created PrismContainer: " + this);
            }
        }
    }

    public PrismContainer(QName name, PrismContainerDefinition<C> definition, PrismContext prismContext) {
        super(name, definition, prismContext);
    }

    public Class<C> getCompileTimeClass() {
        if (this.compileTimeClass != null) {
            return compileTimeClass;
        }
        if (getDefinition() != null) {
            return getDefinition().getCompileTimeClass();
        }
        return null;
    }

    /**
    * Returns true if this object can represent specified compile-time class.
    * I.e. this object can be presented in the compile-time form that is an
    * instance of a specified class.
    */
    public boolean canRepresent(Class<?> compileTimeClass) {
        return (compileTimeClass.isAssignableFrom(getCompileTimeClass()));
    }

    @NotNull
    @Override
    public Collection<C> getRealValues() {
        List<C> realValues = new ArrayList<>(getValues().size());
        for (PrismContainerValue<C> value : getValues()) {
            realValues.add(value.asContainerable());
        }

        return realValues;

    }

    @Override
    public C getRealValue() {
        if (getValue() == null) {
            return null;
        }
        return getValue().asContainerable();
    }

    public PrismContainerValue<C> getOrCreateValue() {
        if (getValues().size() == 0) {
            return createNewValue();
        } else {
            return getValue();
        }
    }

    public PrismContainerValue<C> getValue() {
        if (getValues().size() == 1) {
            return getValues().get(0);
        }
        if (getValues().size() > 1) {
            throw new IllegalStateException(
                    "Attempt to get single value from a multivalued container " + getElementName());
        }
        // We are not sure about multiplicity if there is no definition or the definition is dynamic
        // TODO why testing for isDynamic? consider Item.isSingleValue (we already removed this condition from there); see MID-3922
        if (getDefinition() != null && !getDefinition().isDynamic()) {
            if (getDefinition().isSingleValue()) {
                // Insert first empty value. This simulates empty single-valued container. It the container exists
                // it is clear that it has at least one value (and that value is empty).
                PrismContainerValue<C> pValue = new PrismContainerValue<>(null, null, this, null, null,
                        prismContext);
                try {
                    add(pValue);
                } catch (SchemaException e) {
                    // This should not happen
                    throw new SystemException("Internal Error: " + e.getMessage(), e);
                }
                return pValue;
            } else {
                throw new IllegalStateException(
                        "Attempt to get single value from a multivalued container " + getElementName());
            }
        } else {
            // Insert first empty value. This simulates empty single-valued container. It the container exists
            // it is clear that it has at least one value (and that value is empty).
            PrismContainerValue<C> pValue = new PrismContainerValue<>(null, null, this, null, null, prismContext);
            try {
                add(pValue);
            } catch (SchemaException e) {
                // This should not happen
                throw new SystemException("Internal Error: " + e.getMessage(), e);
            }
            return pValue;
        }
    }

    public void setValue(@NotNull PrismContainerValue<C> value) throws SchemaException {
        checkMutability();
        if (getDefinition() != null) {
            if (getDefinition().isSingleValue()) {
                clear();
                add(value);
            } else {
                throw new IllegalStateException(
                        "Attempt to set single value to a multivalued container " + getElementName());
            }
        } else {
            clear();
            add(value);
        }
    }

    @Override
    public boolean add(@NotNull PrismContainerValue newValue, boolean checkUniqueness) throws SchemaException {
        checkMutability();
        // when a context-less item is added to a contextful container, it is automatically adopted
        if (newValue.getPrismContext() == null && this.prismContext != null) {
            prismContext.adopt(newValue);
        }
        if (newValue.getId() != null) {
            for (PrismContainerValue existingValue : getValues()) {
                if (existingValue.getId() != null && existingValue.getId().equals(newValue.getId())) {
                    throw new IllegalStateException(
                            "Attempt to add a container value with an id that already exists: " + newValue.getId());
                }
            }
        }
        return super.add(newValue, checkUniqueness);
    }

    @Override
    public PrismContainerValue<C> getPreviousValue(PrismValue value) {
        return (PrismContainerValue<C>) super.getPreviousValue(value);
    }

    @Override
    public PrismContainerValue<C> getNextValue(PrismValue value) {
        return (PrismContainerValue<C>) super.getNextValue(value);
    }

    private boolean canAssumeSingleValue() {
        if (getDefinition() != null) {
            return getDefinition().isSingleValue();
        } else {
            if (getValues().size() <= 1) {
                return true;
            } else {
                return false;
            }
        }
    }

    public PrismContainerValue<C> getValue(Long id) {
        for (PrismContainerValue<C> pval : getValues()) {
            if ((id == null && pval.getId() == null) || id.equals(pval.getId())) {
                return pval;
            }
        }
        return null;
    }

    public <T> void setPropertyRealValue(QName propertyName, T realValue) throws SchemaException {
        checkMutability();
        PrismProperty<T> property = findOrCreateProperty(propertyName);
        property.setRealValue(realValue);
    }

    public <T> void setPropertyRealValues(QName propertyName, T... realValues) throws SchemaException {
        checkMutability();
        PrismProperty<T> property = findOrCreateProperty(propertyName);
        property.setRealValues(realValues);
    }

    public <T> T getPropertyRealValue(QName propertyName, Class<T> type) {
        return getPropertyRealValue(new ItemPath(propertyName), type);
    }

    public <T> T getPropertyRealValue(ItemPath propertyPath, Class<T> type) {
        PrismProperty<T> property = findProperty(propertyPath);
        if (property == null) {
            return null;
        }
        return property.getRealValue(type);
    }

    /**
     * Convenience method. Works only on single-valued containers.
     */
    public void add(Item<?, ?> item) throws SchemaException {
        checkMutability();
        getValue().add(item);
    }

    public PrismContainerValue<C> createNewValue() {
        checkMutability();
        PrismContainerValue<C> pValue = new PrismContainerValue<>(prismContext);
        try {
            // No need to check uniqueness, we know that this value is new and therefore
            // it will change anyway and therefore the check is pointless.
            // However, the check is expensive (especially if there are many values).
            // So we are happy to avoid it.
            add(pValue, false);
        } catch (SchemaException e) {
            // This should not happen
            throw new SystemException("Internal Error: " + e.getMessage(), e);
        }
        return pValue;
    }

    public void mergeValues(PrismContainer<C> other) throws SchemaException {
        mergeValues(other.getValues());
    }

    public void mergeValues(Collection<PrismContainerValue<C>> otherValues) throws SchemaException {
        for (PrismContainerValue<C> otherValue : otherValues) {
            mergeValue(otherValue);
        }
    }

    public void mergeValue(PrismContainerValue<C> otherValue) throws SchemaException {
        checkMutability();
        Iterator<PrismContainerValue<C>> iterator = getValues().iterator();
        while (iterator.hasNext()) {
            PrismContainerValue<C> thisValue = iterator.next();
            if (thisValue.equals(otherValue)) {
                // Same values, nothing to merge
                return;
            }
            if (thisValue.getId() != null && thisValue.getId().equals(otherValue.getId())) {
                // Different value but same id. New value overwrites.
                iterator.remove();
            }
        }
        PrismContainerValue<C> clonedOtherValue = otherValue.clone();
        if (getDefinition() != null) {
            clonedOtherValue.applyDefinition(getDefinition());
        }
        add(clonedOtherValue);
    }

    /**
      * Remove all empty values
      */
    public void trim() {
        Iterator<PrismContainerValue<C>> iterator = getValues().iterator();
        while (iterator.hasNext()) {
            PrismContainerValue<C> pval = iterator.next();
            if (pval.isEmpty()) {
                checkMutability();
                iterator.remove();
            }
        }
    }

    /**
     * Returns applicable property container definition.
     * <p>
     * May return null if no definition is applicable or the definition is not
     * know.
     *
     * @return applicable property container definition
     */
    public PrismContainerDefinition<C> getDefinition() {
        return (PrismContainerDefinition<C>) definition;
    }

    /**
     * Sets applicable property container definition.
     *
     * @param definition the definition to set
     */
    public void setDefinition(PrismContainerDefinition<C> definition) {
        checkMutability();
        if (definition != null) {
            for (PrismContainerValue<C> value : getValues()) {
                // TODO reconsider this - sometimes we want to change CTDs, sometimes not
                boolean safeToOverwrite = value.getComplexTypeDefinition() == null || this.definition == null // TODO highly dangerous (the definition might be simply unknown)
                        || this.definition.getComplexTypeDefinition() == null
                        || this.definition.getComplexTypeDefinition().getTypeName()
                                .equals(value.getComplexTypeDefinition().getTypeName());
                if (safeToOverwrite) {
                    value.replaceComplexTypeDefinition(definition.getComplexTypeDefinition());
                }
            }
        }
        this.definition = definition;
    }

    @Override
    public void applyDefinition(PrismContainerDefinition<C> definition) throws SchemaException {
        checkMutability();
        if (definition == null) {
            return;
        }
        this.compileTimeClass = definition.getCompileTimeClass();
        super.applyDefinition(definition);
    }

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

    /**
     * Returns true if the object and all contained prisms have proper definition.
     */
    @Override
    public boolean hasCompleteDefinition() {
        if (getDefinition() == null) {
            return false;
        }
        for (PrismContainerValue<C> cval : getValues()) {
            if (!cval.hasCompleteDefinition()) {
                return false;
            }
        }
        return true;
    }

    @Override
    public Object find(ItemPath path) {
        if (path == null || path.isEmpty()) {
            return this;
        }

        IdItemPathSegment idSegment = ItemPath.getFirstIdSegment(path);
        PrismContainerValue<C> cval = findValue(idSegment);
        if (cval == null) {
            return null;
        }
        // descent to the correct value
        ItemPath rest = ItemPath.pathRestStartingWithName(path);
        return cval.find(rest);
    }

    @Override
    public <IV extends PrismValue, ID extends ItemDefinition> PartiallyResolvedItem<IV, ID> findPartial(
            ItemPath path) {
        if (path == null || path.isEmpty()) {
            return new PartiallyResolvedItem<>((Item<IV, ID>) this, null);
        }

        IdItemPathSegment idSegment = ItemPath.getFirstIdSegment(path);
        PrismContainerValue<C> cval = findValue(idSegment);
        if (cval == null) {
            return null;
        }
        // descent to the correct value
        ItemPath rest = ItemPath.pathRestStartingWithName(path);
        return cval.findPartial(rest);
    }

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

    <IV extends PrismValue, ID extends ItemDefinition, I extends Item<IV, ID>> I findCreateItem(QName itemQName,
            Class<I> type, boolean create) throws SchemaException {
        return getValue().findCreateItem(itemQName, type, null, create);
    }

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

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

    public boolean containsItem(ItemPath itemPath, boolean acceptEmptyItem) throws SchemaException {
        if (itemPath == null || itemPath.isEmpty()) {
            throw new IllegalArgumentException("Empty path specified");
        }

        ItemPath rest = ItemPath.pathRestStartingWithName(itemPath);
        for (PrismContainerValue<C> value : getValues()) {
            if (value.containsItem(rest, acceptEmptyItem)) {
                return true;
            }
        }

        return false;
    }

    <IV extends PrismValue, ID extends ItemDefinition, I extends Item<IV, ID>> I findCreateItem(ItemPath itemPath,
            Class<I> type, ID itemDefinition, boolean create) throws SchemaException {
        if (itemPath == null || itemPath.isEmpty()) {
            throw new IllegalArgumentException("Empty path specified");
        }

        IdItemPathSegment idSegment = ItemPath.getFirstIdSegment(itemPath);
        PrismContainerValue<C> cval = findValue(idSegment);
        if (cval == null) {
            return null;
        }
        // descent to the correct value
        ItemPath rest = ItemPath.pathRestStartingWithName(itemPath);
        return cval.findCreateItem(rest, type, itemDefinition, create);
    }

    public PrismContainerValue<C> findValue(long id) {
        for (PrismContainerValue<C> pval : getValues()) {
            if (id == pval.getId()) {
                return pval;
            }
        }
        return null;
    }

    private PrismContainerValue<C> findValue(IdItemPathSegment idSegment) {
        Long id = null;
        if (idSegment != null) {
            id = idSegment.getId();
        }
        // Otherwise descent to the correct value
        if (id == null) {
            if (canAssumeSingleValue()) {
                return getValue();
            } else {
                throw new IllegalArgumentException(
                        "Attempt to get segment without an ID from a multi-valued container " + getElementName());
            }
        } else {
            for (PrismContainerValue<C> pval : getValues()) {
                if (id.equals(pval.getId())) {
                    return pval;
                }
            }
            return null;
        }
    }

    public <T extends Containerable> PrismContainer<T> findContainer(ItemPath path) {
        return findItem(path, PrismContainer.class);
    }

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

    public <I extends Item<?, ?>> List<I> getItems(Class<I> type) {
        return getValue().getItems(type);
    }

    @SuppressWarnings("unchecked")
    public List<PrismContainer<?>> getContainers() {
        return (List) getItems(PrismContainer.class);
    }

    public <T> PrismProperty<T> findProperty(ItemPath path) {
        return findItem(path, PrismProperty.class);
    }

    public <T> PrismProperty<T> findProperty(QName propertyQName) {
        return findItem(propertyQName, PrismProperty.class);
    }

    public PrismReference findReference(ItemPath path) {
        return findItem(path, PrismReference.class);
    }

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

    public PrismReference findReferenceByCompositeObjectElementName(QName elementName) {
        return getValue().findReferenceByCompositeObjectElementName(elementName);
    }

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

    // The "definition" parameter provides definition of item to create, in case that the container does not have
    // the definition (e.g. in case of "extension" containers)
    public <IV extends PrismValue, ID extends ItemDefinition, I extends Item<IV, ID>> I findOrCreateItem(
            ItemPath containerPath, Class<I> type, ID definition) throws SchemaException {
        if (PrismObject.class.isAssignableFrom(type)) {
            throw new IllegalArgumentException("It makes no sense to find object in a container (class)");
        }
        if (definition instanceof PrismObjectDefinition<?>) {
            throw new IllegalArgumentException("It makes no sense to find object in a container (definition)");
        }
        return findCreateItem(containerPath, type, definition, true);
    }

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

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

    public <T> PrismProperty<T> findOrCreateProperty(ItemPath propertyPath) throws SchemaException {
        return findCreateItem(propertyPath, PrismProperty.class, null, true);
    }

    public <T> PrismProperty<T> findOrCreateProperty(QName propertyName) throws SchemaException {
        return findCreateItem(propertyName, PrismProperty.class, true);
    }

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

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

    /**
     * Convenience method. Works only on single-valued containers.
     */
    public void remove(Item<?, ?> item) {
        getValue().remove(item);
    }

    public void removeProperty(QName propertyQName) {
        removeItem(new ItemPath(propertyQName), PrismProperty.class);
    }

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

    public void removeContainer(QName containerQName) {
        removeItem(new ItemPath(containerQName), PrismContainer.class);
    }

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

    public void removeReference(QName referenceQName) {
        removeItem(new ItemPath(referenceQName), PrismReference.class);
    }

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

    public <IV extends PrismValue, ID extends ItemDefinition, I extends Item<IV, ID>> void removeItem(ItemPath path,
            Class<I> itemType) {
        IdItemPathSegment idSegment = ItemPath.getFirstIdSegment(path);
        PrismContainerValue<C> cval = findValue(idSegment);
        if (cval == null) {
            return;
        }
        cval.removeItem(ItemPath.pathRestStartingWithName(path.rest()), itemType);
    }

    // Expects that the "self" path segment is NOT included in the basePath
    // is this method used anywhere?
    //    void addItemPathsToList(ItemPath basePath, Collection<ItemPath> list) {
    //       boolean addIds = true;
    //       if (getDefinition() != null) {
    //          if (getDefinition().isSingleValue()) {
    //             addIds = false;
    //          }
    //       }
    //       for (PrismContainerValue<V> pval: getValues()) {
    //          ItemPath subpath = null;
    //          ItemPathSegment segment = null;
    //          if (addIds) {
    //             subpath = basePath.subPath(new IdItemPathSegment(pval.getId())).subPath(new NameItemPathSegment(getElementName()));
    //          } else {
    //             subpath = basePath.subPath(new NameItemPathSegment(getElementName()));
    //          }
    //          pval.addItemPathsToList(subpath, list);
    //       }
    //    }

    @Override
    public ContainerDelta<C> createDelta() {
        return new ContainerDelta<>(getPath(), getDefinition(), getPrismContext());
    }

    @Override
    public ContainerDelta<C> createDelta(ItemPath path) {
        return new ContainerDelta<>(path, getDefinition(), getPrismContext());
    }

    public boolean isEmpty() {
        for (PrismContainerValue<C> pval : getValues()) {
            if (!pval.isEmpty()) {
                return false;
            }
        }
        return true;
    }

    @Override
    protected void checkDefinition(PrismContainerDefinition<C> def) {
        if (def == null) {
            throw new IllegalArgumentException("Null definition cannot be applied to container " + this);
        }
    }

    @Override
    public void checkConsistenceInternal(Itemable rootItem, boolean requireDefinitions, boolean prohibitRaw,
            ConsistencyCheckScope scope) {
        checkIds();
        if (scope.isThorough()) {
            // Containers that are from run-time schema cannot have compile-time class.
            if (getDefinition() != null && !getDefinition().isRuntimeSchema()) {
                if (getCompileTimeClass() == null) {
                    throw new IllegalStateException(
                            "No compile-time class in " + this + " (" + getPath() + " in " + rootItem + ")");
                }
            }
        }
        super.checkConsistenceInternal(rootItem, requireDefinitions, prohibitRaw, scope);
    }

    private void checkIds() {
        Set<Long> oidsUsed = new HashSet<>();
        for (PrismContainerValue value : getValues()) {
            Long id = value.getId();
            if (id != null) {
                if (oidsUsed.contains(id)) {
                    throw new IllegalArgumentException(
                            "There are more container values with the id of " + id + " in " + getElementName());
                } else {
                    oidsUsed.add(id);
                }
            }
        }
    }

    @Override
    public void assertDefinitions(boolean tolarateRaw, String sourceDescription) throws SchemaException {
        super.assertDefinitions(tolarateRaw, sourceDescription);
        for (PrismContainerValue<C> val : getValues()) {
            val.assertDefinitions(tolarateRaw, this.toString() + " in " + sourceDescription);
        }
    }

    public ContainerDelta<C> diff(PrismContainer<C> other) {
        return (ContainerDelta<C>) super.diff(other);
    }

    public ContainerDelta<C> diff(PrismContainer<C> other, boolean ignoreMetadata, boolean isLiteral) {
        return (ContainerDelta<C>) super.diff(other, true, false);
    }

    public List<? extends ItemDelta> diffModifications(PrismContainer<C> other) {
        return diffModifications(other, true, false);
    }

    public List<? extends ItemDelta> diffModifications(PrismContainer<C> other, boolean ignoreMetadata,
            boolean isLiteral) {
        List<? extends ItemDelta> itemDeltas = new ArrayList<>();
        diffInternal(other, itemDeltas, ignoreMetadata, isLiteral);
        return itemDeltas;
    }

    @Override
    public PrismContainer<C> clone() {
        return cloneComplex(CloneStrategy.LITERAL);
    }

    @Override
    public PrismContainer<C> cloneComplex(CloneStrategy strategy) {
        PrismContainer<C> clone = new PrismContainer<>(getElementName(), getDefinition(), prismContext);
        copyValues(strategy, clone);
        return clone;
    }

    protected void copyValues(CloneStrategy strategy, PrismContainer<C> clone) {
        super.copyValues(strategy, clone);
        clone.compileTimeClass = this.compileTimeClass;
        for (PrismContainerValue<C> pval : getValues()) {
            try {
                clone.add(pval.cloneComplex(strategy));
            } catch (SchemaException e) {
                // This should not happen
                throw new SystemException("Internal Error: " + e.getMessage(), e);
            }
        }
    }

    public PrismContainerDefinition<C> deepCloneDefinition(boolean ultraDeep,
            Consumer<ItemDefinition> postCloneAction) {
        PrismContainerDefinition<C> clonedDef = (PrismContainerDefinition<C>) getDefinition().deepClone(ultraDeep,
                postCloneAction);
        propagateDeepCloneDefinition(ultraDeep, clonedDef, postCloneAction);
        setDefinition(clonedDef);
        return clonedDef;
    }

    @Override
    protected void propagateDeepCloneDefinition(boolean ultraDeep, PrismContainerDefinition<C> clonedDef,
            Consumer<ItemDefinition> postCloneAction) {
        for (PrismContainerValue<C> cval : getValues()) {
            cval.deepCloneDefinition(ultraDeep, clonedDef, postCloneAction);
        }
    }

    @Override
    public boolean containsEquivalentValue(PrismContainerValue<C> value) {
        if (value.isIdOnly()) {
            PrismContainerValue<C> myValue = findValue(value.getId());
            return myValue != null;
        } else if (value.getId() == null) {
            return super.contains(value, true);
        } else {
            return super.contains(value, false);
        }
    }

    @Override
    public boolean containsEquivalentValue(PrismContainerValue<C> value,
            Comparator<PrismContainerValue<C>> comparator) {
        if (value.isIdOnly()) {
            PrismContainerValue<C> myValue = findValue(value.getId());
            return myValue != null;
        } else if (value.getId() == null) {
            return super.contains(value, true, comparator);
        } else {
            return super.contains(value, false, comparator);
        }
    }

    @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 {
            IdItemPathSegment idSegment = ItemPath.getFirstIdSegment(path);
            ItemPath rest = ItemPath.pathRestStartingWithName(path);
            if (idSegment == null || idSegment.isWildcard()) {
                // visit all values
                for (PrismContainerValue<C> cval : getValues()) {
                    cval.accept(visitor, rest, recursive);
                }
            } else {
                PrismContainerValue<C> cval = findValue(idSegment);
                if (cval != null) {
                    cval.accept(visitor, rest, recursive);
                }
            }
        }
    }

    /**
     * Note: hashcode and equals compare the objects in the "java way". That means the objects must be
     * almost precisely equal to match (e.g. including source demarcation in values and other "annotations").
     * For a method that compares the "meaningful" parts of the objects see equivalent().
     */
    @Override
    public int hashCode() {
        int result = super.hashCode();
        return result;
    }

    /**
     * Note: hashcode and equals compare the objects in the "java way". That means the objects must be
     * almost precisely equal to match (e.g. including source demarcation in values and other "annotations").
     * For a method that compares the "meaningful" parts of the objects see equivalent().
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (!super.equals(obj))
            return false;
        if (getClass() != obj.getClass())
            return false;
        return true;
    }

    /**
     * This method ignores some part of the object during comparison (e.g. source demarcation in values)
     * These methods compare the "meaningful" parts of the objects.
     */
    public boolean equivalent(Object obj) {
        // Alibistic implementation for now. But should work well.
        if (this == obj)
            return true;
        if (getClass() != obj.getClass())
            return false;
        PrismContainer other = (PrismContainer) obj;

        //todo probably better comparation (ignore some part of object)
        return equals(other);
        //        ObjectDelta<T> delta = compareTo(other);
        //        return delta.isEmpty();
    }

    @Override
    public String toString() {
        return getDebugDumpClassName() + "(" + getElementName() + "):" + getValues();
    }

    @Override
    public String debugDump(int indent) {
        StringBuilder sb = new StringBuilder();
        DebugUtil.indentDebugDump(sb, indent);
        if (DebugUtil.isDetailedDebugDump()) {
            sb.append(getDebugDumpClassName()).append(": ");
        }
        sb.append(DebugUtil.formatElementName(getElementName()));
        sb.append(": ");
        appendDebugDumpSuffix(sb);
        PrismContainerDefinition<C> def = getDefinition();
        if (DebugUtil.isDetailedDebugDump()) {
            if (def != null) {
                sb.append(" def(");
                sb.append(PrettyPrinter.prettyPrint(def.getTypeName()));
                if (def.isRuntimeSchema()) {
                    sb.append(",runtime");
                }
                if (def.isDynamic()) {
                    sb.append(",dyn");
                }
                if (def.isElaborate()) {
                    sb.append(",elaborate");
                }
                sb.append(")");
            }
        } else {
            if (def != null) {
                if (def.isElaborate()) {
                    sb.append(",elaborate");
                }
            }
        }
        Iterator<PrismContainerValue<C>> i = getValues().iterator();
        if (i.hasNext()) {
            sb.append("\n");
        }
        while (i.hasNext()) {
            PrismContainerValue<C> pval = i.next();
            sb.append(pval.debugDump(indent + 1));
            if (i.hasNext()) {
                sb.append("\n");
            }
        }
        return sb.toString();
    }

    public static <V extends Containerable> PrismContainer<V> newInstance(PrismContext prismContext, QName type)
            throws SchemaException {
        PrismContainerDefinition<V> definition = prismContext.getSchemaRegistry()
                .findContainerDefinitionByType(type);
        if (definition == null) {
            throw new SchemaException("Definition for " + type + " couldn't be found");
        }
        return definition.instantiate();
    }

    /**
     * Return a human readable name of this class suitable for logs.
     */
    @Override
    protected String getDebugDumpClassName() {
        return "PC";
    }

    public static <V extends PrismContainerValue> void createParentIfNeeded(V value, ItemDefinition definition)
            throws SchemaException {
        if (value.getParent() != null) {
            return;
        }
        if (!(definition instanceof PrismContainerDefinition)) {
            throw new SchemaException("Missing or invalid definition for a PrismContainer: " + definition);
        }
        PrismContainer<?> rv = (PrismContainer) definition.instantiate();
        rv.add(value);
    }

    /**
     * Optimizes (trims) definition tree by removing any definitions not corresponding to items in this container.
     * Works recursively by sub-containers of this one.
     * USE WITH CARE. Make sure the definitions are not shared by other objects!
     */
    public void trimDefinitionTree(Collection<ItemPath> alwaysKeep) {
        PrismContainerDefinition<C> def = getDefinition();
        if (def == null || def.getComplexTypeDefinition() == null) {
            return;
        }
        Set<ItemPath> allPaths = getAllItemPaths(alwaysKeep);
        def.getComplexTypeDefinition().trimTo(allPaths);
        values.forEach(v -> v.trimItemsDefinitionsTrees(alwaysKeep));
    }

    // TODO implement more efficiently
    private Set<ItemPath> getAllItemPaths(Collection<ItemPath> alwaysKeep) {
        Set<ItemPath> paths = new HashSet<>();
        paths.addAll(CollectionUtils.emptyIfNull(alwaysKeep));
        this.accept(v -> {
            if (v instanceof PrismValue) {
                paths.add(((PrismValue) v).getPath());
            }
        });
        return paths;
    }
}