/* * Copyright (c) 2010-2015 Evolveum * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.evolveum.midpoint.prism; import com.evolveum.midpoint.prism.delta.ItemDelta; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.util.DebugDumpable; import com.evolveum.midpoint.util.DebugUtil; import com.evolveum.midpoint.util.MiscUtil; import com.evolveum.midpoint.util.exception.SchemaException; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.w3c.dom.Element; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; /** * @author semancik * */ public abstract class PrismValue implements IPrismValue { private OriginType originType; private Objectable originObject; private Itemable parent; private transient Map<String,Object> userData = new HashMap<>(); protected boolean immutable; transient protected PrismContext prismContext; PrismValue() { } PrismValue(PrismContext prismContext) { this.prismContext = prismContext; } PrismValue(OriginType type, Objectable source) { this(null, type, source); } PrismValue(PrismContext prismContext, OriginType type, Objectable source) { this.prismContext = prismContext; this.originType = type; this.originObject = source; } PrismValue(PrismContext prismContext, OriginType type, Objectable source, Itemable parent) { this.prismContext = prismContext; this.originType = type; this.originObject = source; this.parent = parent; } public void setPrismContext(PrismContext prismContext) { this.prismContext = prismContext; } public void setOriginObject(Objectable source) { this.originObject = source; } public void setOriginType(OriginType type) { this.originType = type; } @Override public OriginType getOriginType() { return originType; } @Override public Objectable getOriginObject() { return originObject; } public Map<String, Object> getUserData() { return userData; } @Override public Object getUserData(@NotNull String key) { return userData.get(key); } @Override public void setUserData(@NotNull String key, Object value) { userData.put(key, value); } @Override public Itemable getParent() { return parent; } @Override public void setParent(Itemable parent) { if (this.parent != null && parent != null && this.parent != parent) { throw new IllegalStateException("Attempt to reset value parent from "+this.parent+" to "+parent); } this.parent = parent; } @NotNull @Override public ItemPath getPath() { Itemable parent = getParent(); if (parent == null) { throw new IllegalStateException("No parent, cannot create value path for "+this); } return parent.getPath(); } /** * Used when we are removing the value from the previous parent. * Or when we know that the previous parent will be discarded and we * want to avoid unnecessary cloning. */ @Override public void clearParent() { parent = null; } public static <T> void clearParent(List<PrismPropertyValue<T>> values) { if (values == null) { return; } for (PrismPropertyValue<T> val: values) { val.clearParent(); } } @Override public PrismContext getPrismContext() { if (prismContext != null) { return prismContext; } if (parent != null) { prismContext = parent.getPrismContext(); return prismContext; } return null; } protected ItemDefinition getDefinition() { Itemable parent = getParent(); if (parent == null) { return null; } return parent.getDefinition(); } @Override public void applyDefinition(ItemDefinition definition) throws SchemaException { checkMutability(); // TODO reconsider applyDefinition(definition, true); } @Override public void applyDefinition(ItemDefinition definition, boolean force) throws SchemaException { checkMutability(); // TODO reconsider // Do nothing by default } public void revive(PrismContext prismContext) throws SchemaException { if (this.prismContext == null) { this.prismContext = prismContext; } recompute(prismContext); } /** * Recompute the value or otherwise "initialize" it before adding it to a prism tree. * This may as well do nothing if no recomputing or initialization is needed. */ @Override public void recompute() { recompute(getPrismContext()); } @Override public void accept(Visitor visitor) { visitor.visit(this); } @Override public void accept(Visitor visitor, ItemPath path, boolean recursive) { // This implementation is supposed to only work for non-hierarchical values, such as properties and references. // hierarchical values must override it. if (recursive) { accept(visitor); } else { visitor.visit(this); } } public abstract void checkConsistenceInternal(Itemable rootItem, boolean requireDefinitions, boolean prohibitRaw, ConsistencyCheckScope scope); /** * Returns true if this and other value represent the same value. * E.g. if they have the same IDs, OIDs or it is otherwise know * that they "belong together" without a deep examination of the * values. * * @param lax If we can reasonably assume that the two values belong together even if they don't have the same ID, * e.g. if they both belong to single-valued parent items. This is useful e.g. when comparing * multi-valued containers. But can cause problems when we want to be sure we are removing the correct * value. */ public boolean representsSameValue(PrismValue other, boolean lax) { return false; } public static <V extends PrismValue> boolean containsRealValue(Collection<V> collection, V value) { if (collection == null) { return false; } for (V colVal: collection) { if (colVal.equalsRealValue(value)) { return true; } } return false; } public static <V extends PrismValue> boolean equalsRealValues(Collection<V> collection1, Collection<V> collection2) { return MiscUtil.unorderedCollectionEquals(collection1, collection2, (v1, v2) -> v1.equalsRealValue(v2)); } public static <V extends PrismValue> boolean containsAll(Collection<V> thisSet, Collection<V> otherSet, boolean ignoreMetadata, boolean isLiteral) { if (thisSet == null && otherSet == null) { return true; } if (otherSet == null) { return true; } if (thisSet == null) { return false; } for (V otherValue: otherSet) { if (!contains(thisSet, otherValue, ignoreMetadata, isLiteral)) { return false; } } return true; } public static <V extends PrismValue> boolean contains(Collection<V> thisSet, V otherValue, boolean ignoreMetadata, boolean isLiteral) { for (V thisValue: thisSet) { if (thisValue.equalsComplex(otherValue, ignoreMetadata, isLiteral)) { return true; } } return false; } @Override public void normalize() { // do nothing by default } public static <X extends PrismValue> Collection<X> cloneValues(Collection<X> values) { Collection<X> clonedCollection = new ArrayList<X>(values.size()); for (X val: values) { clonedCollection.add((X) val.clone()); } return clonedCollection; } public abstract PrismValue clone(); protected void copyValues(PrismValue clone) { clone.originType = this.originType; clone.originObject = this.originObject; // Do not clone parent. The clone will most likely go to a different prism // and setting the parent will make it difficult to add it there. clone.parent = null; // Do not clone immutable flag. if (clone.prismContext == null) { clone.prismContext = this.prismContext; } } @NotNull public static <T extends PrismValue> Collection<T> cloneCollection(Collection<T> values) { Collection<T> clones = new ArrayList<T>(); if (values != null) { for (T value : values) { clones.add((T) value.clone()); } } return clones; } /** * Sets all parents to null. This is good if the items are to be "transplanted" into a * different Containerable. */ public static <T extends PrismValue> Collection<T> resetParentCollection(Collection<T> values) { for (T value: values) { value.setParent(null); } return values; } @Override public int hashCode() { int result = 1; return result; } public boolean equalsComplex(PrismValue other, boolean ignoreMetadata, boolean isLiteral) { // parent is not considered at all. it is not relevant. // neither the immutable flag if (!ignoreMetadata) { if (originObject == null) { if (other.originObject != null) return false; } else if (!originObject.equals(other.originObject)) return false; if (originType != other.originType) return false; } return true; } @Override public boolean equals(PrismValue otherValue, boolean ignoreMetadata) { return equalsComplex(otherValue, ignoreMetadata, false); } public boolean equals(PrismValue thisValue, PrismValue otherValue) { if (thisValue == null && otherValue == null) { return true; } if (thisValue == null || otherValue == null) { return false; } return thisValue.equalsComplex(otherValue, false, false); } public boolean equalsRealValue(PrismValue otherValue) { return equalsComplex(otherValue, true, false); } public boolean equalsRealValue(PrismValue thisValue, PrismValue otherValue) { if (thisValue == null && otherValue == null) { return true; } if (thisValue == null || otherValue == null) { return false; } return thisValue.equalsComplex(otherValue, true, false); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; PrismValue other = (PrismValue) obj; return equalsComplex(other, false, false); } /** * Assumes matching representations. I.e. it assumes that both this and otherValue represent the same instance of item. * E.g. the container with the same ID. */ @Override public Collection<? extends ItemDelta> diff(PrismValue otherValue) { return diff(otherValue, true, false); } /** * Assumes matching representations. I.e. it assumes that both this and otherValue represent the same instance of item. * E.g. the container with the same ID. */ @Override public Collection<? extends ItemDelta> diff(PrismValue otherValue, boolean ignoreMetadata, boolean isLiteral) { Collection<? extends ItemDelta> itemDeltas = new ArrayList<ItemDelta>(); diffMatchingRepresentation(otherValue, itemDeltas, ignoreMetadata, isLiteral); return itemDeltas; } void diffMatchingRepresentation(PrismValue otherValue, Collection<? extends ItemDelta> deltas, boolean ignoreMetadata, boolean isLiteral) { // Nothing to do by default } protected void appendOriginDump(StringBuilder builder) { if (DebugUtil.isDetailedDebugDump()) { if (getOriginType() != null || getOriginObject() != null) { builder.append(", origin: "); builder.append(getOriginType()); builder.append(":"); builder.append(getOriginObject()); } } } public static <T> Set<T> getRealValuesOfCollection(Collection<PrismPropertyValue<T>> collection) { Set<T> retval = new HashSet<T>(collection.size()); for (PrismPropertyValue<T> value : collection) { retval.add(value.getValue()); } return retval; } public static <V extends PrismValue> boolean collectionContainsEquivalentValue(Collection<V> collection, V value) { if (collection == null) { return false; } for (V collectionVal: collection) { if (collectionVal.equals(value, true)) { return true; } } return false; } @Override public boolean isImmutable() { return immutable; } public void setImmutable(boolean immutable) { this.immutable = immutable; } protected void checkMutability() { if (immutable) { throw new IllegalStateException("An attempt to modify an immutable value of " + toHumanReadableString()); } } @Nullable abstract public Class<?> getRealClass(); @Nullable abstract public <T> T getRealValue(); // Returns a root of PrismValue tree. For example, if we have a AccessCertificationWorkItemType that has a parent (owner) // of AccessCertificationCaseType, which has a parent of AccessCertificationCampaignType, this method returns the PCV // of AccessCertificationCampaignType. // // Generally, this method returns either "this" (PrismValue) or a PrismContainerValue. public PrismValue getRootValue() { PrismValue current = this; for (;;) { PrismContainerValue<?> parent = getParentContainerValue(current); if (parent == null) { return current; } current = parent; } } public static PrismContainerValue<?> getParentContainerValue(PrismValue value) { Itemable parent = value.getParent(); if (parent instanceof Item) { PrismValue parentParent = ((Item) parent).getParent(); return parentParent instanceof PrismContainerValue ? (PrismContainerValue) parentParent : null; } else { return null; } } public PrismContainerValue<?> getParentContainerValue() { return getParentContainerValue(this); } }