com.evolveum.midpoint.prism.path.ItemPath.java Source code

Java tutorial

Introduction

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

Source

/*
 * Copyright (c) 2010-2013 Evolveum
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.evolveum.midpoint.prism.path;

import com.evolveum.midpoint.prism.PrismConstants;
import com.evolveum.prism.xml.ns._public.types_3.ItemPathType;
import org.apache.commons.lang.Validate;

import javax.xml.namespace.QName;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

/**
 * @author semancik
 *
 */
public class ItemPath implements Serializable, Cloneable {

    public static final QName XSD_TYPE = ItemPathType.COMPLEX_TYPE;
    public static final ItemPath EMPTY_PATH = new ItemPath();

    private List<ItemPathSegment> segments;
    private Map<String, String> namespaceMap;

    public void setNamespaceMap(Map<String, String> namespaceMap) {
        this.namespaceMap = namespaceMap;
    }

    public Map<String, String> getNamespaceMap() {
        return namespaceMap;
    }

    public ItemPath() {
        segments = new ArrayList<ItemPathSegment>(0);
    }

    public ItemPath(QName... qnames) {
        this.segments = new ArrayList<>(qnames.length);
        for (QName qname : qnames) {
            add(qname);
        }
    }

    public ItemPath(String... names) {
        this.segments = new ArrayList<>(names.length);
        for (String name : names) {
            add(stringToQName(name));
        }
    }

    public ItemPath(Object[] namesOrIds) {
        this.segments = new ArrayList<>(namesOrIds.length);
        for (Object nameOrId : namesOrIds) {
            if (nameOrId instanceof QName) {
                add((QName) nameOrId);
            } else if (nameOrId instanceof String) {
                add(stringToQName((String) nameOrId));
            } else if (nameOrId instanceof Long) {
                this.segments.add(new IdItemPathSegment((Long) nameOrId));
            } else if (nameOrId instanceof Integer) {
                this.segments.add(new IdItemPathSegment(((Integer) nameOrId).longValue()));
            } else {
                throw new IllegalArgumentException("Invalid item path segment value: " + nameOrId);
            }
        }
    }

    private QName stringToQName(String name) {
        Validate.notNull(name, "name");
        if ("..".equals(name)) {
            return PrismConstants.T_PARENT;
        } else if ("@".equals(name)) {
            return PrismConstants.T_OBJECT_REFERENCE;
        } else if ("#".equals(name)) {
            return PrismConstants.T_ID;
        } else {
            return new QName(name);
        }
    }

    public ItemPath(ItemPath parentPath, QName subName) {
        this.segments = new ArrayList<>(parentPath.segments.size() + 1);
        segments.addAll(parentPath.segments);
        add(subName);
    }

    public ItemPath(ItemPath parentPath, ItemPath childPath) {
        this.segments = new ArrayList<>(parentPath.segments.size() + childPath.segments.size());
        segments.addAll(parentPath.segments);
        segments.addAll(childPath.segments);
    }

    public ItemPath(List<ItemPathSegment> segments) {
        this.segments = new ArrayList<>(segments.size());
        this.segments.addAll(segments);
    }

    public ItemPath(List<ItemPathSegment> segments, ItemPathSegment subSegment) {
        this.segments = new ArrayList<>(segments.size() + 1);
        this.segments.addAll(segments);
        this.segments.add(subSegment);
    }

    public ItemPath(List<ItemPathSegment> segments, QName subName) {
        this.segments = new ArrayList<>(segments.size() + 1);
        this.segments.addAll(segments);
        add(subName);
    }

    public ItemPath(ItemPathSegment... segments) {
        this.segments = new ArrayList<>(segments.length);
        Collections.addAll(this.segments, segments);
    }

    public ItemPath(ItemPath parentPath, ItemPathSegment subSegment) {
        this.segments = new ArrayList<>(parentPath.segments.size() + 1);
        this.segments.addAll(parentPath.segments);
        this.segments.add(subSegment);
    }

    public ItemPath subPath(QName subName) {
        return new ItemPath(segments, subName);
    }

    public ItemPath subPath(Long id) {
        return subPath(new IdItemPathSegment(id));
    }

    public ItemPath subPath(ItemPathSegment subSegment) {
        return new ItemPath(segments, subSegment);
    }

    public ItemPath subPath(ItemPath subPath) {
        ItemPath newPath = new ItemPath(segments);
        newPath.segments.addAll(subPath.getSegments());
        return newPath;
    }

    /**
     * Null-proof static version. 
     */
    public static ItemPath subPath(ItemPath prefix, ItemPathSegment subSegment) {
        if (prefix == null && subSegment == null) {
            return EMPTY_PATH;
        }
        if (prefix == null) {
            return new ItemPath(subSegment);
        }
        return prefix.subPath(subSegment);
    }

    private void add(QName qname) {
        if (PrismConstants.T_PARENT.equals(qname)) {
            this.segments.add(new ParentPathSegment());
        } else if (PrismConstants.T_OBJECT_REFERENCE.equals(qname)) {
            this.segments.add(new ObjectReferencePathSegment());
        } else if (PrismConstants.T_ID.equals(qname)) {
            this.segments.add(new IdentifierPathSegment());
        } else {
            this.segments.add(new NameItemPathSegment(qname));
        }
    }

    public List<ItemPathSegment> getSegments() {
        return segments;
    }

    public ItemPathSegment first() {
        if (segments.size() == 0) {
            return null;
        }
        return segments.get(0);
    }

    public ItemPath rest() {
        return tail();
    }

    public NameItemPathSegment lastNamed() {
        for (int i = segments.size() - 1; i >= 0; i--) {
            if (segments.get(i) instanceof NameItemPathSegment) {
                return (NameItemPathSegment) segments.get(i);
            }
        }
        return null;
    }

    public ItemPathSegment last() {
        if (segments.size() == 0) {
            return null;
        }
        return segments.get(segments.size() - 1);
    }

    /**
     * Returns first segment in a form of path.
     */
    public ItemPath head() {
        return new ItemPath(first());
    }

    /**
     * Returns path containing all segments except the first N.
     */
    public ItemPath tail(int n) {
        if (segments.size() < n) {
            return EMPTY_PATH;
        }
        return new ItemPath(segments.subList(n, segments.size()));
    }

    public ItemPath tail() {
        return tail(1);
    }

    /**
     * Returns a path containing all segments except the last one.
     */
    public ItemPath allExceptLast() {
        if (segments.size() == 0) {
            return EMPTY_PATH;
        }
        return new ItemPath(segments.subList(0, segments.size() - 1));
    }

    /**
     * Returns a path containing all segments up to (and not including) the last one.
     */
    public ItemPath allUpToLastNamed() {
        for (int i = segments.size() - 1; i >= 0; i--) {
            if (segments.get(i) instanceof NameItemPathSegment) {
                return new ItemPath(segments.subList(0, i));
            }
        }
        return EMPTY_PATH;
    }

    /**
     * Returns a path containing all segments up to (not including) the specified one;
     * counted from backwards.
     * If the segment is not present, returns empty path.
     */
    public ItemPath allUpTo(ItemPathSegment segment) {
        int i = segments.lastIndexOf(segment);
        if (i < 0) {
            return EMPTY_PATH;
        } else {
            return new ItemPath(segments.subList(0, i));
        }
    }

    /**
     * Returns a path containing all segments up to (including) the specified one;
     * counted from backwards.
     * If the segment is not present, returns empty path.
     */
    public ItemPath allUpToIncluding(ItemPathSegment segment) {
        int i = segments.lastIndexOf(segment);
        if (i < 0) {
            return EMPTY_PATH;
        } else {
            return allUpToIncluding(i);
        }
    }

    public ItemPath allUpToIncluding(int i) {
        return new ItemPath(segments.subList(0, i + 1));
    }

    public int size() {
        return segments.size();
    }

    public boolean isEmpty() {
        return segments.isEmpty();
    }

    /**
     * Makes the path "normal" by inserting null Id segments where they were omitted. 
     */
    public ItemPath normalize() {
        ItemPath normalizedPath = new ItemPath();
        ItemPathSegment lastSegment = null;
        Iterator<ItemPathSegment> iterator = segments.iterator();
        while (iterator.hasNext()) {
            ItemPathSegment origSegment = iterator.next();
            if (lastSegment != null && !(lastSegment instanceof IdItemPathSegment)
                    && !(origSegment instanceof IdItemPathSegment)) {
                normalizedPath.segments.add(new IdItemPathSegment());
            }
            normalizedPath.segments.add(origSegment);
            lastSegment = origSegment;
        }
        if (lastSegment != null && !(lastSegment instanceof IdItemPathSegment) &&
        // Make sure we do not insert the Id segment as a last one. That is not correct and it would spoil comparing paths
                iterator.hasNext()) {
            normalizedPath.segments.add(new IdItemPathSegment());
        }
        return normalizedPath;
    }

    public CompareResult compareComplex(ItemPath otherPath) {
        ItemPath thisNormalized = this.normalize();
        ItemPath otherNormalized = otherPath == null ? EMPTY_PATH : otherPath.normalize();
        int i = 0;
        while (i < thisNormalized.segments.size() && i < otherNormalized.segments.size()) {
            ItemPathSegment thisSegment = thisNormalized.segments.get(i);
            ItemPathSegment otherSegment = otherNormalized.segments.get(i);
            if (!thisSegment.equivalent(otherSegment)) {
                return CompareResult.NO_RELATION;
            }
            i++;
        }
        if (i < thisNormalized.size()) {
            return CompareResult.SUPERPATH;
        }
        if (i < otherNormalized.size()) {
            return CompareResult.SUBPATH;
        }
        return CompareResult.EQUIVALENT;
    }

    public static boolean containsEquivalent(Collection<ItemPath> paths, ItemPath pathToBeFound) {
        for (ItemPath path : paths) {
            if (path.equivalent(pathToBeFound)) {
                return true;
            }
        }
        return false;
    }

    public static boolean containsSubpathOrEquivalent(Collection<ItemPath> paths, ItemPath pathToBeFound) {
        for (ItemPath path : paths) {
            CompareResult r = pathToBeFound.compareComplex(path);
            if (r == CompareResult.SUBPATH || r == CompareResult.EQUIVALENT) {
                return true;
            }
        }
        return false;
    }

    public ItemPath namedSegmentsOnly() {
        ItemPath rv = new ItemPath();
        for (ItemPathSegment segment : segments) {
            if (segment instanceof NameItemPathSegment) {
                rv.add(((NameItemPathSegment) segment).getName());
            }
        }
        return rv;
    }

    public static boolean isNullOrEmpty(ItemPath itemPath) {
        return itemPath == null || itemPath.isEmpty();
    }

    public static boolean containsSingleNameSegment(ItemPath path) {
        return path != null && path.size() == 1 && path.first() instanceof NameItemPathSegment;
    }

    public boolean startsWith(Class<? extends ItemPathSegment> clazz) {
        if (isEmpty()) {
            return false;
        } else {
            return clazz.isAssignableFrom(first().getClass());
        }
    }

    public boolean startsWith(ItemPath other) {
        if (other == null) {
            return true;
        }
        return other.isSubPathOrEquivalent(this);
    }

    public QName asSingleName() {
        if (size() == 1 && startsWith(NameItemPathSegment.class)) {
            return ((NameItemPathSegment) first()).getName();
        } else {
            return null;
        }
    }

    public static QName asSingleName(ItemPath path) {
        return path != null ? path.asSingleName() : null;
    }

    public enum CompareResult {
        EQUIVALENT, SUPERPATH, SUBPATH, NO_RELATION;
    }

    public boolean isSubPath(ItemPath otherPath) {
        return compareComplex(otherPath) == CompareResult.SUBPATH;
    }

    public boolean isSuperPath(ItemPath otherPath) {
        return compareComplex(otherPath) == CompareResult.SUPERPATH;
    }

    public boolean isSubPathOrEquivalent(ItemPath otherPath) {
        CompareResult result = compareComplex(otherPath);
        return result == CompareResult.SUBPATH || result == CompareResult.EQUIVALENT;
    }

    /**
     * Compares two paths semantically.
     *
     * @param otherPath
     * @return
     */
    public boolean equivalent(ItemPath otherPath) {
        return compareComplex(otherPath) == CompareResult.EQUIVALENT;
    }

    public ItemPath substract(ItemPath otherPath) {
        //        return remainder(otherPath);                        // the code seems to be equivalent to the one of remainder()
        ItemPath thisNormalized = this.normalize();
        ItemPath otherNormalized = otherPath.normalize();
        if (thisNormalized.size() < otherNormalized.size()) {
            throw new IllegalArgumentException(
                    "Cannot substract path '" + otherPath + "' from '" + this + "' because it is not a subset");
        }
        int i = 0;
        while (i < otherNormalized.segments.size()) {
            ItemPathSegment thisSegment = thisNormalized.segments.get(i);
            ItemPathSegment otherSegment = otherNormalized.segments.get(i);
            if (!thisSegment.equivalent(otherSegment)) {
                throw new IllegalArgumentException("Cannot subtract segment '" + otherSegment + "' from path '"
                        + this + "' because it does not contain corresponding segment; it has '" + thisSegment
                        + "' instead.");
            }
            i++;
        }
        List<ItemPathSegment> substractSegments = thisNormalized.segments.subList(i,
                thisNormalized.segments.size());
        return new ItemPath(substractSegments);
    }

    /**
     * Returns the remainder of "this" path after passing all segments from the other path.
     * (I.e. this path must begin with the content of the other path. Throws an exception when
     * it is not the case.)
     *
     * @param otherPath
     * @return
     */
    public ItemPath remainder(ItemPath otherPath) {
        ItemPath thisNormalized = this.normalize();
        ItemPath otherNormalized = otherPath.normalize();
        if (thisNormalized.size() < otherNormalized.size()) {
            throw new IllegalArgumentException("Cannot compute remainder of path '" + this + "' after '" + otherPath
                    + "' because this path is not a superset");
        }
        int i = 0;
        while (i < otherNormalized.segments.size()) {
            ItemPathSegment thisSegment = thisNormalized.segments.get(i);
            ItemPathSegment otherSegment = otherNormalized.segments.get(i);
            if (!thisSegment.equivalent(otherSegment)) {
                throw new IllegalArgumentException("Cannot subtract segment '" + otherSegment + "' from path '"
                        + this + "' because it does not contain corresponding segment; it has '" + thisSegment
                        + "' instead.");
            }
            i++;
        }
        List<ItemPathSegment> substractSegments = thisNormalized.segments.subList(i,
                thisNormalized.segments.size());
        return new ItemPath(substractSegments);
    }

    /**
     * Convenience static method with checks
     * @throw IllegalArgumentException
     */
    public static QName getName(ItemPathSegment segment) {
        if (segment == null) {
            return null;
        }
        if (!(segment instanceof NameItemPathSegment)) {
            throw new IllegalArgumentException("Unable to get name from non-name path segment " + segment);
        }
        return ((NameItemPathSegment) segment).getName();
    }

    public static IdItemPathSegment getFirstIdSegment(ItemPath itemPath) {
        ItemPathSegment first = itemPath.first();
        if (first instanceof IdItemPathSegment) {
            return (IdItemPathSegment) first;
        }
        return null;
    }

    public static NameItemPathSegment getFirstNameSegment(ItemPath itemPath) {
        if (itemPath == null) {
            return null;
        }
        ItemPathSegment first = itemPath.first();
        if (first instanceof NameItemPathSegment) {
            return (NameItemPathSegment) first;
        }
        if (first instanceof IdItemPathSegment) {
            return getFirstNameSegment(itemPath.rest());
        }
        return null;
    }

    public static QName getFirstName(ItemPath itemPath) {
        NameItemPathSegment nameSegment = getFirstNameSegment(itemPath);
        if (nameSegment == null) {
            return null;
        }
        return nameSegment.getName();
    }

    public static ItemPath pathRestStartingWithName(ItemPath path) {
        ItemPathSegment pathSegment = path.first();
        if (pathSegment instanceof NameItemPathSegment) {
            return path;
        } else if (pathSegment instanceof IdItemPathSegment) {
            return path.rest();
        } else {
            throw new IllegalArgumentException("Unexpected path segment " + pathSegment);
        }
    }

    public boolean containsName(QName name) {
        for (ItemPathSegment segment : segments) {
            if (segment instanceof NameItemPathSegment && ((NameItemPathSegment) segment).getName().equals(name)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        Iterator<ItemPathSegment> iterator = segments.iterator();
        while (iterator.hasNext()) {
            sb.append(iterator.next());
            if (iterator.hasNext()) {
                sb.append("/");
            }
        }
        return sb.toString();
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((segments == null) ? 0 : segments.hashCode());
        return result;
    }

    /**
     * More strict version of ItemPath comparison. Does not use any normalization
     * nor approximate matching QNames via QNameUtil.match.
     *
     * For semantic-level comparison, please use equivalent(..) method.
     *
     * @param obj
     * @return
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        ItemPath other = (ItemPath) obj;
        if (segments == null) {
            if (other.segments != null)
                return false;
        } else if (!segments.equals(other.segments))
            return false;
        return true;
    }

    public ItemPath clone() {
        ItemPath clone = new ItemPath();
        for (ItemPathSegment segment : segments) {
            clone.segments.add(segment.clone());
        }
        if (namespaceMap != null) {
            clone.namespaceMap = new HashMap<>(namespaceMap);
        }
        return clone;
    }

    public static boolean containsSpecialSymbols(ItemPath path) {
        return path != null && path.containsSpecialSymbols();
    }

    public boolean containsSpecialSymbols() {
        for (ItemPathSegment segment : segments) {
            if (segment instanceof ReferencePathSegment || segment instanceof IdentifierPathSegment) {
                return true;
            }
        }
        return false;
    }

    public static void checkNoReferences(ItemPath path) {
        if (containsSpecialSymbols(path)) {
            throw new IllegalStateException("Item path shouldn't contain references but it does: " + path);
        }
    }

}