com.evolveum.midpoint.prism.delta.ItemDelta.java Source code

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2010-2014 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.delta;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;

import javax.xml.namespace.QName;

import com.evolveum.midpoint.prism.Containerable;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.Itemable;
import com.evolveum.midpoint.prism.Objectable;
import com.evolveum.midpoint.prism.PathVisitable;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismContainerValue;
import com.evolveum.midpoint.prism.PrismContext;
import com.evolveum.midpoint.prism.PrismObject;
import com.evolveum.midpoint.prism.PrismObjectDefinition;
import com.evolveum.midpoint.prism.PrismProperty;
import com.evolveum.midpoint.prism.PrismValue;
import com.evolveum.midpoint.prism.Visitable;
import com.evolveum.midpoint.prism.Visitor;
import com.evolveum.midpoint.prism.parser.XPathHolder;
import com.evolveum.midpoint.prism.path.IdItemPathSegment;
import com.evolveum.midpoint.prism.path.ItemPath;
import com.evolveum.midpoint.prism.path.ItemPath.CompareResult;
import com.evolveum.midpoint.prism.path.ItemPathSegment;
import com.evolveum.midpoint.prism.path.NameItemPathSegment;
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.exception.SchemaException;
import com.evolveum.prism.xml.ns._public.types_3.ItemPathType;

import org.apache.commons.lang.Validate;

/**
 * @author Radovan Semancik
 * 
 */
public abstract class ItemDelta<V extends PrismValue>
        implements Itemable, DebugDumpable, Visitable, PathVisitable, Serializable {

    /**
     * Name of the property
     */
    protected QName elementName;
    /**
     * Parent path of the property (path to the property container)
     */
    protected ItemPath parentPath;
    protected ItemDefinition definition;

    protected Collection<V> valuesToReplace = null;
    protected Collection<V> valuesToAdd = null;
    protected Collection<V> valuesToDelete = null;

    transient private PrismContext prismContext;

    protected ItemDelta(ItemDefinition itemDefinition, PrismContext prismContext) {
        if (itemDefinition == null) {
            throw new IllegalArgumentException("Attempt to create item delta without a definition");
        }
        //checkPrismContext(prismContext, itemDefinition);
        this.prismContext = prismContext;
        this.elementName = itemDefinition.getName();
        this.parentPath = new ItemPath();
        this.definition = itemDefinition;
    }

    protected ItemDelta(QName elementName, ItemDefinition itemDefinition, PrismContext prismContext) {
        //checkPrismContext(prismContext, itemDefinition);
        this.prismContext = prismContext;
        this.elementName = elementName;
        this.parentPath = new ItemPath();
        this.definition = itemDefinition;
    }

    protected ItemDelta(ItemPath parentPath, QName elementName, ItemDefinition itemDefinition,
            PrismContext prismContext) {
        //checkPrismContext(prismContext, itemDefinition);
        this.prismContext = prismContext;
        this.elementName = elementName;
        this.parentPath = parentPath;
        this.definition = itemDefinition;
    }

    protected ItemDelta(ItemPath path, ItemDefinition itemDefinition, PrismContext prismContext) {
        //checkPrismContext(prismContext, itemDefinition);
        this.prismContext = prismContext;

        if (path == null) {
            throw new IllegalArgumentException("Null path specified while creating item delta");
        }
        if (path.isEmpty()) {
            this.elementName = null;
        } else {
            ItemPathSegment last = path.last();
            if (!(last instanceof NameItemPathSegment)) {
                throw new IllegalArgumentException(
                        "Invalid delta path " + path + ". Delta path must always point to item, not to value");
            }
            this.elementName = ((NameItemPathSegment) last).getName();
            this.parentPath = path.allExceptLast();
        }
        this.definition = itemDefinition;
    }

    // currently unused; we allow deltas without prismContext, except for some operations (e.g. serialization to ItemDeltaType)
    //    private void checkPrismContext(PrismContext prismContext, ItemDefinition itemDefinition) {
    //        if (prismContext == null) {
    //            throw new IllegalStateException("No prismContext in delta for " + itemDefinition);
    //        }
    //    }

    public QName getElementName() {
        return elementName;
    }

    public void setElementName(QName elementName) {
        this.elementName = elementName;
    }

    public ItemPath getParentPath() {
        return parentPath;
    }

    public void setParentPath(ItemPath parentPath) {
        this.parentPath = parentPath;
    }

    @Override
    public ItemPath getPath() {
        return getParentPath().subPath(elementName);
    }

    public ItemDefinition getDefinition() {
        return definition;
    }

    public void setDefinition(ItemDefinition definition) {
        this.definition = definition;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
        if (getValuesToAdd() != null) {
            for (V pval : getValuesToAdd()) {
                pval.accept(visitor);
            }
        }
        if (getValuesToDelete() != null) {
            for (V pval : getValuesToDelete()) {
                pval.accept(visitor);
            }
        }
        if (getValuesToReplace() != null) {
            for (V pval : getValuesToReplace()) {
                pval.accept(visitor);
            }
        }
    }

    @Override
    public void accept(Visitor visitor, ItemPath path, boolean recursive) {
        if (path == null || path.isEmpty()) {
            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
                if (getValuesToAdd() != null) {
                    for (V pval : getValuesToAdd()) {
                        pval.accept(visitor, rest, recursive);
                    }
                }
                if (getValuesToDelete() != null) {
                    for (V pval : getValuesToDelete()) {
                        pval.accept(visitor, rest, recursive);
                    }
                }
                if (getValuesToReplace() != null) {
                    for (V pval : getValuesToReplace()) {
                        pval.accept(visitor, rest, recursive);
                    }
                }
            } else {
                Long id = idSegment.getId();
                acceptSet(getValuesToAdd(), id, visitor, rest, recursive);
                acceptSet(getValuesToDelete(), id, visitor, rest, recursive);
                acceptSet(getValuesToReplace(), id, visitor, rest, recursive);
            }
        }
    }

    private void acceptSet(Collection<V> set, Long id, Visitor visitor, ItemPath rest, boolean recursive) {
        if (set == null) {
            return;
        }
        for (V pval : set) {
            if (pval instanceof PrismContainerValue<?>) {
                PrismContainerValue<?> cval = (PrismContainerValue<?>) pval;
                if (id == null || id.equals(cval.getId())) {
                    pval.accept(visitor, rest, recursive);
                }
            } else {
                throw new IllegalArgumentException("Attempt to fit container id to " + pval.getClass());
            }
        }
    }

    public void applyDefinition(ItemDefinition definition) throws SchemaException {
        this.definition = definition;
        if (getValuesToAdd() != null) {
            for (V pval : getValuesToAdd()) {
                pval.applyDefinition(definition);
            }
        }
        if (getValuesToDelete() != null) {
            for (V pval : getValuesToDelete()) {
                pval.applyDefinition(definition);
            }
        }
        if (getValuesToReplace() != null) {
            for (V pval : getValuesToReplace()) {
                pval.applyDefinition(definition);
            }
        }
    }

    public static void applyDefinition(Collection<? extends ItemDelta> deltas, PrismObjectDefinition definition)
            throws SchemaException {
        for (ItemDelta<?> itemDelta : deltas) {
            ItemPath path = itemDelta.getPath();
            ItemDefinition itemDefinition = definition.findItemDefinition(path, ItemDefinition.class);
            if (itemDefinition == null) {
                throw new SchemaException(
                        "Object type " + definition.getTypeName() + " doesn't contain definition for path "
                                + new XPathHolder(path).getXPathWithDeclarations(false));
            }
            itemDelta.applyDefinition(itemDefinition);
        }
    }

    public boolean hasCompleteDefinition() {
        return getDefinition() != null;
    }

    public PrismContext getPrismContext() {
        return prismContext;
    }

    public abstract Class<? extends Item> getItemClass();

    public Collection<V> getValuesToAdd() {
        return valuesToAdd;
    }

    public void clearValuesToAdd() {
        valuesToAdd = null;
    }

    public Collection<V> getValuesToDelete() {
        return valuesToDelete;
    }

    public void clearValuesToDelete() {
        valuesToDelete = null;
    }

    public Collection<V> getValuesToReplace() {
        return valuesToReplace;
    }

    public void clearValuesToReplace() {
        valuesToReplace = null;
    }

    public void addValuesToAdd(Collection<V> newValues) {
        for (V val : newValues) {
            addValueToAdd(val);
        }
    }

    public void addValuesToAdd(V... newValues) {
        for (V val : newValues) {
            addValueToAdd(val);
        }
    }

    public void addValueToAdd(V newValue) {
        if (valuesToReplace != null) {
            throw new IllegalStateException("Delta " + this + " already has values to replace (" + valuesToReplace
                    + "), attempt to add value (" + newValue + ") is an error");
        }
        if (valuesToAdd == null) {
            valuesToAdd = newValueCollection();
        }
        if (PrismValue.containsRealValue(valuesToAdd, newValue)) {
            return;
        }
        valuesToAdd.add(newValue);
        newValue.setParent(this);
        newValue.recompute();
    }

    public boolean removeValueToAdd(V valueToRemove) {
        return removeValue(valueToRemove, valuesToAdd);
    }

    public boolean removeValueToDelete(V valueToRemove) {
        return removeValue(valueToRemove, valuesToDelete);
    }

    public boolean removeValueToReplace(V valueToRemove) {
        return removeValue(valueToRemove, valuesToReplace);
    }

    private boolean removeValue(V valueToRemove, Collection<V> set) {
        boolean removed = false;
        if (set == null) {
            return removed;
        }
        Iterator<V> valuesToReplaceIterator = set.iterator();
        while (valuesToReplaceIterator.hasNext()) {
            V valueToReplace = valuesToReplaceIterator.next();
            if (valueToReplace.equalsRealValue(valueToRemove)) {
                valuesToReplaceIterator.remove();
                removed = true;
            }
        }
        return removed;
    }

    public void mergeValuesToAdd(Collection<V> newValues) {
        for (V val : newValues) {
            mergeValueToAdd(val);
        }
    }

    public void mergeValuesToAdd(V[] newValues) {
        for (V val : newValues) {
            mergeValueToAdd(val);
        }
    }

    public void mergeValueToAdd(V newValue) {
        if (valuesToReplace != null) {
            if (!PrismValue.containsRealValue(valuesToReplace, newValue)) {
                valuesToReplace.add(newValue);
                newValue.setParent(this);
            }
        } else {
            if (!removeValueToDelete(newValue)) {
                addValueToAdd(newValue);
            }
        }
    }

    public void addValuesToDelete(Collection<V> newValues) {
        for (V val : newValues) {
            addValueToDelete(val);
        }
    }

    public void addValuesToDelete(V... newValues) {
        for (V val : newValues) {
            addValueToDelete(val);
        }
    }

    public void addValueToDelete(V newValue) {
        if (valuesToReplace != null) {
            throw new IllegalStateException("Delta " + this + " already has values to replace (" + valuesToReplace
                    + "), attempt to set value to delete (" + newValue + ")");
        }
        if (valuesToDelete == null) {
            valuesToDelete = newValueCollection();
        }
        if (PrismValue.containsRealValue(valuesToDelete, newValue)) {
            return;
        }
        valuesToDelete.add(newValue);
        newValue.setParent(this);
        newValue.recompute();
    }

    public void mergeValuesToDelete(Collection<V> newValues) {
        for (V val : newValues) {
            mergeValueToDelete(val);
        }
    }

    public void mergeValuesToDelete(V[] newValues) {
        for (V val : newValues) {
            mergeValueToDelete(val);
        }
    }

    public void mergeValueToDelete(V newValue) {
        if (valuesToReplace != null) {
            removeValueToReplace(newValue);
        } else {
            if (!removeValueToAdd(newValue)) {
                addValueToDelete(newValue);
            }
        }
    }

    public void resetValuesToAdd() {
        valuesToAdd = null;
    }

    public void setValuesToReplace(Collection<V> newValues) {
        if (valuesToAdd != null) {
            throw new IllegalStateException("Delta " + this + " already has values to add (" + valuesToAdd
                    + "), attempt to set value to replace (" + newValues + ")");
        }
        if (valuesToDelete != null) {
            throw new IllegalStateException(
                    "Delta " + this + " already has values to delete, attempt to set value to replace");
        }
        if (valuesToReplace == null) {
            valuesToReplace = newValueCollection();
        } else {
            valuesToReplace.clear();
        }
        for (V val : newValues) {
            valuesToReplace.add(val);
            val.setParent(this);
            val.recompute();
        }
    }

    public void setValuesToReplace(V... newValues) {
        if (valuesToAdd != null) {
            throw new IllegalStateException(
                    "Delta " + this + " already has values to add, attempt to set value to replace");
        }
        if (valuesToDelete != null) {
            throw new IllegalStateException(
                    "Delta " + this + " already has values to delete, attempt to set value to replace");
        }
        if (valuesToReplace == null) {
            valuesToReplace = newValueCollection();
        } else {
            valuesToReplace.clear();
        }
        for (V val : newValues) {
            valuesToReplace.add(val);
            val.setParent(this);
            val.recompute();
        }
    }

    /**
     * Sets empty value to replace. This efficiently means removing all values.
     */
    public void setValueToReplace() {
        if (valuesToReplace == null) {
            valuesToReplace = newValueCollection();
        } else {
            valuesToReplace.clear();
        }
    }

    public void setValueToReplace(V newValue) {
        if (valuesToAdd != null) {
            throw new IllegalStateException(
                    "Delta " + this + " already has values to add, attempt to set value to replace");
        }
        if (valuesToDelete != null) {
            throw new IllegalStateException(
                    "Delta " + this + " already has values to delete, attempt to set value to replace");
        }
        if (valuesToReplace == null) {
            valuesToReplace = newValueCollection();
        } else {
            valuesToReplace.clear();
        }
        valuesToReplace.add(newValue);
        newValue.setParent(this);
        newValue.recompute();
    }

    public void mergeValuesToReplace(Collection<V> newValues) {
        // No matter what type the delta was before. We are just discarding all the previous
        // state as the replace that we are applying will overwrite that anyway.
        valuesToAdd = null;
        valuesToDelete = null;
        setValuesToReplace(newValues);
    }

    public void mergeValuesToReplace(V[] newValues) {
        // No matter what type the delta was before. We are just discarding all the previous
        // state as the replace that we are applying will overwrite that anyway.
        valuesToAdd = null;
        valuesToDelete = null;
        setValuesToReplace(newValues);
    }

    public void mergeValueToReplace(V newValue) {
        // No matter what type the delta was before. We are just discarding all the previous
        // state as the replace that we are applying will overwrite that anyway.
        valuesToAdd = null;
        valuesToDelete = null;
        setValueToReplace(newValue);
    }

    private Collection<V> newValueCollection() {
        return new ArrayList<V>();
    }

    public boolean isValueToAdd(V value) {
        return isValueSet(value, false, valuesToAdd);
    }

    public boolean isValueToAdd(V value, boolean ignoreMetadata) {
        return isValueSet(value, ignoreMetadata, valuesToAdd);
    }

    public boolean isValueToDelete(V value) {
        return isValueSet(value, false, valuesToDelete);
    }

    public boolean isValueToDelete(V value, boolean ignoreMetadata) {
        return isValueSet(value, ignoreMetadata, valuesToDelete);
    }

    public boolean isValueToReplace(V value) {
        return isValueSet(value, false, valuesToReplace);
    }

    public boolean isValueToReplace(V value, boolean ignoreMetadata) {
        return isValueSet(value, ignoreMetadata, valuesToReplace);
    }

    private boolean isValueSet(V value, boolean ignoreMetadata, Collection<V> set) {
        if (set == null) {
            return false;
        }
        for (V myVal : set) {
            if (myVal.equals(value, ignoreMetadata)) {
                return true;
            }
        }
        return false;
    }

    public V getAnyValue() {
        V anyValue = getAnyValue(valuesToAdd);
        if (anyValue != null) {
            return anyValue;
        }
        anyValue = getAnyValue(valuesToDelete);
        if (anyValue != null) {
            return anyValue;
        }
        anyValue = getAnyValue(valuesToReplace);
        if (anyValue != null) {
            return anyValue;
        }
        return null;
    }

    private V getAnyValue(Collection<V> set) {
        if (set == null || set.isEmpty()) {
            return null;
        }
        return set.iterator().next();
    }

    public boolean isEmpty() {
        if (valuesToAdd == null && valuesToDelete == null && valuesToReplace == null) {
            return true;
        }
        return false;
    }

    public boolean addsAnyValue() {
        return hasAnyValue(valuesToAdd) || hasAnyValue(valuesToReplace);
    }

    private boolean hasAnyValue(Collection<V> set) {
        return (set != null && !set.isEmpty());
    }

    public void normalize() {
        normalize(valuesToAdd);
        normalize(valuesToDelete);
        normalize(valuesToReplace);
    }

    private void normalize(Collection<V> set) {
        if (set == null) {
            return;
        }
        Iterator<V> iterator = set.iterator();
        while (iterator.hasNext()) {
            V value = iterator.next();
            value.normalize();
            if (value.isEmpty()) {
                iterator.remove();
            }
        }
    }

    public boolean isReplace() {
        return (valuesToReplace != null);
    }

    public boolean isAdd() {
        return (valuesToAdd != null && !valuesToAdd.isEmpty());
    }

    public boolean isDelete() {
        return (valuesToDelete != null && !valuesToDelete.isEmpty());
    }

    public void clear() {
        valuesToReplace = null;
        valuesToAdd = null;
        valuesToDelete = null;
    }

    public static PropertyDelta findPropertyDelta(Collection<? extends ItemDelta> deltas, QName propertyName) {
        return findPropertyDelta(deltas, new ItemPath(propertyName));
    }

    public static PropertyDelta findPropertyDelta(Collection<? extends ItemDelta> deltas, ItemPath parentPath,
            QName propertyName) {
        return findPropertyDelta(deltas, new ItemPath(parentPath, propertyName));
    }

    public static PropertyDelta findPropertyDelta(Collection<? extends ItemDelta> deltas, ItemPath propertyPath) {
        return findItemDelta(deltas, propertyPath, PropertyDelta.class);
    }

    public static <X extends Containerable> ContainerDelta<X> findContainerDelta(
            Collection<? extends ItemDelta> deltas, ItemPath propertyPath) {
        return findItemDelta(deltas, propertyPath, ContainerDelta.class);
    }

    public static <X extends Containerable> ContainerDelta<X> findContainerDelta(
            Collection<? extends ItemDelta> deltas, QName name) {
        return findContainerDelta(deltas, new ItemPath(name));
    }

    public static <D extends ItemDelta> D findItemDelta(Collection<? extends ItemDelta> deltas,
            ItemPath propertyPath, Class<D> deltaType) {
        if (deltas == null) {
            return null;
        }
        for (ItemDelta<?> delta : deltas) {
            if (deltaType.isAssignableFrom(delta.getClass()) && delta.getPath().equivalent(propertyPath)) {
                return (D) delta;
            }
        }
        return null;
    }

    public static Collection<? extends ItemDelta<?>> findItemDeltasSubPath(
            Collection<? extends ItemDelta<?>> deltas, ItemPath itemPath) {
        Collection<ItemDelta<?>> foundDeltas = new ArrayList<ItemDelta<?>>();
        if (deltas == null) {
            return foundDeltas;
        }
        for (ItemDelta<?> delta : deltas) {
            if (itemPath.isSubPath(delta.getPath())) {
                foundDeltas.add(delta);
            }
        }
        return foundDeltas;
    }

    public static <D extends ItemDelta> D findItemDelta(Collection<? extends ItemDelta> deltas, QName itemName,
            Class<D> deltaType) {
        return findItemDelta(deltas, new ItemPath(itemName), deltaType);
    }

    public static ReferenceDelta findReferenceModification(Collection<? extends ItemDelta> deltas, QName itemName) {
        return findItemDelta(deltas, itemName, ReferenceDelta.class);
    }

    public static <D extends ItemDelta> void removeItemDelta(Collection<? extends ItemDelta> deltas,
            ItemPath propertyPath, Class<D> deltaType) {
        if (deltas == null) {
            return;
        }
        Iterator<? extends ItemDelta> deltasIterator = deltas.iterator();
        while (deltasIterator.hasNext()) {
            ItemDelta<?> delta = deltasIterator.next();
            if (deltaType.isAssignableFrom(delta.getClass()) && delta.getPath().equivalent(propertyPath)) {
                deltasIterator.remove();
            }
        }
    }

    /**
     * Filters out all delta values that are meaningless to apply. E.g. removes all values to add that the property already has,
     * removes all values to delete that the property does not have, etc. 
     */
    public ItemDelta<V> narrow(PrismObject<? extends Objectable> object) {
        Item<V> currentItem = (Item<V>) object.findItem(getPath());
        if (currentItem == null) {
            if (valuesToDelete != null) {
                ItemDelta<V> clone = clone();
                clone.valuesToDelete = null;
                return clone;
            } else {
                // Nothing to narrow
                return this;
            }
        } else {
            ItemDelta<V> clone = clone();
            if (clone.valuesToDelete != null) {
                Iterator<V> iterator = clone.valuesToDelete.iterator();
                while (iterator.hasNext()) {
                    V valueToDelete = iterator.next();
                    if (!currentItem.contains(valueToDelete, true)) {
                        iterator.remove();
                    }
                }
            }
            if (clone.valuesToAdd != null) {
                Iterator<V> iterator = clone.valuesToAdd.iterator();
                while (iterator.hasNext()) {
                    V valueToDelete = iterator.next();
                    if (currentItem.contains(valueToDelete, true)) {
                        iterator.remove();
                    }
                }
            }
            return clone;
        }
    }

    public void validate() throws SchemaException {
        validate(null);
    }

    public void validate(String contextDescription) throws SchemaException {
        if (definition == null) {
            throw new IllegalStateException("Attempt to validate delta without a definition: " + this);
        }
        if (definition.isSingleValue()) {
            if (valuesToAdd != null && valuesToAdd.size() > 1) {
                throw new SchemaException(
                        "Attempt to add " + valuesToAdd.size() + " values to a single-valued item " + getPath()
                                + (contextDescription == null ? "" : " in " + contextDescription));
            }
            if (valuesToReplace != null && valuesToReplace.size() > 1) {
                throw new SchemaException(
                        "Attempt to replace " + valuesToReplace.size() + " values to a single-valued item "
                                + getPath() + (contextDescription == null ? "" : " in " + contextDescription));
            }
        }
        if (definition.isMandatory()) {
            if (valuesToReplace != null && valuesToReplace.isEmpty()) {
                throw new SchemaException("Attempt to clear all values of a mandatory item " + getPath()
                        + (contextDescription == null ? "" : " in " + contextDescription));
            }
        }
    }

    public static void checkConsistence(Collection<? extends ItemDelta> deltas) {
        checkConsistence(deltas, false, false);
    }

    public static void checkConsistence(Collection<? extends ItemDelta> deltas, boolean requireDefinition,
            boolean prohibitRaw) {
        for (ItemDelta<?> delta : deltas) {
            delta.checkConsistence(requireDefinition, prohibitRaw);
        }
    }

    public void checkConsistence() {
        checkConsistence(false, false);
    }

    public void checkConsistence(boolean requireDefinition, boolean prohibitRaw) {
        if (parentPath == null) {
            throw new IllegalStateException("Null parent path in " + this);
        }
        if (requireDefinition && definition == null) {
            throw new IllegalStateException("Null definition in " + this);
        }
        if (valuesToReplace != null && (valuesToAdd != null || valuesToDelete != null)) {
            throw new IllegalStateException("The delta cannot be both 'replace' and 'add/delete' at the same time");
        }
        assertSetConsistence(valuesToReplace, "replace", requireDefinition, prohibitRaw);
        assertSetConsistence(valuesToAdd, "add", requireDefinition, prohibitRaw);
        assertSetConsistence(valuesToDelete, "delete", requireDefinition, prohibitRaw);
    }

    private void assertSetConsistence(Collection<V> values, String type, boolean requireDefinitions,
            boolean prohibitRaw) {
        if (values == null) {
            return;
        }
        // This may be not be 100% correct but we can tolerate it now
        // if (values.isEmpty()) {
        // throw new
        // IllegalStateException("The "+type+" values set in "+this+" is not-null but it is empty");
        // }
        for (V val : values) {
            if (val == null) {
                throw new IllegalStateException("Null value in the " + type + " values set in " + this);
            }
            if (val.getParent() != this) {
                throw new IllegalStateException("Wrong parent for " + val + " in " + type + " values set in " + this
                        + ": " + val.getParent());
            }
            val.checkConsistenceInternal(this, requireDefinitions, prohibitRaw);
        }
    }

    /**
     * Distributes the replace values of this delta to add and delete with
     * respect to provided existing values.
     */
    public void distributeReplace(Collection<V> existingValues) {
        Collection<V> origValuesToReplace = getValuesToReplace();
        // We have to clear before we distribute, otherwise there will be replace/add or replace/delete conflict
        clearValuesToReplace();
        if (existingValues != null) {
            for (V existingVal : existingValues) {
                if (!isIn(origValuesToReplace, existingVal)) {
                    addValueToDelete((V) existingVal.clone());
                }
            }
        }
        for (V replaceVal : origValuesToReplace) {
            if (!isIn(existingValues, replaceVal) && !isIn(getValuesToAdd(), replaceVal)) {
                addValueToAdd((V) replaceVal.clone());
            }
        }
    }

    private boolean isIn(Collection<V> values, V val) {
        if (values == null) {
            return false;
        }
        for (V v : values) {
            if (v.equalsRealValue(val)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Merge specified delta to this delta. This delta is assumed to be
     * chronologically earlier, delta provided in the parameter is chronilogically later.
     */
    public void merge(ItemDelta<V> deltaToMerge) {
        if (deltaToMerge.isEmpty()) {
            return;
        }
        if (deltaToMerge.valuesToReplace != null) {
            mergeValuesToReplace(PrismValue.cloneValues(deltaToMerge.valuesToReplace));
        } else {
            if (deltaToMerge.valuesToAdd != null) {
                mergeValuesToAdd(PrismValue.cloneValues(deltaToMerge.valuesToAdd));
            }
            if (deltaToMerge.valuesToDelete != null) {
                mergeValuesToDelete(PrismValue.cloneValues(deltaToMerge.valuesToDelete));
            }
        }
        // We do not want to clean up the sets during merging (e.g. in removeValue methods) because the set
        // may become empty and the a values may be added later. So just clean it up when all is done.
        removeEmptySets();
    }

    private void removeEmptySets() {
        if (valuesToReplace != null && valuesToReplace.isEmpty()) {
            valuesToReplace = null;
        }
        if (valuesToAdd != null && valuesToAdd.isEmpty()) {
            valuesToAdd = null;
        }
        if (valuesToDelete != null && valuesToDelete.isEmpty()) {
            valuesToDelete = null;
        }
    }

    /**
     * Transforms the delta to the simplest (and safest) form. E.g. it will transform add delta for
     * single-value properties to replace delta. 
     */
    public void simplify() {
        ItemDefinition itemDefinition = getDefinition();
        if (itemDefinition == null) {
            throw new IllegalStateException("Attempt to simplify delta without a definition");
        }
        if (itemDefinition.isSingleValue() && isAdd()) {
            valuesToReplace = valuesToAdd;
            valuesToAdd = null;
            valuesToDelete = null;
        }
    }

    private void cleanupAllTheWayUp(Item<?> item) {
        if (item.isEmpty()) {
            PrismValue itemParent = item.getParent();
            if (itemParent != null) {
                if (itemParent instanceof PrismContainerValue<?>) {
                    ((PrismContainerValue<?>) itemParent).remove(item);
                    if (itemParent.isEmpty()) {
                        Itemable itemGrandparent = itemParent.getParent();
                        if (itemGrandparent != null) {
                            if (itemGrandparent instanceof Item<?>) {
                                cleanupAllTheWayUp((Item<?>) itemGrandparent);
                            }
                        }
                    }
                }
            }
        }
    }

    public static void applyTo(Collection<? extends ItemDelta> deltas, PrismContainer propertyContainer)
            throws SchemaException {
        for (ItemDelta delta : deltas) {
            delta.applyTo(propertyContainer);
        }
    }

    public static void applyToMatchingPath(Collection<? extends ItemDelta> deltas, PrismContainer propertyContainer)
            throws SchemaException {
        for (ItemDelta delta : deltas) {
            delta.applyToMatchingPath(propertyContainer);
        }
    }

    public void applyTo(Item item) throws SchemaException {
        ItemPath itemPath = item.getPath();
        ItemPath deltaPath = getPath();
        CompareResult compareComplex = itemPath.compareComplex(deltaPath);
        if (compareComplex == CompareResult.EQUIVALENT) {
            applyToMatchingPath(item);
            cleanupAllTheWayUp(item);
        } else if (compareComplex == CompareResult.SUBPATH) {
            if (item instanceof PrismContainer<?>) {
                PrismContainer<?> container = (PrismContainer<?>) item;
                ItemPath remainderPath = deltaPath.remainder(itemPath);
                Item subItem = container.findOrCreateItem(remainderPath, getItemClass(), getDefinition());
                applyToMatchingPath(subItem);
            } else {
                throw new SchemaException("Cannot apply delta " + this + " to " + item
                        + " as delta path is below the item path and the item is not a container");
            }
        } else if (compareComplex == CompareResult.SUPERPATH) {
            throw new SchemaException(
                    "Cannot apply delta " + this + " to " + item + " as delta path is above the item path");
        } else if (compareComplex == CompareResult.NO_RELATION) {
            throw new SchemaException("Cannot apply delta " + this + " to " + item + " as paths do not match");
        }
    }

    /**
     * Applies delta to item were path of the delta and path of the item matches (skips path checks).
     */
    public void applyToMatchingPath(Item item) throws SchemaException {
        if (item.getDefinition() == null && getDefinition() != null) {
            item.applyDefinition(getDefinition());
        }
        if (!getItemClass().isAssignableFrom(item.getClass())) {
            throw new SchemaException("Cannot apply delta " + this + " to " + item
                    + " because the deltas is applicable only to " + getItemClass().getSimpleName());
        }
        if (valuesToReplace != null) {
            item.replaceAll(PrismValue.cloneCollection(valuesToReplace));
            // Application of delta might have removed values therefore leaving empty items.
            // Those needs to be cleaned-up (removed) as empty item is not a legal state.
            cleanupAllTheWayUp(item);
            return;
        }
        if (valuesToAdd != null) {
            if (item.getDefinition() != null && item.getDefinition().isSingleValue()) {
                item.replaceAll(PrismValue.cloneCollection(valuesToAdd));
            } else {
                for (V valueToAdd : valuesToAdd) {
                    if (!item.containsEquivalentValue(valueToAdd)) {
                        item.add(valueToAdd.clone());
                    }
                }
            }
        }
        if (valuesToDelete != null) {
            item.removeAll(valuesToDelete);
        }
        // Application of delta might have removed values therefore leaving empty items.
        // Those needs to be cleaned-up (removed) as empty item is not a legal state.
        cleanupAllTheWayUp(item);
    }

    public ItemDelta<?> getSubDelta(ItemPath path) {
        return this;
    }

    public boolean isApplicableTo(Item item) {
        if (item == null) {
            return false;
        }
        if (!isApplicableToType(item)) {
            return false;
        }
        // TODO: maybe check path?
        return true;
    }

    protected abstract boolean isApplicableToType(Item item);

    public static void accept(Collection<? extends ItemDelta> modifications, Visitor visitor, ItemPath path,
            boolean recursive) {
        for (ItemDelta modification : modifications) {
            ItemPath modPath = modification.getPath();
            CompareResult rel = modPath.compareComplex(path);
            if (rel == CompareResult.EQUIVALENT) {
                modification.accept(visitor, null, recursive);
            } else if (rel == CompareResult.SUBPATH) {
                modification.accept(visitor, path.substract(modPath), recursive);
            }
        }
    }

    /**
     * Returns the "new" state of the property - the state that would be after
     * the delta is applied.
     */
    public Item<V> getItemNew() throws SchemaException {
        return getItemNew(null);
    }

    /**
     * Returns the "new" state of the property - the state that would be after
     * the delta is applied.
     */
    public Item<V> getItemNew(Item<V> itemOld) throws SchemaException {
        if (definition == null) {
            throw new IllegalStateException("No definition in " + this);
        }
        Item<V> itemNew;
        if (itemOld == null) {
            if (isEmpty()) {
                return null;
            }
            itemNew = definition.instantiate(getElementName());
        } else {
            itemNew = itemOld.clone();
        }
        applyTo(itemNew);
        return itemNew;
    }

    public Item<V> getItemNewMatchingPath(Item<V> itemOld) throws SchemaException {
        if (definition == null) {
            throw new IllegalStateException("No definition in " + this);
        }
        Item<V> itemNew;
        if (itemOld == null) {
            if (isEmpty()) {
                return null;
            }
            itemNew = definition.instantiate(getElementName());
        } else {
            itemNew = itemOld.clone();
        }
        applyToMatchingPath(itemNew);
        return itemNew;
    }

    /**
     * Returns true if the other delta is a complete subset of this delta.
     * I.e. if all the statements of the other delta are already contained
     * in this delta. As a consequence it also returns true if the two
     * deltas are equal. 
     */
    public boolean contains(ItemDelta<V> other) {
        if (!this.getPath().equivalent(other.getPath())) {
            return false;
        }
        if (!containsSet(this.valuesToAdd, other.valuesToAdd)) {
            return false;
        }
        if (!containsSet(this.valuesToDelete, other.valuesToDelete)) {
            return false;
        }
        if (!containsSet(this.valuesToReplace, other.valuesToReplace)) {
            return false;
        }
        return true;
    }

    private boolean containsSet(Collection<V> thisSet, Collection<V> otherSet) {
        if (thisSet == null && otherSet == null) {
            return true;
        }
        if (otherSet == null) {
            return true;
        }
        if (thisSet == null) {
            return false;
        }
        return thisSet.containsAll(otherSet);
    }

    public abstract ItemDelta<V> clone();

    protected void copyValues(ItemDelta<V> clone) {
        clone.definition = this.definition;
        clone.elementName = this.elementName;
        clone.parentPath = this.parentPath;
        clone.valuesToAdd = cloneSet(clone, this.valuesToAdd);
        clone.valuesToDelete = cloneSet(clone, this.valuesToDelete);
        clone.valuesToReplace = cloneSet(clone, this.valuesToReplace);
    }

    private Collection<V> cloneSet(ItemDelta clone, Collection<V> thisSet) {
        if (thisSet == null) {
            return null;
        }
        Collection<V> clonedSet = newValueCollection();
        for (V thisVal : thisSet) {
            V clonedVal = (V) thisVal.clone();
            clonedVal.setParent(clone);
            clonedSet.add(clonedVal);
        }
        return clonedSet;
    }

    public static <D extends ItemDelta<?>> Collection<D> cloneCollection(Collection<D> orig) {
        if (orig == null) {
            return null;
        }
        Collection<D> clone = new ArrayList<>(orig.size());
        for (D delta : orig) {
            clone.add((D) delta.clone());
        }
        return clone;
    }

    @Deprecated
    public static <T extends PrismValue> PrismValueDeltaSetTriple<T> toDeltaSetTriple(Item<T> item,
            ItemDelta<T> delta, boolean oldValuesValid, boolean newValuesValid) {
        if (item == null && delta == null) {
            return null;
        }
        if (!oldValuesValid && !newValuesValid) {
            return null;
        }
        if (oldValuesValid && !newValuesValid) {
            // There were values but they no longer are -> everything to minus set
            PrismValueDeltaSetTriple<T> triple = new PrismValueDeltaSetTriple<T>();
            if (item != null) {
                triple.addAllToMinusSet(item.getValues());
            }
            return triple;
        }
        if (item == null && delta != null) {
            return delta.toDeltaSetTriple(item);
        }
        if (delta == null || (!oldValuesValid && newValuesValid)) {
            PrismValueDeltaSetTriple<T> triple = new PrismValueDeltaSetTriple<T>();
            if (item != null) {
                triple.addAllToZeroSet(item.getValues());
            }
            return triple;
        }
        return delta.toDeltaSetTriple(item);
    }

    public static <T extends PrismValue> PrismValueDeltaSetTriple<T> toDeltaSetTriple(Item<T> item,
            ItemDelta<T> delta) {
        if (item == null && delta == null) {
            return null;
        }
        if (delta == null) {
            PrismValueDeltaSetTriple<T> triple = new PrismValueDeltaSetTriple<T>();
            triple.addAllToZeroSet(PrismValue.cloneCollection(item.getValues()));
            return triple;
        }
        return delta.toDeltaSetTriple(item);
    }

    public PrismValueDeltaSetTriple<V> toDeltaSetTriple() {
        return toDeltaSetTriple(null);
    }

    public PrismValueDeltaSetTriple<V> toDeltaSetTriple(Item<V> itemOld) {
        PrismValueDeltaSetTriple<V> triple = new PrismValueDeltaSetTriple<V>();
        if (isReplace()) {
            triple.getPlusSet().addAll(PrismValue.cloneCollection(getValuesToReplace()));
            if (itemOld != null) {
                triple.getMinusSet().addAll(PrismValue.cloneCollection(itemOld.getValues()));
            }
            return triple;
        }
        if (isAdd()) {
            triple.getPlusSet().addAll(PrismValue.cloneCollection(getValuesToAdd()));
        }
        if (isDelete()) {
            triple.getMinusSet().addAll(PrismValue.cloneCollection(getValuesToDelete()));
        }
        if (itemOld != null && itemOld.getValues() != null) {
            for (V itemVal : itemOld.getValues()) {
                if (!PrismValue.containsRealValue(valuesToDelete, itemVal)
                        && !PrismValue.containsRealValue(valuesToAdd, itemVal)) {
                    triple.getZeroSet().add((V) itemVal.clone());
                }
            }
        }
        return triple;
    }

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

    public void assertDefinitions(boolean tolarateRawValues, String sourceDescription) throws SchemaException {
        if (tolarateRawValues && isRaw()) {
            return;
        }
        if (definition == null) {
            throw new SchemaException("No definition in " + this + " in " + sourceDescription);
        }
        assertDefinitions(tolarateRawValues, valuesToAdd, "values to add in " + sourceDescription);
        assertDefinitions(tolarateRawValues, valuesToReplace, "values to replace in " + sourceDescription);
        assertDefinitions(tolarateRawValues, valuesToDelete, "values to delete in " + sourceDescription);
    }

    private void assertDefinitions(boolean tolarateRawValues, Collection<V> values, String sourceDescription)
            throws SchemaException {
        if (values == null) {
            return;
        }
        for (V val : values) {
            if (val instanceof PrismContainerValue<?>) {
                PrismContainerValue<?> cval = (PrismContainerValue<?>) val;
                if (cval.getItems() == null) {
                    continue;
                }
                for (Item<?> item : cval.getItems()) {
                    item.assertDefinitions(tolarateRawValues, cval.toString() + " in " + sourceDescription);
                }
            }
        }
    }

    public boolean isRaw() {
        Boolean isRaw = MiscUtil.and(isRawSet(valuesToAdd), isRawSet(valuesToReplace), isRawSet(valuesToDelete));
        if (isRaw == null) {
            return false;
        }
        return isRaw;
    }

    private Boolean isRawSet(Collection<V> set) {
        if (set == null) {
            return null;
        }
        for (V val : set) {
            if (!val.isRaw()) {
                return false;
            }
        }
        return true;
    }

    public void revive(PrismContext prismContext) throws SchemaException {
        this.prismContext = prismContext;
        reviveSet(valuesToAdd, prismContext);
        reviveSet(valuesToDelete, prismContext);
        reviveSet(valuesToReplace, prismContext);
    }

    private void reviveSet(Collection<V> set, PrismContext prismContext) throws SchemaException {
        if (set == null) {
            return;
        }
        for (V val : set) {
            val.revive(prismContext);
        }
    }

    public void applyDefinition(ItemDefinition itemDefinition, boolean force) throws SchemaException {
        if (this.definition != null && !force) {
            return;
        }
        this.definition = itemDefinition;
        applyDefinitionSet(valuesToAdd, itemDefinition, force);
        applyDefinitionSet(valuesToReplace, itemDefinition, force);
        applyDefinitionSet(valuesToDelete, itemDefinition, force);
    }

    private void applyDefinitionSet(Collection<V> set, ItemDefinition itemDefinition, boolean force)
            throws SchemaException {
        if (set == null) {
            return;
        }
        for (V val : set) {
            val.applyDefinition(itemDefinition, force);
        }
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((definition == null) ? 0 : definition.hashCode());
        result = prime * result + ((elementName == null) ? 0 : elementName.hashCode());
        // if equals uses parentPath.equivalent call, we should not use default implementation of parentPath.hashCode
        //result = prime * result + ((parentPath == null) ? 0 : parentPath.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        ItemDelta other = (ItemDelta) obj;
        if (definition == null) {
            if (other.definition != null)
                return false;
        } else if (!definition.equals(other.definition))
            return false;
        if (elementName == null) {
            if (other.elementName != null)
                return false;
        } else if (!elementName.equals(other.elementName))
            return false;
        if (parentPath == null) {
            if (other.parentPath != null)
                return false;
        } else if (!parentPath.equivalent(other.parentPath)) // or "equals" ?
            return false;
        if (!equalsSetRealValue(this.valuesToAdd, other.valuesToAdd))
            return false;
        if (!equalsSetRealValue(this.valuesToDelete, other.valuesToDelete))
            return false;
        if (!equalsSetRealValue(this.valuesToReplace, other.valuesToReplace))
            return false;
        return true;
    }

    private boolean equalsSetRealValue(Collection<V> thisValue, Collection<V> otherValues) {
        Comparator<?> comparator = new Comparator<Object>() {
            @Override
            public int compare(Object o1, Object o2) {
                if (o1 instanceof PrismValue && o2 instanceof PrismValue) {
                    PrismValue v1 = (PrismValue) o1;
                    PrismValue v2 = (PrismValue) o2;
                    return v1.equalsRealValue(v2) ? 0 : 1;
                } else {
                    return -1;
                }
            }
        };
        return MiscUtil.unorderedCollectionEquals(thisValue, otherValues, comparator);
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName()).append("(");
        sb.append(parentPath).append(" / ").append(PrettyPrinter.prettyPrint(elementName));
        if (valuesToReplace != null) {
            sb.append(", REPLACE");
        }

        if (valuesToAdd != null) {
            sb.append(", ADD");
        }

        if (valuesToDelete != null) {
            sb.append(", DELETE");
        }
        sb.append(")");
        return sb.toString();
    }

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

    @Override
    public String debugDump(int indent) {
        StringBuilder sb = new StringBuilder();
        DebugUtil.indentDebugDump(sb, indent);
        if (DebugUtil.isDetailedDebugDump()) {
            sb.append(getClass().getSimpleName()).append(":");
        }
        ItemPath path = getPath();
        sb.append(path);

        if (definition != null && DebugUtil.isDetailedDebugDump()) {
            sb.append(" def");
        }

        if (valuesToReplace != null) {
            sb.append("\n");
            dumpValues(sb, "REPLACE", valuesToReplace, indent + 1);
        }

        if (valuesToAdd != null) {
            sb.append("\n");
            dumpValues(sb, "ADD", valuesToAdd, indent + 1);
        }

        if (valuesToDelete != null) {
            sb.append("\n");
            dumpValues(sb, "DELETE", valuesToDelete, indent + 1);
        }

        return sb.toString();

    }

    protected void dumpValues(StringBuilder sb, String label, Collection<V> values, int indent) {
        DebugUtil.indentDebugDump(sb, indent);
        sb.append(label).append(": ");
        if (values == null) {
            sb.append("(null)");
        } else {
            if (DebugUtil.isDetailedDebugDump()) {
                for (V value : values) {
                    sb.append("\n");
                    sb.append(value.debugDump(indent + 1));
                }
            } else {
                Iterator<V> i = values.iterator();
                while (i.hasNext()) {
                    V value = i.next();
                    sb.append(value.toHumanReadableString());
                    if (i.hasNext()) {
                        sb.append(", ");
                    }
                }
            }
        }
    }

}