/*******************************************************************************
* Copyright (c) 2015-2015 CWI
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* * Davy Landman - Davy.Landman@cwi.nl - CWI
*******************************************************************************/
package org.rascalmpl.uri;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.NavigableMap;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
public abstract class FileTree {
public static class FSEntry {
public long lastModified;
public FSEntry(long lastModified) {
this.lastModified = lastModified;
}
}
// perhaps the string could be split into folders and some smart interning
// but for now, this works.
protected final ConcurrentNavigableMap<String, FSEntry> fs;
protected long totalSize;
protected IOException throwMe;
public FileTree() {
fs = new ConcurrentSkipListMap<>();;
totalSize = 0;
throwMe = null;
}
public boolean exists(String path) {
if (throwMe != null) {
return false;
}
if ("/".equals(path)) {
return true;
}
// since we only store files, but they are sorted
// the ceilingKey will return either the first file in the directory
// or the actual file itself
String result = fs.ceilingKey(path);
if (result == null) {
return false;
}
if (result.equals(path)) {
return true;
}
// it might be a directory
if (!path.endsWith("/")) {
if (result.startsWith(path)) {
char separator = result.charAt(path.length());
if (separator == '/') {
return true;
}
// If there is a file named a.class and a directory called a.
// The a.class is "higher" than the files in the directory.
// so ceilingKey returns the "a.class", in that case we have to test if path/ might have a different ceilingKey
if (separator < '/') {
return exists(path + "/");
}
}
}
return result.startsWith(path);
}
public boolean isDirectory(String path) {
if (throwMe != null) {
return false;
}
if (!path.endsWith("/")) {
path += "/";
}
if ("/".equals(path)) {
return true;
}
// since we only store files, but they are sorted
// the ceilingKey will return either the first file in the directory
// or the first file greater than the directory
String result = fs.ceilingKey(path);
if (result == null) {
return false;
}
return result.startsWith(path);
}
public boolean isFile(String path) {
if (throwMe != null) {
return false;
}
return fs.containsKey(path);
}
public long getLastModified(String path) throws IOException {
if (throwMe != null) {
throw throwMe;
}
FSEntry result = fs.get(path);
if (result == null) {
throw new FileNotFoundException(path);
}
return result.lastModified;
}
private static final String biggestChar = new String(new int[] {Character.MAX_CODE_POINT}, 0, 1);
public String[] directChildren(String path) throws IOException {
if (throwMe != null) {
throw throwMe;
}
if (!path.endsWith("/") && !path.isEmpty()) {
path += "/";
}
NavigableMap<String, FSEntry> contents = fs.tailMap(path, true);
String end = fs.higherKey(path + biggestChar); // the last key
int offset = path.length();
ArrayList<String> result = new ArrayList<>();
String previousDir = "+"; // never valid
for (String subPath : contents.keySet()) {
if (subPath == end) {
break;
}
int nextSlash = subPath.indexOf('/', offset);
if (nextSlash != -1) {
if (!subPath.startsWith(previousDir, offset)) {
previousDir = subPath.substring(offset, nextSlash);
result.add(previousDir);
previousDir = previousDir + "/"; // to make sure the starts with doesn't match same prefix dirs
}
}
else {
result.add(subPath.substring(offset));
}
}
return result.toArray(new String[0]);
}
public long totalSize() {
return totalSize;
}
}