<commit_before>/*
 * Copyright (c) 2012 Evolveum
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * http://www.opensource.org/licenses/cddl1 or
 * CDDLv1.0.txt file in the source code distribution.
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 *
 * Portions Copyrighted 2012 [name of copyright owner]
 */
package com.evolveum.midpoint.prism.delta;

import com.evolveum.midpoint.prism.Containerable;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.Objectable;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismContainerDefinition;
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.PrismPropertyDefinition;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.PrismReference;
import com.evolveum.midpoint.prism.PrismReferenceDefinition;
import com.evolveum.midpoint.prism.PrismReferenceValue;
import com.evolveum.midpoint.prism.PropertyPath;
import com.evolveum.midpoint.prism.Visitable;
import com.evolveum.midpoint.prism.Visitor;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.util.PrismAsserts;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.Dumpable;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.exception.SchemaException;

import javax.xml.namespace.QName;

import org.apache.commons.lang.Validate;

import java.io.Serializable;
import java.util.*;

/**
 * Relative difference (delta) of the object.
 * <p/>
 * This class describes how the object changes. It can describe either object addition, modification of deletion.
 * <p/>
 * Addition described complete new (absolute) state of the object.
 * <p/>
 * Modification contains a set property deltas that describe relative changes to individual properties
 * <p/>
 * Deletion does not contain anything. It only marks object for deletion.
 * <p/>
 * The OID is mandatory for modification and deletion.
 *
 * @author <NAME>
 * @see PropertyDelta
 */
public class ObjectDelta<T extends Objectable> implements Dumpable, DebugDumpable, Visitable, Serializable {

    private ChangeType changeType;

    /**
     * OID of the object that this delta applies to.
     */
    private String oid;

    /**
     * New object to add. Valid only if changeType==ADD
     */
    private PrismObject<T> objectToAdd;

    /**
     * Set of relative property deltas. Valid only if changeType==MODIFY
     */
    private Collection<? extends ItemDelta> modifications;

    /**
     * Class of the object that we describe.
     */
    private Class<T> objectTypeClass;
    
    transient private PrismContext prismContext;

    public ObjectDelta(Class<T> objectTypeClass, ChangeType changeType, PrismContext prismContext) {
    	Validate.notNull(objectTypeClass,"No objectTypeClass");
    	Validate.notNull(changeType,"No changeType");
    	
        this.changeType = changeType;
        this.objectTypeClass = objectTypeClass;
        this.prismContext = prismContext;
        objectToAdd = null;
        modifications = createEmptyModifications();
    }
    
    @Override
    public void accept(Visitor visitor){
    	visitor.visit(this);
    	for (ItemDelta<?> delta : getModifications()){
    		delta.accept(visitor);
    	}
    }

    public ChangeType getChangeType() {
        return changeType;
    }

    public void setChangeType(ChangeType changeType) {
        this.changeType = changeType;
    }
    
	public static boolean isAdd(ObjectDelta<?> objectDelta) {
		if (objectDelta == null) {
			return false;
		}
		return objectDelta.isAdd();
	}

	public boolean isAdd() {
		return changeType == ChangeType.ADD;
	}

	public static boolean isDelete(ObjectDelta<?> objectDelta) {
		if (objectDelta == null) {
			return false;
		}
		return objectDelta.isDelete();
	}

	public boolean isDelete() {
		return changeType == ChangeType.DELETE;
	}

	public static boolean isModify(ObjectDelta<?> objectDelta) {
		if (objectDelta == null) {
			return false;
		}
		return objectDelta.isModify();
	}

	public boolean isModify() {
		return changeType == ChangeType.MODIFY;
	}

	public String getOid() {
        return oid;
    }

    public void setOid(String oid) {
        this.oid = oid;
    }

    public PrismContext getPrismContext() {
		return prismContext;
	}

	public PrismObject<T> getObjectToAdd() {
        return objectToAdd;
    }

    public void setObjectToAdd(PrismObject<T> objectToAdd) {
    	if (getChangeType() != ChangeType.ADD) {
    		throw new IllegalStateException("Cannot set object to "+getChangeType()+" delta");
    	}
        this.objectToAdd = objectToAdd;
        if (objectToAdd != null) {
            this.objectTypeClass = objectToAdd.getCompileTimeClass();
        }
    }

    public Collection<? extends ItemDelta> getModifications() {
        return modifications;
    }

    public void addModification(ItemDelta itemDelta) {
    	if (getChangeType() != ChangeType.MODIFY) {
    		throw new IllegalStateException("Cannot add modifications to "+getChangeType()+" delta");
    	}
    	QName name = itemDelta.getName();
    	ItemDelta existingModification = findModification(name, itemDelta.getClass());
    	if (existingModification != null) {
    		existingModification.merge(itemDelta);
    	} else {
    		((Collection)modifications).add(itemDelta);
    	}
    }
    
	public boolean containsModification(ItemDelta itemDelta) {
		for (ItemDelta modification: modifications) {
			if (modification.equals(itemDelta)) {
				return true;
			}
		}
		return false;
	}

	public void addModifications(Collection<? extends ItemDelta> itemDeltas) {
    	for (ItemDelta<?> modDelta: itemDeltas) {
    		addModification(modDelta);
    	}
    }
    
    public void addModifications(ItemDelta<?>... itemDeltas) {
    	for (ItemDelta<?> modDelta: itemDeltas) {
    		addModification(modDelta);
    	}
    }
    
    public ItemDelta<?> findItemDelta(PropertyPath propertyPath) {
    	return findItemDelta(propertyPath, ItemDelta.class, Item.class);
    }
    
    private <D extends ItemDelta, I extends Item> D findItemDelta(PropertyPath propertyPath, Class<D> deltaType, Class<I> itemType) {
        if (changeType == ChangeType.ADD) {
            I item = objectToAdd.findItem(propertyPath, itemType);
            if (item == null) {
                return null;
            }
            D itemDelta = createEmptyDelta(propertyPath, item.getDefinition(), deltaType, item.getClass());
            itemDelta.addValuesToAdd(item.getClonedValues());
            return itemDelta;
        } else if (changeType == ChangeType.MODIFY) {
            return findModification(propertyPath, deltaType);
        } else {
            return null;
        }
    }

    public boolean hasItemDelta(PropertyPath propertyPath) {
        if (changeType == ChangeType.ADD) {
            Item item = objectToAdd.findItem(propertyPath, Item.class);
            return item != null;
        } else if (changeType == ChangeType.MODIFY) {
            ItemDelta modification = findModification(propertyPath, ItemDelta.class);
            return modification != null;
        } else {
            return false;
        }
    }
    
    public boolean hasCompleteDefinition() {
    	if (isAdd()) {
    		return getObjectToAdd().hasCompleteDefinition();
    	} else if (isModify()) {
    		for (ItemDelta modification: getModifications()) {
    			if (!modification.hasCompleteDefinition()) {
    				return false;
    			}
    			return true;
    		}
    	} else if (isDelete()) {
    		return true;
    	}
    	throw new IllegalStateException("Strange things happen");
    }
    
    private <D extends ItemDelta, I extends Item> D createEmptyDelta(PropertyPath propertyPath, ItemDefinition itemDef,
    		Class<D> deltaType, Class<I> itemType) {
    
    	if (PrismProperty.class.isAssignableFrom(itemType)) {
    		return (D) new PropertyDelta(propertyPath, (PrismPropertyDefinition)itemDef);
    	} else if (PrismContainer.class.isAssignableFrom(itemType)) {
    		return (D) new ContainerDelta(propertyPath, (PrismContainerDefinition)itemDef);
    	} else if (PrismReference.class.isAssignableFrom(itemType)) {
    		return (D) new ReferenceDelta(propertyPath, (PrismReferenceDefinition)itemDef);
    	} else {
    		throw new IllegalArgumentException("Unknown item type "+itemType);
    	}
    }

    public Class<T> getObjectTypeClass() {
        return objectTypeClass;
    }

    public void setObjectTypeClass(Class<T> objectTypeClass) {
        this.objectTypeClass = objectTypeClass;
    }

    /**
     * Top-level path is assumed.
     */
    public PropertyDelta findPropertyDelta(QName propertyName) {
        return findPropertyDelta(new PropertyPath(propertyName));
    }

    public PropertyDelta findPropertyDelta(PropertyPath parentPath, QName propertyName) {
        return findPropertyDelta(new PropertyPath(parentPath, propertyName));
    }
    
    public PropertyDelta findPropertyDelta(PropertyPath propertyPath) {
    	return findItemDelta(propertyPath, PropertyDelta.class, PrismProperty.class);
    }
    
    public <X extends Containerable> ContainerDelta<X> findContainerDelta(PropertyPath propertyPath) {
    	return findItemDelta(propertyPath, ContainerDelta.class, PrismContainer.class);
    }

