How to check if a given path is possible child of another path?

JavaFile Io

Java Problem Overview


I am trying to find if given path is possible child of another path using java. Both path may not exist.

Say c:\Program Files\My Company\test\My App is a possible child of c:\Program Files.

Currently I am doing this with

boolean myCheck(File maybeChild, File possibleParent)
{
    return maybeChild.getAbsolutePath().startsWith( possibleParent.getAbsolutePath());
}

Java Solutions


Solution 1 - Java

You can also use java.nio.file.Path to do this much more easily. The java.nio.file.Path.startsWith method seems to handle all possible cases.

Example:

private static void isChild(Path child, String parentText) {
    Path parent = Paths.get(parentText).toAbsolutePath();
    System.out.println(parentText + " = " + child.startsWith(parent));
}

public static void main(String[] args) {
    Path child = Paths.get("/FolderA/FolderB/File").toAbsolutePath();
    isChild(child, "/FolderA/FolderB/File");
    isChild(child, "/FolderA/FolderB/F");
    isChild(child, "/FolderA/FolderB");
    isChild(child, "/FolderA/Folder");
    isChild(child, "/FolderA");
    isChild(child, "/Folder");
    isChild(child, "/");
    isChild(child, "");
}

Output:

/FolderA/FolderB/File = true
/FolderA/FolderB/F = false
/FolderA/FolderB = true
/FolderA/Folder = false
/FolderA = true
/Folder = false
/ = true
 = false

If you need more reliability you can use toRealPath instead of toAbsolutePath.

Solution 2 - Java

File parent = maybeChild.getParentFile();
while ( parent != null ) {
  if ( parent.equals( possibleParent ) )
    return true;
  parent = parent.getParentFile();
}
return false;

Solution 3 - Java

Asides from the fact the paths may not exist (and the canonicalisation may not succeed), this looks like a reasonable approach that should work in the straightforward case.

You may want to look at calling getParentFile() on the "maybe child" in a loop, testing if it matches the parent at each step. You can also short-circuit the comparison if the parent isn't a (real) directory.

Perhaps something like the following:

boolean myCheck(File maybeChild, File possibleParent) throws IOException
{
    final File parent = possibleParent.getCanonicalFile();
    if (!parent.exists() || !parent.isDirectory()) {
        // this cannot possibly be the parent
        return false;
    }

    File child = maybeChild.getCanonicalFile();
    while (child != null) {
        if (child.equals(parent)) {
            return true;
        }
        child = child.getParentFile();
    }
    // No match found, and we've hit the root directory
    return false;
}

Note that if you want the child relationship to be strict (i.e. a directory is not a child of itself) you can change the initial child assignment on line 9 to be child.getParentFile() so the first check happens on the child's containing directory.

Solution 4 - Java

This will work for your example. It will also return true if the child is a relative path (which is often desirable.)

boolean myCheck(File maybeChild, File possibleParent)
{
    URI parentURI = possibleParent.toURI();
    URI childURI = maybeChild.toURI();
    return !parentURI.relativize(childURI).isAbsolute();
}

Solution 5 - Java

That will probably work fine as it is, although I would use getCanonicalPath() rather than getAbsolutePath(). This should normalize any weird paths like x/../y/z which would otherwise screw up the matching.

Solution 6 - Java

maybeChild.getCanonicalPath().startsWith( possibleParent.getCanonicalPath() );

Solution 7 - Java

Be aware of relative paths! I think that simplest solution is something like this:

public boolean myCheck(File maybeChild, File possibleParent) {
  if (requestedFile.isAbsolute) {
    return possibleParent.resolve(maybeChild).normalize().toAbsolutePath.startsWith(possibleParent.normalize().toAbsolutePath)
  } else {
    return maybeChild.normalize().toAbsolutePath.startsWith(possibleParent.normalize().toAbsolutePath)
  }
}

In scala you can have similar approach:

val baseDir = Paths.get("/home/luvar/tmp")
val baseDirF = baseDir.toFile
//val requestedFile = Paths.get("file1")
val requestedFile = Paths.get("../.viminfo")
val fileToBeRead = if (requestedFile.isAbsolute) {
  requestedFile
} else {
  baseDir.resolve(requestedFile)
}
fileToBeRead.toAbsolutePath
baseDir.toAbsolutePath
fileToBeRead.normalize()
baseDir.normalize()
val isSubpath = fileToBeRead.normalize().toAbsolutePath.startsWith(baseDir.normalize().toAbsolutePath)

Solution 8 - Java

Old question but a pre-1.7 solution:

public boolean startsWith(String possibleRoot, String possibleChildOrSame) {
        String[] possiblePath = new File(possibleRoot).getAbsolutePath().replace('\\', '/').split("/");
        String[] possibleChildOrSamePath = new File(possibleChildOrSame).getAbsolutePath().replace('\\', '/').split("/");

        if (possibleChildOrSamePath.length < possiblePath.length) {
            return false;
        }

        // not ignoring case
        for (int i = 0; i < possiblePath.length; i++) {
            if (!possiblePath[i].equals(possibleChildOrSamePath[i])) {
                return false;
            }
        }
        return true;
}

