diff --git a/src/main/java/org/apache/sling/api/resource/ResourceUtil.java b/src/main/java/org/apache/sling/api/resource/ResourceUtil.java
index e3355cf0..9e4f2a81 100644
--- a/src/main/java/org/apache/sling/api/resource/ResourceUtil.java
+++ b/src/main/java/org/apache/sling/api/resource/ResourceUtil.java
@@ -195,6 +195,35 @@ private static int countDotsSegment(final String segment) {
return parentPath;
}
+ /**
+ * Utility method to check whether the given path represents
+ * the root path. The path is normalized by {@link #normalize(String)} before
+ * checking.
+ *
+ * @param path The path to check.
+ * @return true if the path represents the root path after
+ * normalization, false otherwise.
+ * @throws IllegalArgumentException If the path cannot be normalized by the
+ * {@link #normalize(String)} method.
+ * @throws NullPointerException If path is null.
+ * @since 2.15.0 (Sling API Bundle 3.1.0)
+ */
+ public static boolean isRoot(@NotNull String path) {
+ // quick check for obvious root
+ if ("/".equals(path)) {
+ return true;
+ }
+
+ // normalize path (remove . and ..)
+ String normalizedPath = normalize(path);
+ if (normalizedPath == null) {
+ throw new IllegalArgumentException(
+ String.format("normalizing path '%s' resolves to a path higher than root", path));
+ }
+
+ return "/".equals(normalizedPath);
+ }
+
/**
* Utility method returns the ancestor's path at the given level
* relative to path, which is normalized by {@link #normalize(String)}
diff --git a/src/test/java/org/apache/sling/api/resource/ResourceUtilTest.java b/src/test/java/org/apache/sling/api/resource/ResourceUtilTest.java
index b957bd0c..c1622ec9 100644
--- a/src/test/java/org/apache/sling/api/resource/ResourceUtilTest.java
+++ b/src/test/java/org/apache/sling/api/resource/ResourceUtilTest.java
@@ -449,4 +449,33 @@ public void testEscapeAndUnescapeNameWithDots() {
assertEquals(nameWithSpecialChars, ResourceUtil.unescapeName(escapedName));
assertFalse(escapedName.contains("."));
}
+
+ @Test
+ public void testIsRoot() {
+ // root path
+ assertTrue(ResourceUtil.isRoot("/"));
+
+ // paths that normalize to root
+ assertTrue(ResourceUtil.isRoot("///"));
+ assertTrue(ResourceUtil.isRoot("/a/.."));
+ assertTrue(ResourceUtil.isRoot("/a/b/../.."));
+ assertTrue(ResourceUtil.isRoot("/."));
+
+ // non-root paths
+ assertFalse(ResourceUtil.isRoot("/a"));
+ assertFalse(ResourceUtil.isRoot("/a/b"));
+ assertFalse(ResourceUtil.isRoot("/a/b/c"));
+ assertFalse(ResourceUtil.isRoot("/a/b/.."));
+
+ // relative paths (not root)
+ assertFalse(ResourceUtil.isRoot("a"));
+ assertFalse(ResourceUtil.isRoot("a/b"));
+
+ // null should throw NullPointerException
+ assertThrows(NullPointerException.class, () -> ResourceUtil.isRoot(null));
+
+ // paths that cannot be normalized should throw IllegalArgumentException
+ assertThrows(IllegalArgumentException.class, () -> ResourceUtil.isRoot("/.."));
+ assertThrows(IllegalArgumentException.class, () -> ResourceUtil.isRoot("/a/../.."));
+ }
}