    public <X extends Containerable> ContainerDelta<X> findContainerDelta(QName name) {
    	return findContainerDelta(new PropertyPath(name));
    }

    private <D extends ItemDelta> D findModification(PropertyPath propertyPath, Class<D> deltaType) {
    	return ItemDelta.findItemDelta(modifications, propertyPath, deltaType);
    }
    
    private  <D extends ItemDelta> D findModification(QName itemName, Class<D> deltaType) {
    	return findModification(new PropertyPath(itemName), deltaType);
    }
    
    public ReferenceDelta findReferenceModification(QName itemName) {
    	return findModification(itemName, ReferenceDelta.class);
    }
    
    private <D extends ItemDelta> void removeModification(PropertyPath propertyPath, Class<D> deltaType) {
    	ItemDelta.removeItemDelta(modifications, propertyPath, deltaType);
    }
    
    private <D extends ItemDelta> void removeModification(QName itemName, Class<D> deltaType) {
    	removeModification(new PropertyPath(itemName), deltaType);
    }
    
    public void removeReferenceModification(QName itemName) {
    	removeModification(itemName, ReferenceDelta.class);
    }

    public boolean isEmpty() {
    	if (getChangeType() == ChangeType.DELETE) {
    		// Delete delta is never empty
    		return false;
    	}
    	if (getChangeType() == ChangeType.ADD) {
    		return objectToAdd == null || objectToAdd.isEmpty();
    	}
        if (modifications == null || modifications.isEmpty()) {
        	return true;
        }
        for (ItemDelta<?> mod: modifications) {
        	if (!mod.isEmpty()) {
        		return false;
        	}
        }
        return true;
    }
    
    public void applyDefinition(PrismObjectDefinition<T> definition) throws SchemaException {
    	if (objectToAdd != null) {
    		objectToAdd.applyDefinition(definition);
    	}
    	ItemDelta.applyDefinition(getModifications(), definition);
    }

    /**
     * Semi-deep clone.
     */
    public ObjectDelta<T> clone() {
        ObjectDelta<T> clone = new ObjectDelta<T>(this.objectTypeClass, this.changeType, this.prismContext);
        clone.oid = this.oid;
        clone.modifications = createEmptyModifications();
        clone.modifications.addAll((Collection)this.modifications);
        if (this.objectToAdd == null) {
            clone.objectToAdd = null;
        } else {
            clone.objectToAdd = this.objectToAdd.clone();
        }
        return clone;
    }

    /**
     * Merge provided delta into this delta.
     * This delta is assumed to be chronologically earlier.
     */
    public void merge(ObjectDelta<T> deltaToMerge) throws SchemaException {
        if (changeType == ChangeType.ADD) {
            if (deltaToMerge.changeType == ChangeType.ADD) {
                // Maybe we can, be we do not want. This is usually an error anyway.
                throw new IllegalArgumentException("Cannot merge two ADD deltas: " + this + ", " + deltaToMerge);
            } else if (deltaToMerge.changeType == ChangeType.MODIFY) {
                if (objectToAdd == null) {
                    throw new IllegalStateException("objectToAdd is null");
                }
                deltaToMerge.applyTo(objectToAdd);
            } else if (deltaToMerge.changeType == ChangeType.DELETE) {
                this.changeType = ChangeType.DELETE;
            }
        } else if (changeType == ChangeType.MODIFY) {
            if (deltaToMerge.changeType == ChangeType.ADD) {
                throw new IllegalArgumentException("Cannot merge 'add' delta to a 'modify' object delta");
            } else if (deltaToMerge.changeType == ChangeType.MODIFY) {
            	mergeModifications(deltaToMerge.modifications);
            } else if (deltaToMerge.changeType == ChangeType.DELETE) {
                this.changeType = ChangeType.DELETE;
            }
        } else { // DELETE
            if (deltaToMerge.changeType == ChangeType.ADD) {
                this.changeType = ChangeType.ADD;
                // TODO: clone?
                this.objectToAdd = deltaToMerge.objectToAdd;
            } else if (deltaToMerge.changeType == ChangeType.MODIFY) {
                // Just ignore the modification of a deleted object
            } else if (deltaToMerge.changeType == ChangeType.DELETE) {
                // Nothing to do
            }
        }
    }

    /**
     * Union of several object deltas. The deltas are merged to create a single delta
     * that contains changes from all the deltas.
     */
    public static <T extends Objectable> ObjectDelta<T> union(ObjectDelta<T>... deltas) throws SchemaException {
        List<ObjectDelta<T>> modifyDeltas = new ArrayList<ObjectDelta<T>>(deltas.length);
        ObjectDelta<T> addDelta = null;
        ObjectDelta<T> deleteDelta = null;
        for (ObjectDelta<T> delta : deltas) {
            if (delta == null) {
                continue;
            }
            if (delta.changeType == ChangeType.MODIFY) {
                modifyDeltas.add(delta);
            } else if (delta.changeType == ChangeType.ADD) {
                if (addDelta != null) {
                    // Maybe we can, be we do not want. This is usually an error anyway.
                    throw new IllegalArgumentException("Cannot merge two add deltas: " + addDelta + ", " + delta);
                }
                addDelta = delta;
            } else if (delta.changeType == ChangeType.DELETE) {
                deleteDelta = delta;
            }

        }

        if (deleteDelta != null && addDelta == null) {
            // Merging DELETE with anything except ADD is still a DELETE
            return deleteDelta.clone();
        }

        if (deleteDelta != null && addDelta != null) {
            throw new IllegalArgumentException("Cannot merge add and delete deltas: " + addDelta + ", " + deleteDelta);
        }

        if (addDelta != null) {
            return mergeToDelta(addDelta, modifyDeltas);
        } else {
            if (modifyDeltas.size() == 0) {
                return null;
            }
            if (modifyDeltas.size() == 1) {
                return modifyDeltas.get(0);
            }
            return mergeToDelta(modifyDeltas.get(0), modifyDeltas.subList(1, modifyDeltas.size()));
        }
    }

    private static <T extends Objectable> ObjectDelta<T> mergeToDelta(ObjectDelta<T> firstDelta,
            List<ObjectDelta<T>> modifyDeltas) throws SchemaException {
        if (modifyDeltas.size() == 0) {
            return firstDelta;
        }
        ObjectDelta<T> delta = firstDelta.clone();
        for (ObjectDelta<T> modifyDelta : modifyDeltas) {
            if (modifyDelta == null) {
                continue;
            }
            if (modifyDelta.changeType != ChangeType.MODIFY) {
                throw new IllegalArgumentException("Can only merge MODIFY changes, got " + modifyDelta.changeType);
            }
            delta.mergeModifications(modifyDelta.modifications);
        }
        return delta;
    }
    
    public void mergeModifications(Collection<? extends ItemDelta> modificationsToMerge) throws SchemaException {
        for (ItemDelta<?> propDelta : modificationsToMerge) {
        	mergeModification(propDelta);
        }
    }
    
    public void mergeModification(ItemDelta<?> modificationToMerge) throws SchemaException {
        if (changeType == ChangeType.ADD) {
        	modificationToMerge.applyTo(objectToAdd);
        } else if (changeType == ChangeType.MODIFY) {
        	ItemDelta myDelta = findModification(modificationToMerge.getPath(), ItemDelta.class);
            if (myDelta == null) {
                addModification(modificationToMerge.clone());
            } else {
                myDelta.merge(modificationToMerge);
            }
        } // else it is DELETE. There's nothing to do. Merging anything to delete is still delete
    }


    /**
     * Applies this object delta to specified object, returns updated object.
     * It modifies the provided object.
     */
    public void applyTo(PrismObject<T> targetObject) throws SchemaException {
    	if (isEmpty()) {
    		// nothing to do
    		return;
    	}
        if (changeType != ChangeType.MODIFY) {
            throw new IllegalStateException("Can apply only MODIFY delta to object, got " + changeType + " delta");
        }
        for (ItemDelta itemDelta : modifications) {
            itemDelta.applyTo(targetObject);
        }
    }

    /**
     * Applies this object delta to specified object, returns updated object.
     * It leaves the original object unchanged.
     *
     * @param objectOld object before change
     * @return object with applied changes or null if the object should not exit (was deleted)
     */
    public PrismObject<T> computeChangedObject(PrismObject<T> objectOld) throws SchemaException {
        if (objectOld == null) {
            if (getChangeType() == ChangeType.ADD) {
                objectOld = getObjectToAdd();
            } else {
                //throw new IllegalStateException("Cannot apply "+getChangeType()+" delta to a null old object");
                // This seems to be quite OK
                return null;
            }
        }
        if (getChangeType() == ChangeType.DELETE) {
            return null;
        }
        // MODIFY change
        PrismObject<T> objectNew = objectOld.clone();
        for (ItemDelta modification : modifications) {
            modification.applyTo(objectNew);
        }
        return objectNew;
    }

    /**
     * Incorporates the property delta into the existing property deltas
     * (regardless of the change type).
     */
    public void swallow(ItemDelta<?> newItemDelta) throws SchemaException {
        if (changeType == ChangeType.MODIFY) {
            // TODO: check for conflict
            addModification(newItemDelta);
        } else if (changeType == ChangeType.ADD) {
//        	Class<?> valueClass = newPropertyDelta.getValueClass();
            PrismProperty<?> property = objectToAdd.findOrCreateProperty(new PropertyPath(newItemDelta.getParentPath(), newItemDelta.getName()));
            newItemDelta.applyTo(property);
        }
        // nothing to do for DELETE
    }

    private Collection<? extends ItemDelta> createEmptyModifications() {
    	// Lists are easier to debug
        return new ArrayList<ItemDelta>();
    }
    
    public <X> PropertyDelta<X> createPropertyModification(QName name, PrismPropertyDefinition propertyDefinition) {
    	PropertyDelta<X> propertyDelta = new PropertyDelta<X>(name, propertyDefinition);
    	addModification(propertyDelta);
    	return propertyDelta;
    }
    
    public <X> PropertyDelta<X> createPropertyModification(PropertyPath path) {
	    PrismObjectDefinition<T> objDef = getPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(getObjectTypeClass());
		PrismPropertyDefinition propDef = objDef.findPropertyDefinition(path);
		return createPropertyModification(path, propDef);
    }
	    
    public <X> PropertyDelta<X> createPropertyModification(PropertyPath path, PrismPropertyDefinition propertyDefinition) {
    	PropertyDelta<X> propertyDelta = new PropertyDelta<X>(path, propertyDefinition);
    	addModification(propertyDelta);
    	return propertyDelta;
    }
    
    public ReferenceDelta createReferenceModification(QName name, PrismReferenceDefinition referenceDefinition) {
    	ReferenceDelta referenceDelta = new ReferenceDelta(name, referenceDefinition);
    	addModification(referenceDelta);
    	return referenceDelta;
    }
    
    /**
     * Convenience method for quick creation of object deltas that replace a single object property. This is used quite often
     * to justify a separate method. 
     */
    public static <O extends Objectable, X> ObjectDelta<O> createModificationReplaceProperty(Class<O> type, String oid, QName propertyName,
    		PrismContext prismContext, X... propertyValues) {
    	PropertyPath propertyPath = new PropertyPath(propertyName);
    	return createModificationReplaceProperty(type, oid, propertyPath, prismContext, propertyValues);
    }
    
    /**
     * Convenience method for quick creation of object deltas that replace a single object property. This is used quite often
     * to justify a separate method. 
     */
    public static <O extends Objectable, X> ObjectDelta<O> createModificationReplaceProperty(Class<O> type, String oid, 
    		PropertyPath propertyPath, PrismContext prismContext, X... propertyValues) {
    	ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
    	objectDelta.setOid(oid);
    	fillInModificationReplaceProperty(objectDelta, propertyPath, propertyValues);
    	return objectDelta;
    }
    
    public static <O extends Objectable, X> ObjectDelta<O> createModificationAddProperty(Class<O> type, String oid, 
    		PropertyPath propertyPath, PrismContext prismContext, X... propertyValues) {
    	ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
    	objectDelta.setOid(oid);
    	fillInModificationAddProperty(objectDelta, propertyPath, propertyValues);
    	return objectDelta;
    }
    
    public static <O extends Objectable, X> ObjectDelta<O> createModificationDeleteProperty(Class<O> type, String oid, 
    		PropertyPath propertyPath, PrismContext prismContext, X... propertyValues) {
    	ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
    	objectDelta.setOid(oid);
    	fillInModificationDeleteProperty(objectDelta, propertyPath, propertyValues);
    	return objectDelta;
    }
    
    public <X> void addModificationReplaceProperty(QName propertyQName, X... propertyValues) {
    	addModificationReplaceProperty(new PropertyPath(propertyQName), propertyValues);
    }
    
    public <X> void addModificationReplaceProperty(PropertyPath propertyPath, X... propertyValues) {
    	fillInModificationReplaceProperty(this, propertyPath, propertyValues);
    }
    
    public <X> void addModificationAddProperty(QName propertyQName, X... propertyValues) {
    	addModificationAddProperty(new PropertyPath(propertyQName), propertyValues);
    }
    
    public <X> void addModificationAddProperty(PropertyPath propertyPath, X... propertyValues) {
    	fillInModificationAddProperty(this, propertyPath, propertyValues);
    }
    
    public <X> void addModificationDeleteProperty(QName propertyQName, X... propertyValues) {
    	addModificationDeleteProperty(new PropertyPath(propertyQName), propertyValues);
    }
     
    public <X> void addModificationDeleteProperty(PropertyPath propertyPath, X... propertyValues) {
    	fillInModificationDeleteProperty(this, propertyPath, propertyValues);
    }
    
    protected static <O extends Objectable, X> void fillInModificationReplaceProperty(ObjectDelta<O> objectDelta,
    		PropertyPath propertyPath, X... propertyValues) {
    	PropertyDelta<X> propertyDelta = createModification(objectDelta, propertyPath);
    	if (propertyValues != null) {
	    	Collection<PrismPropertyValue<X>> valuesToReplace = toPrismPropertyValues(objectDelta.getPrismContext(), propertyValues);
	    	propertyDelta.setValuesToReplace(valuesToReplace);
    	}
    }

    protected static <O extends Objectable, X> void fillInModificationAddProperty(ObjectDelta<O> objectDelta,
    		PropertyPath propertyPath, X... propertyValues) {
    	PropertyDelta<X> propertyDelta = createModification(objectDelta, propertyPath);
    	if (propertyValues != null) {
	    	Collection<PrismPropertyValue<X>> valuesToAdd = toPrismPropertyValues(objectDelta.getPrismContext(), propertyValues);
	    	propertyDelta.addValuesToAdd(valuesToAdd);
    	}
    }
    
    protected static <O extends Objectable, X> void fillInModificationDeleteProperty(ObjectDelta<O> objectDelta,
    		PropertyPath propertyPath, X... propertyValues) {
    	PropertyDelta<X> propertyDelta = createModification(objectDelta, propertyPath);
    	if (propertyValues != null) {
	    	Collection<PrismPropertyValue<X>> valuesToDelete = toPrismPropertyValues(objectDelta.getPrismContext(), propertyValues);
	    	propertyDelta.addValuesToDelete(valuesToDelete);
    	}
    }
    
    protected static <O extends Objectable, X> PropertyDelta<X> createModification(ObjectDelta<O> objectDelta, PropertyPath propertyPath) {
    	PrismObjectDefinition<O> objDef = objectDelta.getPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(objectDelta.getObjectTypeClass());
    	PrismPropertyDefinition propDef = objDef.findPropertyDefinition(propertyPath);
    	return objectDelta.createPropertyModification(propertyPath, propDef);
    }
    
    protected static <X> Collection<PrismPropertyValue<X>> toPrismPropertyValues(PrismContext prismContext, X... propertyValues) {
    	Collection<PrismPropertyValue<X>> pvalues = new ArrayList<PrismPropertyValue<X>>(propertyValues.length);
    	for (X val: propertyValues) {
    		if (val instanceof PolyString) {
    			PolyString polyStringVal = (PolyString)val;
    			if (!polyStringVal.isComputed()) {
    				polyStringVal.recompute(prismContext.getDefaultPolyStringNormalizer());
    			}
    		}
    		PrismPropertyValue<X> pval = new PrismPropertyValue<X>(val);
    		pvalues.add(pval);
    	}
    	return pvalues;
    }
    
    /**
     * Convenience method for quick creation of object deltas that replace a single object property. This is used quite often
     * to justify a separate method. 
     */
    public static <O extends Objectable> ObjectDelta<O> createModificationAddReference(Class<O> type, String oid, QName propertyName,
    		PrismContext prismContext, PrismObject<?>... referenceObjects) {
    	ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
    	objectDelta.setOid(oid);
    	PrismObjectDefinition<O> objDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(type);
    	PrismReferenceDefinition refDef = objDef.findReferenceDefinition(propertyName);
    	ReferenceDelta referenceDelta = objectDelta.createReferenceModification(propertyName, refDef);
    	Collection<PrismReferenceValue> valuesToReplace = new ArrayList<PrismReferenceValue>(referenceObjects.length);
    	for (PrismObject<?> refObject: referenceObjects) {
    		PrismReferenceValue refVal = new PrismReferenceValue();
    		refVal.setObject(refObject);
    		valuesToReplace.add(refVal);
    	}
    	referenceDelta.setValuesToReplace(valuesToReplace);
    	return objectDelta;
    }
    
    public static <T extends Objectable> ObjectDelta<T> createModifyDelta(String oid, Collection<? extends ItemDelta> modifications,
    		Class<T> objectTypeClass, PrismContext prismContext) {
    	ObjectDelta<T> objectDelta = new ObjectDelta<T>(objectTypeClass, ChangeType.MODIFY, prismContext);
    	objectDelta.addModifications(modifications);
    	objectDelta.setOid(oid);
    	return objectDelta;
    }
    
    public static <O extends Objectable> ObjectDelta<O> createEmptyAddDelta(Class<O> type, String oid, PrismContext prismContext) {
    	ObjectDelta<O> objectDelta = createEmptyDelta(type, oid, prismContext, ChangeType.ADD);
    	PrismObjectDefinition<O> objDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(type);
    	PrismObject<O> objectToAdd = objDef.instantiate();
    	objectDelta.setObjectToAdd(objectToAdd);
    	return objectDelta;
    }

    public static <O extends Objectable> ObjectDelta<O> createEmptyModifyDelta(Class<O> type, String oid, PrismContext prismContext) {
    	return createEmptyDelta(type, oid, prismContext, ChangeType.MODIFY);
    }

    public static <O extends Objectable> ObjectDelta<O> createEmptyDeleteDelta(Class<O> type, String oid, PrismContext prismContext) {
    	return createEmptyDelta(type, oid, prismContext, ChangeType.DELETE);
    }

    public static <O extends Objectable> ObjectDelta<O> createEmptyDelta(Class<O> type, String oid, PrismContext prismContext, 
    		ChangeType changeType) {
    	ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, changeType, prismContext);
    	objectDelta.setOid(oid);
    	return objectDelta;
    }
    
    public static <O extends Objectable> ObjectDelta<O> createAddDelta(PrismObject<O> objectToAdd) {
    	ObjectDelta<O> objectDelta = new ObjectDelta<O>(objectToAdd.getCompileTimeClass(), ChangeType.ADD, objectToAdd.getPrismContext());
    	objectDelta.setOid(objectToAdd.getOid());
    	objectDelta.setObjectToAdd(objectToAdd);
    	return objectDelta;
    }
    
    public static <O extends Objectable> ObjectDelta<O> createDeleteDelta(Class<O> type, String oid, PrismContext prismContext) {
    	ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.DELETE, prismContext);
    	objectDelta.setOid(oid);
    	return objectDelta;
    }
        
    public void checkConsistence() {
    	checkConsistence(true, false, false);
    }
    
    public void checkConsistence(boolean requireOid, boolean requireDefinition, boolean prohibitRaw) {
    	if (getChangeType() == ChangeType.ADD) {
			if (getModifications() != null && !getModifications().isEmpty()) {
				throw new IllegalStateException("Modifications present in ADD delta "+this);
			}
			if (getObjectToAdd() != null) {
				getObjectToAdd().checkConsistence(requireDefinition, prohibitRaw);
			} else {
				throw new IllegalStateException("User primary delta is ADD, but there is not object to add in "+this);
			}
		} else if (getChangeType() == ChangeType.MODIFY) {
			checkIdentifierConsistence(requireOid);
			if (getObjectToAdd() != null) {
				throw new IllegalStateException("Object to add present in MODIFY delta "+this);
			}
			if (getModifications() == null) {
				throw new IllegalStateException("Null modification in MODIFY delta "+this);
			}
			ItemDelta.checkConsistence(getModifications(), requireDefinition, prohibitRaw);
		} else if (getChangeType() == ChangeType.DELETE) {
	    	if (requireOid && getOid() == null) {
	    		throw new IllegalStateException("Null oid in delta "+this);
	    	}
			if (getObjectToAdd() != null) {
				throw new IllegalStateException("Object to add present in DELETE delta "+this);
			}
			if (getModifications() != null && !getModifications().isEmpty()) {
				throw new IllegalStateException("Modifications present in DELETE delta "+this);
			}			
		} else {
			throw new IllegalStateException("Unknown change type "+getChangeType()+" in delta "+this);
		}
    }
    
	protected void checkIdentifierConsistence(boolean requireOid) {
		if (requireOid && getOid() == null) {
    		throw new IllegalStateException("Null oid in delta "+this);
    	}
	}

	public static void checkConsistence(Collection<? extends ObjectDelta<?>> deltas) {
		for (ObjectDelta<?> delta: deltas) {
			delta.checkConsistence();
		}
	}
    
    public void assertDefinitions() throws SchemaException {
    	assertDefinitions("");
    }
    
    public void assertDefinitions(String sourceDescription) throws SchemaException {
    	assertDefinitions(false, sourceDescription);
    }
    
    public void assertDefinitions(boolean tolerateRawElements) throws SchemaException {
    	assertDefinitions(tolerateRawElements, "");
    }
    
    /**
     * Assert that all the items has appropriate definition.
     */
    public void assertDefinitions(boolean tolerateRawElements, String sourceDescription) throws SchemaException {
    	if (changeType == ChangeType.ADD) {
    		objectToAdd.assertDefinitions("add delta in "+sourceDescription);
    	}
    	if (changeType == ChangeType.MODIFY) {
    		for (ItemDelta<?> mod: modifications) {
    			mod.assertDefinitions(tolerateRawElements, "modify delta for "+getOid()+" in "+sourceDescription);
    		}
    	}
    }
    
    public void revive(PrismContext prismContext) {
    	if (objectToAdd != null) {
    		objectToAdd.revive(prismContext);
    	}
    	if (modifications != null) {
    		for (ItemDelta modification: modifications) {
    			modification.revive(prismContext);
    		}
    	}
	}
    
    public void applyDefinition(PrismObjectDefinition<T> objectDefinition, boolean force) throws SchemaException {
    	if (objectToAdd != null) {
    		objectToAdd.applyDefinition(objectDefinition, force);
    	}
    	if (modifications != null) {
    		for (ItemDelta modification: modifications) {
    			PropertyPath path = modification.getPath();
    			ItemDefinition itemDefinition = objectDefinition.findItemDefinition(path);
    			modification.applyDefinition(itemDefinition, force);
    		}
    	}
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(debugName());
        sb.append("(").append(debugIdentifiers());
        sb.append(",").append(changeType).append(": ");
        if (changeType == ChangeType.ADD) {
            if (objectToAdd == null) {
                sb.append("null");
            } else {
                sb.append(objectToAdd.toString());
            }
        } else if (changeType == ChangeType.MODIFY) {
            Iterator<? extends ItemDelta> i = modifications.iterator();
            while (i.hasNext()) {
                sb.append(i.next().toString());
                if (i.hasNext()) {
                    sb.append(", ");
                }
            }
        }
        // Nothing to print for delete
        sb.append(")");
        return sb.toString();
    }
    
    protected String debugName() {
    	return "ObjectDelta";
    }
    
    protected String debugIdentifiers() {
    	return "oid=" + getOid();
    }

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

    @Override
    public String debugDump(int indent) {
        StringBuilder sb = new StringBuilder();
        DebugUtil.indentDebugDump(sb, indent);
        sb.append(debugName());
        sb.append("<").append(objectTypeClass.getSimpleName()).append(">(");
        sb.append(debugIdentifiers()).append(",").append(changeType).append("):\n");
        if (objectToAdd == null) {
        	if (changeType == ChangeType.ADD) {
	        	DebugUtil.indentDebugDump(sb, indent + 1);
	            sb.append("null");
        	}
        } else {
            sb.append(objectToAdd.debugDump(indent + 1));
        }
        if (modifications != null) {
            Iterator<? extends ItemDelta> i = modifications.iterator();
            while (i.hasNext()) {
                sb.append(i.next().debugDump(indent + 1));
                if (i.hasNext()) {
                    sb.append("\n");
                }
            }
        }
        // Nothing to print for delete
        return sb.toString();
    }

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


}
<commit_msg>fixed problem with "strange things happen"..<commit_after>/*
 * Copyright (c) 2012 Evolveum
 *
 * The contents of this file are subject to the terms
 * of the Common Development and Distribution License
 * (the License). You may not use this file except in
 * compliance with the License.
 *
 * You can obtain a copy of the License at
 * http://www.opensource.org/licenses/cddl1 or
 * CDDLv1.0.txt file in the source code distribution.
 * See the License for the specific language governing
 * permission and limitations under the License.
 *
 * If applicable, add the following below the CDDL Header,
 * with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 *
 * Portions Copyrighted 2012 [name of copyright owner]
 */
package com.evolveum.midpoint.prism.delta;

import com.evolveum.midpoint.prism.Containerable;
import com.evolveum.midpoint.prism.Item;
import com.evolveum.midpoint.prism.ItemDefinition;
import com.evolveum.midpoint.prism.Objectable;
import com.evolveum.midpoint.prism.PrismContainer;
import com.evolveum.midpoint.prism.PrismContainerDefinition;
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.PrismPropertyDefinition;
import com.evolveum.midpoint.prism.PrismPropertyValue;
import com.evolveum.midpoint.prism.PrismReference;
import com.evolveum.midpoint.prism.PrismReferenceDefinition;
import com.evolveum.midpoint.prism.PrismReferenceValue;
import com.evolveum.midpoint.prism.PropertyPath;
import com.evolveum.midpoint.prism.Visitable;
import com.evolveum.midpoint.prism.Visitor;
import com.evolveum.midpoint.prism.polystring.PolyString;
import com.evolveum.midpoint.prism.util.PrismAsserts;
import com.evolveum.midpoint.util.DebugUtil;
import com.evolveum.midpoint.util.DebugDumpable;
import com.evolveum.midpoint.util.Dumpable;
import com.evolveum.midpoint.util.MiscUtil;
import com.evolveum.midpoint.util.exception.SchemaException;

import javax.xml.namespace.QName;

import org.apache.commons.lang.Validate;

import java.io.Serializable;
import java.util.*;

/**
 * Relative difference (delta) of the object.
 * <p/>
 * This class describes how the object changes. It can describe either object addition, modification of deletion.
 * <p/>
 * Addition described complete new (absolute) state of the object.
 * <p/>
 * Modification contains a set property deltas that describe relative changes to individual properties
 * <p/>
 * Deletion does not contain anything. It only marks object for deletion.
 * <p/>
 * The OID is mandatory for modification and deletion.
 *
 * @author <NAME>
 * @see PropertyDelta
 */
public class ObjectDelta<T extends Objectable> implements Dumpable, DebugDumpable, Visitable, Serializable {

    private ChangeType changeType;

    /**
     * OID of the object that this delta applies to.
     */
    private String oid;

    /**
     * New object to add. Valid only if changeType==ADD
     */
    private PrismObject<T> objectToAdd;

    /**
     * Set of relative property deltas. Valid only if changeType==MODIFY
     */
    private Collection<? extends ItemDelta> modifications;

    /**
     * Class of the object that we describe.
     */
    private Class<T> objectTypeClass;
    
    transient private PrismContext prismContext;

    public ObjectDelta(Class<T> objectTypeClass, ChangeType changeType, PrismContext prismContext) {
    	Validate.notNull(objectTypeClass,"No objectTypeClass");
    	Validate.notNull(changeType,"No changeType");
    	
        this.changeType = changeType;
        this.objectTypeClass = objectTypeClass;
        this.prismContext = prismContext;
        objectToAdd = null;
        modifications = createEmptyModifications();
    }
    
    @Override
    public void accept(Visitor visitor){
    	visitor.visit(this);
    	for (ItemDelta<?> delta : getModifications()){
    		delta.accept(visitor);
    	}
    }

    public ChangeType getChangeType() {
        return changeType;
    }

    public void setChangeType(ChangeType changeType) {
        this.changeType = changeType;
    }
    
	public static boolean isAdd(ObjectDelta<?> objectDelta) {
		if (objectDelta == null) {
			return false;
		}
		return objectDelta.isAdd();
	}

	public boolean isAdd() {
		return changeType == ChangeType.ADD;
	}

	public static boolean isDelete(ObjectDelta<?> objectDelta) {
		if (objectDelta == null) {
			return false;
		}
		return objectDelta.isDelete();
	}

	public boolean isDelete() {
		return changeType == ChangeType.DELETE;
	}

	public static boolean isModify(ObjectDelta<?> objectDelta) {
		if (objectDelta == null) {
			return false;
		}
		return objectDelta.isModify();
	}

	public boolean isModify() {
		return changeType == ChangeType.MODIFY;
	}

	public String getOid() {
        return oid;
    }

    public void setOid(String oid) {
        this.oid = oid;
    }

    public PrismContext getPrismContext() {
		return prismContext;
	}

	public PrismObject<T> getObjectToAdd() {
        return objectToAdd;
    }

    public void setObjectToAdd(PrismObject<T> objectToAdd) {
    	if (getChangeType() != ChangeType.ADD) {
    		throw new IllegalStateException("Cannot set object to "+getChangeType()+" delta");
    	}
        this.objectToAdd = objectToAdd;
        if (objectToAdd != null) {
            this.objectTypeClass = objectToAdd.getCompileTimeClass();
        }
    }

    public Collection<? extends ItemDelta> getModifications() {
        return modifications;
    }

    public void addModification(ItemDelta itemDelta) {
    	if (getChangeType() != ChangeType.MODIFY) {
    		throw new IllegalStateException("Cannot add modifications to "+getChangeType()+" delta");
    	}
    	QName name = itemDelta.getName();
    	ItemDelta existingModification = findModification(name, itemDelta.getClass());
    	if (existingModification != null) {
    		existingModification.merge(itemDelta);
    	} else {
    		((Collection)modifications).add(itemDelta);
    	}
    }
    
	public boolean containsModification(ItemDelta itemDelta) {
		for (ItemDelta modification: modifications) {
			if (modification.equals(itemDelta)) {
				return true;
			}
		}
		return false;
	}

	public void addModifications(Collection<? extends ItemDelta> itemDeltas) {
    	for (ItemDelta<?> modDelta: itemDeltas) {
    		addModification(modDelta);
    	}
    }
    
    public void addModifications(ItemDelta<?>... itemDeltas) {
    	for (ItemDelta<?> modDelta: itemDeltas) {
    		addModification(modDelta);
    	}
    }
    
    public ItemDelta<?> findItemDelta(PropertyPath propertyPath) {
    	return findItemDelta(propertyPath, ItemDelta.class, Item.class);
    }
    
    private <D extends ItemDelta, I extends Item> D findItemDelta(PropertyPath propertyPath, Class<D> deltaType, Class<I> itemType) {
        if (changeType == ChangeType.ADD) {
            I item = objectToAdd.findItem(propertyPath, itemType);
            if (item == null) {
                return null;
            }
            D itemDelta = createEmptyDelta(propertyPath, item.getDefinition(), deltaType, item.getClass());
            itemDelta.addValuesToAdd(item.getClonedValues());
            return itemDelta;
        } else if (changeType == ChangeType.MODIFY) {
            return findModification(propertyPath, deltaType);
        } else {
            return null;
        }
    }

    public boolean hasItemDelta(PropertyPath propertyPath) {
        if (changeType == ChangeType.ADD) {
            Item item = objectToAdd.findItem(propertyPath, Item.class);
            return item != null;
        } else if (changeType == ChangeType.MODIFY) {
            ItemDelta modification = findModification(propertyPath, ItemDelta.class);
            return modification != null;
        } else {
            return false;
        }
    }
    
    public boolean hasCompleteDefinition() {
    	if (isAdd()) {
    		return getObjectToAdd().hasCompleteDefinition();
    	} else if (isModify()) {
    		for (ItemDelta modification: getModifications()) {
    			if (!modification.hasCompleteDefinition()) {
    				return false;
    			}
//    			return true;
    		}
    		return true;
    	} else if (isDelete()) {
    		return true;
    	}
    	throw new IllegalStateException("Strange things happen");
    }
    
    private <D extends ItemDelta, I extends Item> D createEmptyDelta(PropertyPath propertyPath, ItemDefinition itemDef,
    		Class<D> deltaType, Class<I> itemType) {
    
    	if (PrismProperty.class.isAssignableFrom(itemType)) {
    		return (D) new PropertyDelta(propertyPath, (PrismPropertyDefinition)itemDef);
    	} else if (PrismContainer.class.isAssignableFrom(itemType)) {
    		return (D) new ContainerDelta(propertyPath, (PrismContainerDefinition)itemDef);
    	} else if (PrismReference.class.isAssignableFrom(itemType)) {
    		return (D) new ReferenceDelta(propertyPath, (PrismReferenceDefinition)itemDef);
    	} else {
    		throw new IllegalArgumentException("Unknown item type "+itemType);
    	}
    }

    public Class<T> getObjectTypeClass() {
        return objectTypeClass;
    }

    public void setObjectTypeClass(Class<T> objectTypeClass) {
        this.objectTypeClass = objectTypeClass;
    }

    /**
     * Top-level path is assumed.
     */
    public PropertyDelta findPropertyDelta(QName propertyName) {
        return findPropertyDelta(new PropertyPath(propertyName));
    }

    public PropertyDelta findPropertyDelta(PropertyPath parentPath, QName propertyName) {
        return findPropertyDelta(new PropertyPath(parentPath, propertyName));
    }
    
    public PropertyDelta findPropertyDelta(PropertyPath propertyPath) {
    	return findItemDelta(propertyPath, PropertyDelta.class, PrismProperty.class);
    }
    
    public <X extends Containerable> ContainerDelta<X> findContainerDelta(PropertyPath propertyPath) {
    	return findItemDelta(propertyPath, ContainerDelta.class, PrismContainer.class);
    }

    public <X extends Containerable> ContainerDelta<X> findContainerDelta(QName name) {
    	return findContainerDelta(new PropertyPath(name));
    }

    private <D extends ItemDelta> D findModification(PropertyPath propertyPath, Class<D> deltaType) {
    	return ItemDelta.findItemDelta(modifications, propertyPath, deltaType);
    }
    
    private  <D extends ItemDelta> D findModification(QName itemName, Class<D> deltaType) {
    	return findModification(new PropertyPath(itemName), deltaType);
    }
    
    public ReferenceDelta findReferenceModification(QName itemName) {
    	return findModification(itemName, ReferenceDelta.class);
    }
    
    private <D extends ItemDelta> void removeModification(PropertyPath propertyPath, Class<D> deltaType) {
    	ItemDelta.removeItemDelta(modifications, propertyPath, deltaType);
    }
    
    private <D extends ItemDelta> void removeModification(QName itemName, Class<D> deltaType) {
    	removeModification(new PropertyPath(itemName), deltaType);
    }
    
    public void removeReferenceModification(QName itemName) {
    	removeModification(itemName, ReferenceDelta.class);
    }

    public boolean isEmpty() {
    	if (getChangeType() == ChangeType.DELETE) {
    		// Delete delta is never empty
    		return false;
    	}
    	if (getChangeType() == ChangeType.ADD) {
    		return objectToAdd == null || objectToAdd.isEmpty();
    	}
        if (modifications == null || modifications.isEmpty()) {
        	return true;
        }
        for (ItemDelta<?> mod: modifications) {
        	if (!mod.isEmpty()) {
        		return false;
        	}
        }
        return true;
    }
    
    public void applyDefinition(PrismObjectDefinition<T> definition) throws SchemaException {
    	if (objectToAdd != null) {
    		objectToAdd.applyDefinition(definition);
    	}
    	ItemDelta.applyDefinition(getModifications(), definition);
    }

    /**
     * Semi-deep clone.
     */
    public ObjectDelta<T> clone() {
        ObjectDelta<T> clone = new ObjectDelta<T>(this.objectTypeClass, this.changeType, this.prismContext);
        clone.oid = this.oid;
        clone.modifications = createEmptyModifications();
        clone.modifications.addAll((Collection)this.modifications);
        if (this.objectToAdd == null) {
            clone.objectToAdd = null;
        } else {
            clone.objectToAdd = this.objectToAdd.clone();
        }
        return clone;
    }

    /**
     * Merge provided delta into this delta.
     * This delta is assumed to be chronologically earlier.
     */
    public void merge(ObjectDelta<T> deltaToMerge) throws SchemaException {
        if (changeType == ChangeType.ADD) {
            if (deltaToMerge.changeType == ChangeType.ADD) {
                // Maybe we can, be we do not want. This is usually an error anyway.
                throw new IllegalArgumentException("Cannot merge two ADD deltas: " + this + ", " + deltaToMerge);
            } else if (deltaToMerge.changeType == ChangeType.MODIFY) {
                if (objectToAdd == null) {
                    throw new IllegalStateException("objectToAdd is null");
                }
                deltaToMerge.applyTo(objectToAdd);
            } else if (deltaToMerge.changeType == ChangeType.DELETE) {
                this.changeType = ChangeType.DELETE;
            }
        } else if (changeType == ChangeType.MODIFY) {
            if (deltaToMerge.changeType == ChangeType.ADD) {
                throw new IllegalArgumentException("Cannot merge 'add' delta to a 'modify' object delta");
            } else if (deltaToMerge.changeType == ChangeType.MODIFY) {
            	mergeModifications(deltaToMerge.modifications);
            } else if (deltaToMerge.changeType == ChangeType.DELETE) {
                this.changeType = ChangeType.DELETE;
            }
        } else { // DELETE
            if (deltaToMerge.changeType == ChangeType.ADD) {
                this.changeType = ChangeType.ADD;
                // TODO: clone?
                this.objectToAdd = deltaToMerge.objectToAdd;
            } else if (deltaToMerge.changeType == ChangeType.MODIFY) {
                // Just ignore the modification of a deleted object
            } else if (deltaToMerge.changeType == ChangeType.DELETE) {
                // Nothing to do
            }
        }
    }

    /**
     * Union of several object deltas. The deltas are merged to create a single delta
     * that contains changes from all the deltas.
     */
    public static <T extends Objectable> ObjectDelta<T> union(ObjectDelta<T>... deltas) throws SchemaException {
        List<ObjectDelta<T>> modifyDeltas = new ArrayList<ObjectDelta<T>>(deltas.length);
        ObjectDelta<T> addDelta = null;
        ObjectDelta<T> deleteDelta = null;
        for (ObjectDelta<T> delta : deltas) {
            if (delta == null) {
                continue;
            }
            if (delta.changeType == ChangeType.MODIFY) {
                modifyDeltas.add(delta);
            } else if (delta.changeType == ChangeType.ADD) {
                if (addDelta != null) {
                    // Maybe we can, be we do not want. This is usually an error anyway.
                    throw new IllegalArgumentException("Cannot merge two add deltas: " + addDelta + ", " + delta);
                }
                addDelta = delta;
            } else if (delta.changeType == ChangeType.DELETE) {
                deleteDelta = delta;
            }

        }

        if (deleteDelta != null && addDelta == null) {
            // Merging DELETE with anything except ADD is still a DELETE
            return deleteDelta.clone();
        }

        if (deleteDelta != null && addDelta != null) {
            throw new IllegalArgumentException("Cannot merge add and delete deltas: " + addDelta + ", " + deleteDelta);
        }

        if (addDelta != null) {
            return mergeToDelta(addDelta, modifyDeltas);
        } else {
            if (modifyDeltas.size() == 0) {
                return null;
            }
            if (modifyDeltas.size() == 1) {
                return modifyDeltas.get(0);
            }
            return mergeToDelta(modifyDeltas.get(0), modifyDeltas.subList(1, modifyDeltas.size()));
        }
    }

    private static <T extends Objectable> ObjectDelta<T> mergeToDelta(ObjectDelta<T> firstDelta,
            List<ObjectDelta<T>> modifyDeltas) throws SchemaException {
        if (modifyDeltas.size() == 0) {
            return firstDelta;
        }
        ObjectDelta<T> delta = firstDelta.clone();
        for (ObjectDelta<T> modifyDelta : modifyDeltas) {
            if (modifyDelta == null) {
                continue;
            }
            if (modifyDelta.changeType != ChangeType.MODIFY) {
                throw new IllegalArgumentException("Can only merge MODIFY changes, got " + modifyDelta.changeType);
            }
            delta.mergeModifications(modifyDelta.modifications);
        }
        return delta;
    }
    
    public void mergeModifications(Collection<? extends ItemDelta> modificationsToMerge) throws SchemaException {
        for (ItemDelta<?> propDelta : modificationsToMerge) {
        	mergeModification(propDelta);
        }
    }
    
    public void mergeModification(ItemDelta<?> modificationToMerge) throws SchemaException {
        if (changeType == ChangeType.ADD) {
        	modificationToMerge.applyTo(objectToAdd);
        } else if (changeType == ChangeType.MODIFY) {
        	ItemDelta myDelta = findModification(modificationToMerge.getPath(), ItemDelta.class);
            if (myDelta == null) {
                addModification(modificationToMerge.clone());
            } else {
                myDelta.merge(modificationToMerge);
            }
        } // else it is DELETE. There's nothing to do. Merging anything to delete is still delete
    }


    /**
     * Applies this object delta to specified object, returns updated object.
     * It modifies the provided object.
     */
    public void applyTo(PrismObject<T> targetObject) throws SchemaException {
    	if (isEmpty()) {
    		// nothing to do
    		return;
    	}
        if (changeType != ChangeType.MODIFY) {
            throw new IllegalStateException("Can apply only MODIFY delta to object, got " + changeType + " delta");
        }
        for (ItemDelta itemDelta : modifications) {
            itemDelta.applyTo(targetObject);
        }
    }

    /**
     * Applies this object delta to specified object, returns updated object.
     * It leaves the original object unchanged.
     *
     * @param objectOld object before change
     * @return object with applied changes or null if the object should not exit (was deleted)
     */
    public PrismObject<T> computeChangedObject(PrismObject<T> objectOld) throws SchemaException {
        if (objectOld == null) {
            if (getChangeType() == ChangeType.ADD) {
                objectOld = getObjectToAdd();
            } else {
                //throw new IllegalStateException("Cannot apply "+getChangeType()+" delta to a null old object");
                // This seems to be quite OK
                return null;
            }
        }
        if (getChangeType() == ChangeType.DELETE) {
            return null;
        }
        // MODIFY change
        PrismObject<T> objectNew = objectOld.clone();
        for (ItemDelta modification : modifications) {
            modification.applyTo(objectNew);
        }
        return objectNew;
    }

    /**
     * Incorporates the property delta into the existing property deltas
     * (regardless of the change type).
     */
    public void swallow(ItemDelta<?> newItemDelta) throws SchemaException {
        if (changeType == ChangeType.MODIFY) {
            // TODO: check for conflict
            addModification(newItemDelta);
        } else if (changeType == ChangeType.ADD) {
//        	Class<?> valueClass = newPropertyDelta.getValueClass();
            PrismProperty<?> property = objectToAdd.findOrCreateProperty(new PropertyPath(newItemDelta.getParentPath(), newItemDelta.getName()));
            newItemDelta.applyTo(property);
        }
        // nothing to do for DELETE
    }

    private Collection<? extends ItemDelta> createEmptyModifications() {
    	// Lists are easier to debug
        return new ArrayList<ItemDelta>();
    }
    
    public <X> PropertyDelta<X> createPropertyModification(QName name, PrismPropertyDefinition propertyDefinition) {
    	PropertyDelta<X> propertyDelta = new PropertyDelta<X>(name, propertyDefinition);
    	addModification(propertyDelta);
    	return propertyDelta;
    }
    
    public <X> PropertyDelta<X> createPropertyModification(PropertyPath path) {
	    PrismObjectDefinition<T> objDef = getPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(getObjectTypeClass());
		PrismPropertyDefinition propDef = objDef.findPropertyDefinition(path);
		return createPropertyModification(path, propDef);
    }
	    
    public <X> PropertyDelta<X> createPropertyModification(PropertyPath path, PrismPropertyDefinition propertyDefinition) {
    	PropertyDelta<X> propertyDelta = new PropertyDelta<X>(path, propertyDefinition);
    	addModification(propertyDelta);
    	return propertyDelta;
    }
    
    public ReferenceDelta createReferenceModification(QName name, PrismReferenceDefinition referenceDefinition) {
    	ReferenceDelta referenceDelta = new ReferenceDelta(name, referenceDefinition);
    	addModification(referenceDelta);
    	return referenceDelta;
    }
    
    /**
     * Convenience method for quick creation of object deltas that replace a single object property. This is used quite often
     * to justify a separate method. 
     */
    public static <O extends Objectable, X> ObjectDelta<O> createModificationReplaceProperty(Class<O> type, String oid, QName propertyName,
    		PrismContext prismContext, X... propertyValues) {
    	PropertyPath propertyPath = new PropertyPath(propertyName);
    	return createModificationReplaceProperty(type, oid, propertyPath, prismContext, propertyValues);
    }
    
    /**
     * Convenience method for quick creation of object deltas that replace a single object property. This is used quite often
     * to justify a separate method. 
     */
    public static <O extends Objectable, X> ObjectDelta<O> createModificationReplaceProperty(Class<O> type, String oid, 
    		PropertyPath propertyPath, PrismContext prismContext, X... propertyValues) {
    	ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
    	objectDelta.setOid(oid);
    	fillInModificationReplaceProperty(objectDelta, propertyPath, propertyValues);
    	return objectDelta;
    }
    
    public static <O extends Objectable, X> ObjectDelta<O> createModificationAddProperty(Class<O> type, String oid, 
    		PropertyPath propertyPath, PrismContext prismContext, X... propertyValues) {
    	ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
    	objectDelta.setOid(oid);
    	fillInModificationAddProperty(objectDelta, propertyPath, propertyValues);
    	return objectDelta;
    }
    
    public static <O extends Objectable, X> ObjectDelta<O> createModificationDeleteProperty(Class<O> type, String oid, 
    		PropertyPath propertyPath, PrismContext prismContext, X... propertyValues) {
    	ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
    	objectDelta.setOid(oid);
    	fillInModificationDeleteProperty(objectDelta, propertyPath, propertyValues);
    	return objectDelta;
    }
    
    public <X> void addModificationReplaceProperty(QName propertyQName, X... propertyValues) {
    	addModificationReplaceProperty(new PropertyPath(propertyQName), propertyValues);
    }
    
    public <X> void addModificationReplaceProperty(PropertyPath propertyPath, X... propertyValues) {
    	fillInModificationReplaceProperty(this, propertyPath, propertyValues);
    }
    
    public <X> void addModificationAddProperty(QName propertyQName, X... propertyValues) {
    	addModificationAddProperty(new PropertyPath(propertyQName), propertyValues);
    }
    
    public <X> void addModificationAddProperty(PropertyPath propertyPath, X... propertyValues) {
    	fillInModificationAddProperty(this, propertyPath, propertyValues);
    }
    
    public <X> void addModificationDeleteProperty(QName propertyQName, X... propertyValues) {
    	addModificationDeleteProperty(new PropertyPath(propertyQName), propertyValues);
    }
     
    public <X> void addModificationDeleteProperty(PropertyPath propertyPath, X... propertyValues) {
    	fillInModificationDeleteProperty(this, propertyPath, propertyValues);
    }
    
    protected static <O extends Objectable, X> void fillInModificationReplaceProperty(ObjectDelta<O> objectDelta,
    		PropertyPath propertyPath, X... propertyValues) {
    	PropertyDelta<X> propertyDelta = createModification(objectDelta, propertyPath);
    	if (propertyValues != null) {
	    	Collection<PrismPropertyValue<X>> valuesToReplace = toPrismPropertyValues(objectDelta.getPrismContext(), propertyValues);
	    	propertyDelta.setValuesToReplace(valuesToReplace);
    	}
    }

    protected static <O extends Objectable, X> void fillInModificationAddProperty(ObjectDelta<O> objectDelta,
    		PropertyPath propertyPath, X... propertyValues) {
    	PropertyDelta<X> propertyDelta = createModification(objectDelta, propertyPath);
    	if (propertyValues != null) {
	    	Collection<PrismPropertyValue<X>> valuesToAdd = toPrismPropertyValues(objectDelta.getPrismContext(), propertyValues);
	    	propertyDelta.addValuesToAdd(valuesToAdd);
    	}
    }
    
    protected static <O extends Objectable, X> void fillInModificationDeleteProperty(ObjectDelta<O> objectDelta,
    		PropertyPath propertyPath, X... propertyValues) {
    	PropertyDelta<X> propertyDelta = createModification(objectDelta, propertyPath);
    	if (propertyValues != null) {
	    	Collection<PrismPropertyValue<X>> valuesToDelete = toPrismPropertyValues(objectDelta.getPrismContext(), propertyValues);
	    	propertyDelta.addValuesToDelete(valuesToDelete);
    	}
    }
    
    protected static <O extends Objectable, X> PropertyDelta<X> createModification(ObjectDelta<O> objectDelta, PropertyPath propertyPath) {
    	PrismObjectDefinition<O> objDef = objectDelta.getPrismContext().getSchemaRegistry().findObjectDefinitionByCompileTimeClass(objectDelta.getObjectTypeClass());
    	PrismPropertyDefinition propDef = objDef.findPropertyDefinition(propertyPath);
    	return objectDelta.createPropertyModification(propertyPath, propDef);
    }
    
    protected static <X> Collection<PrismPropertyValue<X>> toPrismPropertyValues(PrismContext prismContext, X... propertyValues) {
    	Collection<PrismPropertyValue<X>> pvalues = new ArrayList<PrismPropertyValue<X>>(propertyValues.length);
    	for (X val: propertyValues) {
    		if (val instanceof PolyString) {
    			PolyString polyStringVal = (PolyString)val;
    			if (!polyStringVal.isComputed()) {
    				polyStringVal.recompute(prismContext.getDefaultPolyStringNormalizer());
    			}
    		}
    		PrismPropertyValue<X> pval = new PrismPropertyValue<X>(val);
    		pvalues.add(pval);
    	}
    	return pvalues;
    }
    
    /**
     * Convenience method for quick creation of object deltas that replace a single object property. This is used quite often
     * to justify a separate method. 
     */
    public static <O extends Objectable> ObjectDelta<O> createModificationAddReference(Class<O> type, String oid, QName propertyName,
    		PrismContext prismContext, PrismObject<?>... referenceObjects) {
    	ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.MODIFY, prismContext);
    	objectDelta.setOid(oid);
    	PrismObjectDefinition<O> objDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(type);
    	PrismReferenceDefinition refDef = objDef.findReferenceDefinition(propertyName);
    	ReferenceDelta referenceDelta = objectDelta.createReferenceModification(propertyName, refDef);
    	Collection<PrismReferenceValue> valuesToReplace = new ArrayList<PrismReferenceValue>(referenceObjects.length);
    	for (PrismObject<?> refObject: referenceObjects) {
    		PrismReferenceValue refVal = new PrismReferenceValue();
    		refVal.setObject(refObject);
    		valuesToReplace.add(refVal);
    	}
    	referenceDelta.setValuesToReplace(valuesToReplace);
    	return objectDelta;
    }
    
    public static <T extends Objectable> ObjectDelta<T> createModifyDelta(String oid, Collection<? extends ItemDelta> modifications,
    		Class<T> objectTypeClass, PrismContext prismContext) {
    	ObjectDelta<T> objectDelta = new ObjectDelta<T>(objectTypeClass, ChangeType.MODIFY, prismContext);
    	objectDelta.addModifications(modifications);
    	objectDelta.setOid(oid);
    	return objectDelta;
    }
    
    public static <O extends Objectable> ObjectDelta<O> createEmptyAddDelta(Class<O> type, String oid, PrismContext prismContext) {
    	ObjectDelta<O> objectDelta = createEmptyDelta(type, oid, prismContext, ChangeType.ADD);
    	PrismObjectDefinition<O> objDef = prismContext.getSchemaRegistry().findObjectDefinitionByCompileTimeClass(type);
    	PrismObject<O> objectToAdd = objDef.instantiate();
    	objectDelta.setObjectToAdd(objectToAdd);
    	return objectDelta;
    }

    public static <O extends Objectable> ObjectDelta<O> createEmptyModifyDelta(Class<O> type, String oid, PrismContext prismContext) {
    	return createEmptyDelta(type, oid, prismContext, ChangeType.MODIFY);
    }

    public static <O extends Objectable> ObjectDelta<O> createEmptyDeleteDelta(Class<O> type, String oid, PrismContext prismContext) {
    	return createEmptyDelta(type, oid, prismContext, ChangeType.DELETE);
    }

    public static <O extends Objectable> ObjectDelta<O> createEmptyDelta(Class<O> type, String oid, PrismContext prismContext, 
    		ChangeType changeType) {
    	ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, changeType, prismContext);
    	objectDelta.setOid(oid);
    	return objectDelta;
    }
    
    public static <O extends Objectable> ObjectDelta<O> createAddDelta(PrismObject<O> objectToAdd) {
    	ObjectDelta<O> objectDelta = new ObjectDelta<O>(objectToAdd.getCompileTimeClass(), ChangeType.ADD, objectToAdd.getPrismContext());
    	objectDelta.setOid(objectToAdd.getOid());
    	objectDelta.setObjectToAdd(objectToAdd);
    	return objectDelta;
    }
    
    public static <O extends Objectable> ObjectDelta<O> createDeleteDelta(Class<O> type, String oid, PrismContext prismContext) {
    	ObjectDelta<O> objectDelta = new ObjectDelta<O>(type, ChangeType.DELETE, prismContext);
    	objectDelta.setOid(oid);
    	return objectDelta;
    }
        
    public void checkConsistence() {
    	checkConsistence(true, false, false);
    }
    
    public void checkConsistence(boolean requireOid, boolean requireDefinition, boolean prohibitRaw) {
    	if (getChangeType() == ChangeType.ADD) {
			if (getModifications() != null && !getModifications().isEmpty()) {
				throw new IllegalStateException("Modifications present in ADD delta "+this);
			}
			if (getObjectToAdd() != null) {
				getObjectToAdd().checkConsistence(requireDefinition, prohibitRaw);
			} else {
				throw new IllegalStateException("User primary delta is ADD, but there is not object to add in "+this);
			}
		} else if (getChangeType() == ChangeType.MODIFY) {
			checkIdentifierConsistence(requireOid);
			if (getObjectToAdd() != null) {
				throw new IllegalStateException("Object to add present in MODIFY delta "+this);
			}
			if (getModifications() == null) {
				throw new IllegalStateException("Null modification in MODIFY delta "+this);
			}
			ItemDelta.checkConsistence(getModifications(), requireDefinition, prohibitRaw);
		} else if (getChangeType() == ChangeType.DELETE) {
	    	if (requireOid && getOid() == null) {
	    		throw new IllegalStateException("Null oid in delta "+this);
	    	}
			if (getObjectToAdd() != null) {
				throw new IllegalStateException("Object to add present in DELETE delta "+this);
			}
			if (getModifications() != null && !getModifications().isEmpty()) {
				throw new IllegalStateException("Modifications present in DELETE delta "+this);
			}			
		} else {
			throw new IllegalStateException("Unknown change type "+getChangeType()+" in delta "+this);
		}
    }
    
	protected void checkIdentifierConsistence(boolean requireOid) {
		if (requireOid && getOid() == null) {
    		throw new IllegalStateException("Null oid in delta "+this);
    	}
	}

	public static void checkConsistence(Collection<? extends ObjectDelta<?>> deltas) {
		for (ObjectDelta<?> delta: deltas) {
			delta.checkConsistence();
		}
	}
    
    public void assertDefinitions() throws SchemaException {
    	assertDefinitions("");
    }
    
    public void assertDefinitions(String sourceDescription) throws SchemaException {
    	assertDefinitions(false, sourceDescription);
    }
    
    public void assertDefinitions(boolean tolerateRawElements) throws SchemaException {
    	assertDefinitions(tolerateRawElements, "");
    }
    
    /**
     * Assert that all the items has appropriate definition.
     */
    public void assertDefinitions(boolean tolerateRawElements, String sourceDescription) throws SchemaException {
    	if (changeType == ChangeType.ADD) {
    		objectToAdd.assertDefinitions("add delta in "+sourceDescription);
    	}
    	if (changeType == ChangeType.MODIFY) {
    		for (ItemDelta<?> mod: modifications) {
    			mod.assertDefinitions(tolerateRawElements, "modify delta for "+getOid()+" in "+sourceDescription);
    		}
    	}
    }
    
    public void revive(PrismContext prismContext) {
    	if (objectToAdd != null) {
    		objectToAdd.revive(prismContext);
    	}
    	if (modifications != null) {
    		for (ItemDelta modification: modifications) {
    			modification.revive(prismContext);
    		}
    	}
	}
    
    public void applyDefinition(PrismObjectDefinition<T> objectDefinition, boolean force) throws SchemaException {
    	if (objectToAdd != null) {
    		objectToAdd.applyDefinition(objectDefinition, force);
    	}
    	if (modifications != null) {
    		for (ItemDelta modification: modifications) {
    			PropertyPath path = modification.getPath();
    			ItemDefinition itemDefinition = objectDefinition.findItemDefinition(path);
    			modification.applyDefinition(itemDefinition, force);
    		}
    	}
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(debugName());
        sb.append("(").append(debugIdentifiers());
        sb.append(",").append(changeType).append(": ");
        if (changeType == ChangeType.ADD) {
            if (objectToAdd == null) {
                sb.append("null");
            } else {
                sb.append(objectToAdd.toString());
            }
        } else if (changeType == ChangeType.MODIFY) {
            Iterator<? extends ItemDelta> i = modifications.iterator();
            while (i.hasNext()) {
                sb.append(i.next().toString());
                if (i.hasNext()) {
                    sb.append(", ");
                }
            }
        }
        // Nothing to print for delete
        sb.append(")");
        return sb.toString();
    }
    
    protected String debugName() {
    	return "ObjectDelta";
    }
    
    protected String debugIdentifiers() {
    	return "oid=" + getOid();
    }

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

    @Override
    public String debugDump(int indent) {
        StringBuilder sb = new StringBuilder();
        DebugUtil.indentDebugDump(sb, indent);
        sb.append(debugName());
        sb.append("<").append(objectTypeClass.getSimpleName()).append(">(");
        sb.append(debugIdentifiers()).append(",").append(changeType).append("):\n");
        if (objectToAdd == null) {
        	if (changeType == ChangeType.ADD) {
	        	DebugUtil.indentDebugDump(sb, indent + 1);
	            sb.append("null");
        	}
        } else {
            sb.append(objectToAdd.debugDump(indent + 1));
        }
        if (modifications != null) {
            Iterator<? extends ItemDelta> i = modifications.iterator();
            while (i.hasNext()) {
                sb.append(i.next().debugDump(indent + 1));
                if (i.hasNext()) {
                    sb.append("\n");
                }
            }
        }
        // Nothing to print for delete
        return sb.toString();
    }

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


}

{'fraction_non_alphanumeric': 0.0963302752293578, 'fraction_numerical': 0.0005989165055944246, 'mean_word_length': 3.0738050349340136, 'pattern_counts': {'":': 2, '<': 387, '<?xml version=': 0, '>': 387, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 2}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 2, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5046488', 'n_tokens_mistral': 20724, 'n_tokens_neox': 20131, 'n_words': 4857}