For completeness the java 1.7+ solution:

public boolean startsWith(String possibleRoot, String possibleChildOrSame) {
        Path p1 = Paths.get(possibleChildOrSame).toAbsolutePath();
        Path p2 = Paths.get(possibleRoot).toAbsolutePath();
        return p1.startsWith(p2);
}

Solution 9 - Java

Surprisingly there is no simple, yet functional solution.

The accepted answer does consider same directories as child, which is wrong.

Here is one using java.nio.file.Path API only:

static boolean isChildPath(Path parent, Path child){
      Path pn = parent.normalize();
      Path cn = child.normalize();
      return cn.getNameCount() > pn.getNameCount() && cn.startsWith(pn);
}

Test cases:

 @Test
public void testChildPath() {
      assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F"))).isFalse();
      assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F/A"))).isTrue();
      assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F/A.txt"))).isTrue();

      assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F/../A"))).isFalse();
      assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/FA"))).isFalse();

      assertThat(isChildPath(Paths.get("FolderA"), Paths.get("FolderA"))).isFalse();
      assertThat(isChildPath(Paths.get("FolderA"), Paths.get("FolderA/B"))).isTrue();
      assertThat(isChildPath(Paths.get("FolderA"), Paths.get("FolderA/B"))).isTrue();
      assertThat(isChildPath(Paths.get("FolderA"), Paths.get("FolderAB"))).isFalse();
      assertThat(isChildPath(Paths.get("/FolderA/FolderB/F"), Paths.get("/FolderA/FolderB/F/Z/X/../A"))).isTrue();
}

Solution 10 - Java

When testing paths for equality, the following considerations should be taken into account:

  1. Case sensitivity of the file system. The only API which can handle case (in)sensitive file systems is NIO.2 (1.7+), that's why neither java.io.File nor String can be used.
  2. Individual path entry handling: C:\abc is not an ancestor, nor even an immediate parent of C:\abcd, hence String.startsWith() API can't be used.
  3. On Windows, C:\Program Files is the same directory as C:\PROGRA~1, and Files.isSameFile() (from NIO.2) is the only API that can handle this right. This is what Path.startsWith() approach doesn't support.
  4. Symlink friendliness (not fully covered by my answer since the actual requirements may differ). For directory symlinks, Files.isSameFile() supports this to some extent, so that C:\Documents and Settings is indeed an ancestor of C:\Users\Public. Again, this is where custom code works slightly better than the Path.startsWith() API (see this most-voted answer).

Having said the above, the solution may look like this. Java:

  boolean isAncestorOf(final Path parent, final Path child) {
    final Path absoluteParent = parent.toAbsolutePath().normalize();
    final Path absoluteChild = child.toAbsolutePath().normalize();

    if (absoluteParent.getNameCount() >= absoluteChild.getNameCount()) {
      return false;
    }

    final Path immediateParent = absoluteChild.getParent();
    if (immediateParent == null) {
      return false;
    }

    return isSameFileAs(absoluteParent, immediateParent) || isAncestorOf(absoluteParent, immediateParent);
  }

  boolean isSameFileAs(final Path path, final Path path2) {
    try {
      return Files.isSameFile(path, path2);
    }
    catch (final IOException ioe) {
      return path.toAbsolutePath().normalize().equals(path2.toAbsolutePath().normalize());
    }
  }

Kotlin:

fun Path.isAncestorOf(child: Path): Boolean {
  val absoluteParent = toAbsolutePath().normalize()
  val absoluteChild = child.toAbsolutePath().normalize()

  if (absoluteParent.nameCount >= absoluteChild.nameCount) {
    return false
  }

  val immediateParent = absoluteChild.parent
                        ?: return false

  return absoluteParent.isSameFileAs(immediateParent) || absoluteParent.isAncestorOf(immediateParent)
}

fun Path.isSameFileAs(that: Path): Boolean =
  try {
    Files.isSameFile(this, that)
  }
  catch (_: NoSuchFileException) {
    toAbsolutePath().normalize() == that.toAbsolutePath().normalize()
  }

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJayanView Question on Stackoverflow
Solution 1 - JavaJecho JekovView Answer on Stackoverflow
Solution 2 - JavabiziclopView Answer on Stackoverflow
Solution 3 - JavaAndrzej DoyleView Answer on Stackoverflow
Solution 4 - JavafinnwView Answer on Stackoverflow
Solution 5 - JavaskaffmanView Answer on Stackoverflow
Solution 6 - Javauser1988293View Answer on Stackoverflow
Solution 7 - JavaĽubomír VargaView Answer on Stackoverflow
Solution 8 - JavaMattias Isegran BerganderView Answer on Stackoverflow
Solution 9 - JavaArthur H.View Answer on Stackoverflow
Solution 10 - JavaBassView Answer on Stackoverflow