SLING-5837 - Allow ResourceChangeListeners to define glob patterns for resource matching
* glob patterns have to start with the 'glob:' prefix; syntax is limited to only the '*' and '**' characters
* updated Javadoc
git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1753534 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/api/resource/observation/ResourceChangeListener.java b/src/main/java/org/apache/sling/api/resource/observation/ResourceChangeListener.java
index 61abd2a..e97634d 100644
--- a/src/main/java/org/apache/sling/api/resource/observation/ResourceChangeListener.java
+++ b/src/main/java/org/apache/sling/api/resource/observation/ResourceChangeListener.java
@@ -60,11 +60,11 @@
*
* <p>If the whole tree of all search paths should be observed, the special value {@code .} should be used.</p>
*
- * <p>The following rules are used to interpret glob patterns:</p>
+ * <p>A glob pattern should start with the {@code glob:} prefix (e.g. <code>glob:**/*.html</code>). The following rules are used
+ * to interpret glob patterns:</p>
* <ul>
* <li>The {@code *} character matches zero or more characters of a name component without crossing directory boundaries.</li>
- * <li>The {@code **} characters matches zero or more characters crossing directory boundaries.</li>
- * <li>The {@code ?} character matches exactly one character of a name component.</li>
+ * <li>The {@code **} characters match zero or more characters crossing directory boundaries.</li>
* </ul>
*
* <p>If one of the paths is a sub resource of another specified path, the sub path is ignored.</p>
diff --git a/src/main/java/org/apache/sling/api/resource/path/Path.java b/src/main/java/org/apache/sling/api/resource/path/Path.java
index b68eb84..2bac5db 100644
--- a/src/main/java/org/apache/sling/api/resource/path/Path.java
+++ b/src/main/java/org/apache/sling/api/resource/path/Path.java
@@ -18,9 +18,8 @@
*/
package org.apache.sling.api.resource.path;
-import java.nio.file.FileSystems;
-import java.nio.file.PathMatcher;
-import java.nio.file.Paths;
+import java.util.regex.Pattern;
+import javax.annotation.Nonnull;
/**
* Simple helper class for path matching.
@@ -34,26 +33,29 @@
private final String prefix;
private final boolean isPattern;
+ private final Pattern regexPattern;
/**
* <p>Create a new path object either from a concrete path or from a glob pattern.</p>
*
- * <p>The following rules are used to interpret glob patterns:</p>
+ * <p>A glob pattern should start with the {@code glob:} prefix (e.g. <code>glob:**/*.html</code>). The following rules are used
+ * to interpret glob patterns:</p>
* <ul>
* <li>The {@code *} character matches zero or more characters of a name component without crossing directory boundaries.</li>
- * <li>The {@code **} characters matches zero or more characters crossing directory boundaries.</li>
- * <li>The {@code ?} character matches exactly one character of a name component.</li>
+ * <li>The {@code **} characters match zero or more characters crossing directory boundaries.</li>
* </ul>
*
* @param path the resource path or a glob pattern.
*/
- public Path(final String path) {
+ public Path(@Nonnull final String path) {
this.path = path;
this.prefix = path.equals("/") ? "/" : path.concat("/");
- if (path.contains("?") || path.contains("*")) {
+ if (path.startsWith("glob:")) {
isPattern = true;
+ regexPattern = Pattern.compile(toRegexPattern(path.substring(5)));
} else {
isPattern = false;
+ regexPattern = null;
}
}
@@ -65,8 +67,7 @@
*/
public boolean matches(final String otherPath) {
if (isPattern) {
- PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + path);
- return matcher.matches(Paths.get(otherPath));
+ return regexPattern.matcher(otherPath).matches();
}
return this.path.equals(otherPath) || otherPath.startsWith(this.prefix);
}
@@ -80,7 +81,7 @@
}
@Override
- public int compareTo(final Path o) {
+ public int compareTo(@Nonnull final Path o) {
return this.getPath().compareTo(o.getPath());
}
@@ -100,4 +101,40 @@
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('\\');
+ }
+
+ 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;
+ }
+
}
diff --git a/src/test/java/org/apache/sling/api/resource/path/PathTest.java b/src/test/java/org/apache/sling/api/resource/path/PathTest.java
index 4579913..0d6a799 100644
--- a/src/test/java/org/apache/sling/api/resource/path/PathTest.java
+++ b/src/test/java/org/apache/sling/api/resource/path/PathTest.java
@@ -49,11 +49,15 @@
}
@Test public void testPatternMatching() {
- final Path path = new Path("/apps/**/*.html");
- assertTrue(path.matches("/apps/project/a.html"));
- assertTrue(path.matches("/apps/project/1/a.html"));
- assertTrue(path.matches("/apps/project/1/2/a.html"));
- assertFalse(path.matches("/apps/a.html"));
- assertFalse(path.matches("/apps/project/a.html/b"));
+ final Path path_1 = new Path("glob:/apps/**/*.html");
+ assertTrue(path_1.matches("/apps/project/a.html"));
+ assertTrue(path_1.matches("/apps/project/1/a.html"));
+ assertTrue(path_1.matches("/apps/project/1/2/a.html"));
+ assertFalse(path_1.matches("/apps/a.html"));
+ assertFalse(path_1.matches("/apps/project/a.html/b"));
+
+ final Path path_2 = new Path("glob:/apps/*.html");
+ assertTrue(path_2.matches("/apps/a.html"));
+ assertFalse(path_2.matches("/apps/a/a.html"));
}
}