package com.sun.faces.application.resource;
import static com.sun.faces.util.Util.startsWithOneOf;
import java.util.ArrayDeque;
import java.util.Iterator;
import javax.faces.context.ExternalContext;
import javax.servlet.ServletContext;
public class ResourcePathsIterator implements Iterator<String> {
private final int maxDepth;
private final ExternalContext externalContext;
private final String[] extensions;
private final String[] restrictedDirectories;
private final ArrayDeque<String> stack = new ArrayDeque<>();
private String next;
public ResourcePathsIterator(String rootPath, int maxDepth, String[] extensions, String[] restrictedDirectories, ExternalContext externalContext) {
this.maxDepth = maxDepth;
this.externalContext = externalContext;
this.extensions = extensions;
this.restrictedDirectories = restrictedDirectories;
visit(rootPath);
}
@Override
public boolean hasNext() {
if (next != null) {
return true;
}
tryTake();
return next != null;
}
@Override
public String next() {
if (next == null) {
tryTake();
}
String nextReturn = next;
next = null;
return nextReturn;
}
private void visit(String resourcePath) {
stack.addAll(externalContext.getResourcePaths(resourcePath));
}
private void tryTake() {
if (stack.isEmpty()) {
return;
}
while (next == null && !stack.isEmpty()) {
String nextCandidate = stack.removeFirst();
if (isDirectory(nextCandidate)) {
if (!startsWithOneOf(nextCandidate, restrictedDirectories) && !directoryExceedsMaxDepth(nextCandidate, maxDepth)) {
visit(nextCandidate);
}
} else if (isValidCandidate(nextCandidate, extensions)) {
next = nextCandidate;
}
}
}
/**
* Checks if the given resource path obtained from {@link ServletContext#getResourcePaths(String)} represents a
* directory.
*
* @param resourcePath the resource path to check
* @return true if the resource path represents a directory, false otherwise
*/
private static boolean isDirectory(final String resourcePath) {
return resourcePath.endsWith("/");
}
private static boolean directoryExceedsMaxDepth(final String resourcePath, final long max) {
return resourcePath.chars().filter(i -> i == '/').count() > max;
}
private static boolean isValidCandidate(final String resourcePath, final String[] extensions) {
if (extensions == null || extensions.length == 0) {
return true;
}
for (String extension : extensions) {
if (resourcePath.endsWith(extension)) {
return true;
}
}
return false;
}
}