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

org.apache.sling.api.resource.path.Path Maven / Gradle / Ivy

/*
 * 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.sling.api.resource.path;

import java.util.regex.Pattern;

import org.jetbrains.annotations.NotNull;

/**
 * Simple helper class for path matching.
 *
 * @since 1.0.0 (Sling API Bundle 2.11.0)
 */
public class Path implements Comparable {

    /**
     * The prefix to be used for glob patterns
     * @since 1.2.0 (Sling API Bundle 2.15.0)
     */
    public static final String GLOB_PREFIX = "glob:";

    private final String path;
    private final String prefix;
    private final boolean isPattern;
    private final Pattern regexPattern;

    /**
     * 

Create a new path object either from a concrete path or from a glob pattern.

* *

A glob pattern must start with the {@code glob:} prefix (e.g. glob:**/*.html). The following rules are used * to interpret glob patterns:

*
    *
  • The {@code *} character matches zero or more characters of a name component without crossing directory boundaries.
  • *
  • The {@code **} characters match zero or more characters crossing directory boundaries.
  • *
* * @param path The resource path or a glob pattern. * @throws NullPointerException If {@code otherPath} is {@code null} * @throws IllegalArgumentException If the provided path is not absolute, or if the glob pattern does not start with a slash. */ public Path(@NotNull final String path) { if ( path.equals("/") ) { this.path = "/"; } else if ( path.endsWith("/") ) { this.path = path.substring(0, path.length() - 1); } else { this.path = path; } if (this.path.startsWith(GLOB_PREFIX)) { final String patternPath = path.substring(GLOB_PREFIX.length()); this.isPattern = true; this.regexPattern = Pattern.compile(toRegexPattern(patternPath)); int lastSlash = 0; int pos = 1; while ( patternPath.length() > pos ) { final char c = patternPath.charAt(pos); if ( c == '/') { lastSlash = pos; } else if ( c == '*') { break; } pos++; } this.prefix = (pos == patternPath.length() ? patternPath : patternPath.substring(0, lastSlash + 1)); } else { this.isPattern = false; this.regexPattern = null; this.prefix = this.path.equals("/") ? "/" : this.path.concat("/"); } if ( !this.prefix.startsWith("/") ) { throw new IllegalArgumentException("Path must be absolute: " + path); } } /** * If this {@code Path} object holds a path (and not a pattern), this method * checks whether the provided path is equal to this path or a sub path of it. * If a glob pattern is provided as the argument, it performs the same check * and respects the provided pattern. This means it returns {@code true} if this * path is a parent to any potential path matching the provided pattern. For * example if this path is {@code /apps/foo} and the provided pattern is * {@code glob:/apps/foo/bar/*.jsp} this method will return true. Same if * the provided pattern is {@code glob:/apps/**/hello.html}. * If this {@code Path} object holds a pattern, it checks whether the * provided path matches the pattern. If this path object holds a pattern * and a pattern is provided as the argument, it returns only {@code true} * if the pattern is the same. * If the provided argument is not an absolute path (e.g. if it is a relative * path or a pattern), this method returns {@code false}. * * @param otherPath Absolute path to check. * @return {@code true} If other path is within the sub tree of this path * or matches the pattern. * @see Path#isPattern() * @throws NullPointerException If {@code otherPath} is {@code null} * @throws IllegalArgumentException If the provided path is not absolute, or if the glob pattern does not start with a slash. */ public boolean matches(final String otherPath) { if ( otherPath.startsWith(GLOB_PREFIX) ) { if ( this.isPattern ) { // both are patterns, then they must be equal. // need to compare Pattern.pattern() as that class does // not implement a semantic equals(...) method final Path oPath = new Path(otherPath); return this.regexPattern.pattern().equals(oPath.regexPattern.pattern()); } // this is path, provided argument is a pattern // simple check, if this is root, everything is a sub pattern if ( "/".equals(this.path) ) { return true; } // simplest case - the prefix of the glob pattern matches already // for example: this path = /apps // glob = /apps/** // then we iterate by removing the last path segment String subPath = otherPath; while ( subPath != null ) { final Path oPath = new Path(subPath); if ( oPath.matches(this.path) ) { return true; } final int lastSlash = subPath.lastIndexOf('/'); if ( lastSlash == GLOB_PREFIX.length() ) { subPath = null; } else { subPath = subPath.substring(0, lastSlash); } } return false; } // provided argument is a path if ( !otherPath.startsWith("/") ) { throw new IllegalArgumentException("Path must be absolute: " + otherPath); } if (isPattern) { return this.regexPattern.matcher(otherPath).matches(); } return this.path.equals(otherPath) || otherPath.startsWith(this.prefix); } /** * Return the path if this {@code Path} object holds a path, * returns the pattern otherwise. * @return The path or pattern. * @see #isPattern() */ public String getPath() { return this.path; } /** * Returns {code true} if this {@code Path} object is holding a pattern * @return {code true} for a pattern, {@code false} for a path. * @since 1.2.0 (Sling API Bundle 2.15.0) */ public boolean isPattern() { return this.isPattern; } @Override public int compareTo(@NotNull final Path o) { return this.getPath().compareTo(o.getPath()); } @Override public int hashCode() { return this.getPath().hashCode(); } @Override public boolean equals(final Object obj) { if (this == obj) { return true; } if (obj == null || !(obj instanceof Path)) { return false; } return this.getPath().equals(((Path)obj).getPath()); } private static String toRegexPattern(String pattern) { StringBuilder stringBuilder = new StringBuilder("^"); int index = 0; while (index < pattern.length()) { char currentChar = pattern.charAt(index++); switch (currentChar) { case '*': if (getCharAtIndex(pattern, index) == '*') { stringBuilder.append(".*"); ++index; } else { stringBuilder.append("[^/]*"); } break; case '/': stringBuilder.append(currentChar); break; default: if (isRegexMeta(currentChar)) { stringBuilder.append(Pattern.quote(Character.toString(currentChar))); } else { stringBuilder.append(currentChar); } } } return stringBuilder.append('$').toString(); } private static char getCharAtIndex(String string, int index) { return index < string.length() ? string.charAt(index) : 0; } private static boolean isRegexMeta(char character) { return "<([{\\^-=$!|]})?*+.>".indexOf(character) != -1; } @Override public String toString() { return "Path [path=" + path + "]"; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy