All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.redhat.lightblue.util.Path Maven / Gradle / Ivy

/*
 Copyright 2013 Red Hat, Inc. and/or its affiliates.

 This file is part of lightblue.

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with this program.  If not, see .
 */
package com.redhat.lightblue.util;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Represents a path in a tree, of the form
 *
 * 
 *   field1.field2.index.field3...
 * 
* * where fields are identifiers, and indexes are integers denoting array * indexes. * * Paths can be used to represent fields as well as patterns. A pattern path * includes '*' for any matching index. For instance: * *
 *    user.address.1.city
 * 
* * denotes the city of the second address of the user, whereas * *
 *    user.address.*.city
 * 
* * matches all cities of addresses. * * Implementation is optimized to be fast to toString and hashCode, and does not * occupy too much memory when a lot of paths are created from a common prefix. * * $parent and $this keywords can be used to create relative paths. *
 *   $this.address // 'address' field relative to this
 *   $parent.address // 'address' field relative to parent
 *   $parent.$parent.address // 'address' field relative to grandparent
 *   $parent.address.$parent.city // 'city' relative to parent of 'address' which is relative to parent
 *   $parent.address.$parent.$parent.login // 'login' field relative to grand parent of address, which is relative to parent of this
 *   address.city.$this // points to 'address'
 * 
* * * Path objects are immutable. Use MutablePath for modifiable paths. */ public class Path implements Comparable, Serializable { private static final long serialVersionUID = 1l; public static final String ANY = "*"; public static final String PARENT = "$parent"; public static final String THIS = "$this"; public static final Path EMPTY = new Path(); public static final Path ANYPATH = new Path(ANY); private PathRep data; /** * Constructs empty path */ public Path() { data = new PathRep(); } /** * Constructs a copy of x */ public Path(Path x) { data = new PathRep(x.data); } /** * Constructs a path with x+y */ public Path(Path x, Path y) { data = new PathRep(x.data); data.append(y.data); } /** * Create a mutable path as a prefix of the given path * * @param x Source path * @param pfix If positive, the new path is a prefix of x containing pfix * elements. If negative, the new path is a prefix of x with last -pfix * elements removed. */ public Path(Path x, int pfix) { data = new PathRep(x.data, pfix); } public Path(String x) { this(); List s = parse(x); data.append(s); } protected void setData(PathRep pr) { data = pr; } protected PathRep getData() { return data; } /** * Create a deep copy of this Path. * * @return the new path */ public Path copy() { return new Path(this); } /** * Create an immutable shallow copy of this path. * * @return */ public Path immutableCopy() { return this; } /** * Create a mutable shallow copy of this path. * * @return */ public MutablePath mutableCopy() { return new MutablePath(this); } /** * Get the last path segment. * * @return */ public String getLast() { return tail(0); } /** * Get the number of path segments. * * @return */ public int numSegments() { return data.size(); } /** * Check if path is empty */ public boolean isEmpty() { return data.size() == 0; } /** * Get path segment at given index starting from the head of the segment * list. * * @param i the index * @return the path segment */ public String head(int i) { return data.get(i); } /** * Get path segment at given index starting from the tail of the segment * list. * * @param i * @return the segment data */ public String tail(int i) { return data.get(data.size() - 1 - i); } /** * Get the array index represented by the given path segment index (relative * to head). * * @param i * @return */ public int getIndex(int i) { return Integer.valueOf(data.get(i)); } /** * Check if path segment at given index (relative to head) is an array * index. * * @param i * @return */ public boolean isIndex(int i) { return Util.isNumber(data.get(i)); } /** * Returns the number of ANY elements in the path */ public int nAnys() { int n = 0; for (Iterator itr = data.iterator(); itr.hasNext();) { if (ANY.equals(itr.next())) { n++; } } return n; } @Override public int hashCode() { return data.hashCode(); } /** * Returns a new path that is a prefix of this path obtained by removing -x * elements from the end (if x is negative), or selecting x elements from * the beginning (if x is positive). If the path is a mutable path, the * returned path is a mutable path. If the path is an immutable path, the * returned path is an immutable path. * * @param x number of elements to remove from end, or include from the * beginning * @return the new path * @throws IndexOutOfBoundException number of elements to remove is greater * than number of segments available */ public Path prefix(int x) { Path p; if (this instanceof MutablePath) { p = new MutablePath((MutablePath) this, x); } else { p = new Path(this, x); } return p; } /** * Returns a new path that is a suffix of this path obtained by removing -x * elements from the beginning (if x is negative), or selecting x elements * from the end (if x is positive). If the path is a mutable path, the * returned path is a mutable path. */ public Path suffix(final int x) { Path p; if (this instanceof MutablePath) { p = new MutablePath((MutablePath) this); } else { p = new Path(this); } int n = p.data.size(); if (x >= 0) { p.data.shiftLeft(n - Math.min(n, x)); } else { p.data.shiftLeft(Math.min(n, Math.abs(x))); } return p; } /** * Check if this path matches the path pattern argument passed in. * * @param pattern the pattern path * @return true if it matches, else false */ public boolean matches(Path pattern) { if (data.size() == pattern.data.size()) { Iterator patternDataItr = pattern.data.iterator(); Iterator dataItr = data.iterator(); while (patternDataItr.hasNext() && dataItr.hasNext()) { String pat = patternDataItr.next(); String val = dataItr.next(); if (!(val.equals(pat) || pat.equals(ANY))) { return false; } } return true; } return false; } /** * Check if this path is a matching descendant of the pattern, that is: - * path matches pattern, or - a prefix of the path matches the pattern */ public boolean matchingDescendant(Path pattern) { int n = pattern.numSegments(); if (n < numSegments()) { return prefix(n).matches(pattern); } else if (n == numSegments()) { return matches(pattern); } return false; } /** * Check if this path is a matching prefix of the pattern, that is: - path * matches pattern, or - path matches a prefix of the pattern */ public boolean matchingPrefix(Path pattern) { int n = pattern.numSegments(); if (n > numSegments()) { return matches(pattern.prefix(numSegments())); } else if (n == numSegments()) { return matches(pattern); } return false; } /** * Returns this+p */ public Path add(Path p) { return new Path(this, p); } /** * Returns a path with all possible $parent and $this references removed. If * there are prefixing $parents, they will remain. */ public Path normalize() { boolean parentThisPresent = false; Iterator dataItr = data.iterator(); while (dataItr.hasNext()) { String s = dataItr.next(); if (PARENT.equals(s) || THIS.equals(s)) { parentThisPresent = true; break; } } if (parentThisPresent) { MutablePath p = new MutablePath(); dataItr = data.iterator(); while (dataItr.hasNext()) { String s = dataItr.next(); if (PARENT.equals(s) && p.numSegments() > 0) { p.pop(); } else if (!THIS.equals(s)) { p.push(s); } } return p.immutableCopy(); } else { return this; } } @Override public boolean equals(Object x) { if (x instanceof Path) { return ((Path) x).data.equals(data); } else { return false; } } @Override public int compareTo(Path x) { return x == null ? -1 : data.compareTo(x.data); } @Override public String toString() { return data.toString(); } /** * Parses the input path string (x) and appends each segment to the segments * argument. * * @param x the new paths segments to parse */ protected static List parse(String x) { List segments = new ArrayList<>(); StringBuilder buf = new StringBuilder(32); int state = parsePath(buf, x, segments); if (state == 1) { segments.add(buf.toString()); } return segments; } private static int parsePath(StringBuilder buf, String x, List segments) { int state = 0; for (int i = 0; i < x.length(); i++) { char c = x.charAt(i); state = parsePathElements(state, c, buf, i, x, segments); } return state; } private static int parsePathElements(int state, char c, StringBuilder buf, int i, String x, List segments) { switch (state) { case 0: // Beginning of path, or after . state = parsePathBeginningOrAfter(c, buf, i, x, state); break; case 1: // Parsing word state = parseWord(c, buf, segments, state); break; case 2: // Parsing end of word state = parseEndOfWord(c, i, x); break; default: state = 0; } return state; } private static int parsePathBeginningOrAfter(char c, StringBuilder buf, int i, String x, int currentState) { if (!Character.isWhitespace(c)) { if (c == '.') { throw new InvalidPathException(UtilConstants.ERR_UNEXPECTED_DOT + i, x); } else { buf.append(c); return 1; } } return currentState; } private static int parseWord(char c, StringBuilder buf, List segments, int currentState) { if (Character.isWhitespace(c)) { segments.add(buf.toString()); buf.setLength(0); buf.trimToSize(); return 2; } else if (c == '.') { segments.add(buf.toString()); buf.setLength(0); buf.trimToSize(); return 0; } else { buf.append(c); } return currentState; } private static int parseEndOfWord(char c, int i, String x) { if (!Character.isWhitespace(c)) { if (c == '.') { return 0; } else { throw new InvalidPathException(UtilConstants.ERR_UNEXPECTED_WHITESPACE + i, x); } } else { throw new InvalidPathException(UtilConstants.ERR_UNEXPECTED_CHARACTER + i, x); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy