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

org.apache.jackrabbit.oak.commons.PathUtils Maven / Gradle / Ivy

Go to download

Implements a resource resolver type for Jackrabbit Oak that can be used in unit tests based on Sling Mocks.

There is a newer version: 4.0.0-1.62.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.jackrabbit.oak.commons;

import static com.google.common.collect.Sets.newHashSet;

import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * Utility methods to parse a path.
 * 

* Each method validates the input, except if the system property * {packageName}.SKIP_VALIDATION is set, in which case only minimal validation * takes place within this function, so when the parameter is an illegal path, * the the result of this method is undefined. */ public final class PathUtils { public static final String ROOT_PATH = "/"; public static final String ROOT_NAME = ""; private static final Pattern SNS_PATTERN = Pattern.compile("(.+)\\[[1-9][0-9]*\\]$"); private PathUtils() { // utility class } /** * Whether the path is the root path ("/"). * * @param path the path * @return whether this is the root */ public static boolean denotesRoot(String path) { assert isValid(path) : "Invalid path ["+path+"]"; return denotesRootPath(path); } private static boolean denotesRootPath(String path) { return ROOT_PATH.equals(path); } /** * @param element The path segment to check for being the current element * @return {@code true} if the specified element equals "."; {@code false} otherwise. */ public static boolean denotesCurrent(String element) { return ".".equals(element); } /** * @param element The path segment to check for being the parent element * @return {@code true} if the specified element equals ".."; {@code false} otherwise. */ public static boolean denotesParent(String element) { return "..".equals(element); } /** * Whether the path is absolute (starts with a slash) or not. * * @param path the path * @return true if it starts with a slash */ public static boolean isAbsolute(String path) { assert isValid(path) : "Invalid path ["+path+"]"; return isAbsolutePath(path); } private static boolean isAbsolutePath(String path) { return !path.isEmpty() && path.charAt(0) == '/'; } /** * Get the parent of a path. The parent of the root path ("/") is the root * path. * * @param path the path * @return the parent path */ @NotNull public static String getParentPath(String path) { return getAncestorPath(path, 1); } /** * Get the nth ancestor of a path. The 1st ancestor is the parent path, * 2nd ancestor the grandparent path, and so on... *

* If {@code nth <= 0}, the path argument is returned as is. * * @param path the path * @param nth indicates the ancestor level for which the path should be * calculated. * @return the ancestor path */ @NotNull public static String getAncestorPath(String path, int nth) { assert isValid(path) : "Invalid path ["+path+"]"; if (path.isEmpty() || denotesRootPath(path) || nth <= 0) { return path; } int end = path.length() - 1; int pos = -1; while (nth-- > 0) { pos = path.lastIndexOf('/', end); if (pos > 0) { end = pos - 1; } else if (pos == 0) { return ROOT_PATH; } else { return ""; } } return path.substring(0, pos); } /** * Get the last element of the (absolute or relative) path. The name of the * root node ("/") and the name of the empty path ("") is the empty path. * * @param path the complete path * @return the last element */ @NotNull public static String getName(String path) { assert isValid(path) : "Invalid path ["+path+"]"; if (path.isEmpty() || denotesRootPath(path)) { return ROOT_NAME; } int end = path.length() - 1; int pos = path.lastIndexOf('/', end); if (pos != -1) { return path.substring(pos + 1, end + 1); } return path; } /** * Returns the given name without the possible SNS index suffix. If the * name does not contain an SNS index, then it is returned as-is. * * @param name name with a possible SNS index suffix * @return name without the SNS index suffix */ @NotNull public static String dropIndexFromName(@NotNull String name) { if (!name.endsWith("]")) { return name; } Matcher matcher = SNS_PATTERN.matcher(name); if (matcher.matches()) { return matcher.group(1); } return name; } /** * Calculate the number of elements in the path. The root path has zero * elements. * * @param path the path * @return the number of elements */ public static int getDepth(String path) { assert isValid(path) : "Invalid path ["+path+"]"; if (path.isEmpty()) { return 0; } int count = 1, i = 0; if (isAbsolutePath(path)) { if (denotesRootPath(path)) { return 0; } i++; } while (true) { i = path.indexOf('/', i) + 1; if (i == 0) { return count; } count++; } } /** * Returns an {@code Iterable} for the path elements. The root path ("/") and the * empty path ("") have zero elements. * * @param path the path * @return an Iterable for the path elements */ @NotNull public static Iterable elements(final String path) { assert isValid(path) : "Invalid path ["+path+"]"; return new Iterable() { @Override public Iterator iterator() { return new Iterator() { int pos = isAbsolute(path) ? 1 : 0; String next; @Override public boolean hasNext() { if (next == null) { if (pos >= path.length()) { return false; } int i = path.indexOf('/', pos); if (i < 0) { next = path.substring(pos); pos = path.length(); } else { next = path.substring(pos, i); pos = i + 1; } } return true; } @Override public String next() { if (hasNext()) { String next = this.next; this.next = null; return next; } throw new NoSuchElementException(); } @Override public void remove() { throw new UnsupportedOperationException("remove"); } }; } }; } /** * Concatenate path elements. * * @param parentPath the parent path * @param relativePaths the relative path elements to add * @return the concatenated path */ @NotNull public static String concat(String parentPath, String... relativePaths) { assert isValid(parentPath) : "Invalid parent path ["+parentPath+"]"; int parentLen = parentPath.length(); int size = relativePaths.length; StringBuilder buff = new StringBuilder(parentLen + size * 5); buff.append(parentPath); boolean needSlash = parentLen > 0 && !denotesRootPath(parentPath); for (String s : relativePaths) { assert isValid(s); if (isAbsolutePath(s)) { throw new IllegalArgumentException("Cannot append absolute path " + s); } if (!s.isEmpty()) { if (needSlash) { buff.append('/'); } buff.append(s); needSlash = true; } } return buff.toString(); } /** * Concatenate path elements. * * @param parentPath the parent path * @param subPath the subPath path to add * @return the concatenated path */ @NotNull public static String concat(String parentPath, String subPath) { assert isValid(parentPath) : "Invalid parent path ["+parentPath+"]"; assert isValid(subPath) : "Invalid sub path ["+subPath+"]"; // special cases if (parentPath.isEmpty()) { return subPath; } else if (subPath.isEmpty()) { return parentPath; } else if (isAbsolutePath(subPath)) { throw new IllegalArgumentException("Cannot append absolute path " + subPath); } StringBuilder buff = new StringBuilder(parentPath.length() + subPath.length() + 1); buff.append(parentPath); if (!denotesRootPath(parentPath)) { buff.append('/'); } buff.append(subPath); return buff.toString(); } /** * Relative path concatenation. * * @param relativePaths relative paths * @return the concatenated path or {@code null} if the resulting path is empty. */ @Nullable public static String concatRelativePaths(String... relativePaths) { StringBuilder result = new StringBuilder(); for (String path : relativePaths) { if (path != null && !path.isEmpty()) { int i0 = 0; int i1 = path.length(); while (i0 < i1 && path.charAt(i0) == '/') { i0++; } while (i1 > i0 && path.charAt(i1-1) == '/') { i1--; } if (i1 > i0) { if (result.length() > 0) { result.append('/'); } result.append(path.substring(i0, i1)); } } } return result.length() == 0 ? null : result.toString(); } /** * Check if a path is a (direct or indirect) ancestor of another path. * * @param ancestor the ancestor path * @param path the potential offspring path * @return true if the path is an offspring of the ancestor */ public static boolean isAncestor(String ancestor, String path) { assert isValid(ancestor) : "Invalid parent path ["+ancestor+"]"; assert isValid(path) : "Invalid path ["+path+"]"; if (ancestor.isEmpty() || path.isEmpty()) { return false; } if (denotesRoot(ancestor)) { if (denotesRoot(path)) { return false; } } else { ancestor += "/"; } return path.startsWith(ancestor); } /** * Relativize a path wrt. a parent path such that * {@code relativize(parentPath, concat(parentPath, path)) == paths} * holds. * * @param parentPath parent pth * @param path path to relativize * @return relativized path */ @NotNull public static String relativize(String parentPath, String path) { assert isValid(parentPath) : "Invalid parent path ["+parentPath+"]"; assert isValid(path) : "Invalid path ["+path+"]"; if (parentPath.equals(path)) { return ""; } String prefix = denotesRootPath(parentPath) ? parentPath : parentPath + '/'; if (path.startsWith(prefix)) { return path.substring(prefix.length()); } throw new IllegalArgumentException("Cannot relativize " + path + " wrt. " + parentPath); } /** * Get the index of the next slash. * * @param path the path * @param index the starting index * @return the index of the next slash (possibly the starting index), or -1 * if not found */ public static int getNextSlash(String path, int index) { assert isValid(path) : "Invalid path ["+path+"]"; return path.indexOf('/', index); } /** * Check if the path is valid, and throw an IllegalArgumentException if not. * A valid path is absolute (starts with a '/') or relative (doesn't start * with '/'), and contains none or more elements. A path may not end with * '/', except for the root path. Elements itself must be at least one * character long. * * @param path the path */ public static void validate(String path) { if (path.isEmpty() || denotesRootPath(path)) { return; } else if (path.charAt(path.length() - 1) == '/') { throw new IllegalArgumentException("Path may not end with '/': " + path); } char last = 0; for (int index = 0, len = path.length(); index < len; index++) { char c = path.charAt(index); if (c == '/') { if (last == '/') { throw new IllegalArgumentException("Path may not contains '//': " + path); } } last = c; } } /** * Check if the path is valid. A valid path is absolute (starts with a '/') * or relative (doesn't start with '/'), and contains none or more elements. * A path may not end with '/', except for the root path. Elements itself must * be at least one character long. * * @param path the path * @return {@code true} iff the path is valid. */ public static boolean isValid(String path) { if (path.isEmpty() || denotesRootPath(path)) { return true; } else if (path.charAt(path.length() - 1) == '/') { return false; } char last = 0; for (int index = 0, len = path.length(); index < len; index++) { char c = path.charAt(index); if (c == '/') { if (last == '/') { return false; } } last = c; } return true; } /** * Unify path inclusions and exclusions. *

    *
  • A path in {@code includePaths} is only retained if {@code includePaths} contains * none of its ancestors and {@code excludePaths} contains neither of its ancestors nor * that path itself.
  • *
  • A path in {@code excludePaths} is only retained if {@code includePaths} contains * an ancestor of that path.
  • *
* * When a set of paths is filtered wrt. {@code includePaths} and {@code excludePaths} * by first excluding all paths that have an ancestor or are contained in {@code excludePaths} * and then including all paths that have an ancestor or are contained in {@code includePaths} * then the result is the same regardless whether the {@code includePaths} and * {@code excludePaths} sets have been run through this method or not. * * @param includePaths set of paths to be included * @param excludedPaths set of paths to be excluded */ public static void unifyInExcludes(Set includePaths, Set excludedPaths) { Set retain = newHashSet(); Set includesRemoved = newHashSet(); for (String include : includePaths) { for (String exclude : excludedPaths) { if (exclude.equals(include) || isAncestor(exclude, include)) { includesRemoved.add(include); } else if (isAncestor(include, exclude)) { retain.add(exclude); } } //Remove redundant includes /a, /a/b -> /a for (String include2 : includePaths) { if (isAncestor(include, include2)) { includesRemoved.add(include2); } } } includePaths.removeAll(includesRemoved); excludedPaths.retainAll(retain); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy