blob: dbf84894263402afcadfd20b5fcc750ec0d51552 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.vfs.impl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.impl.ApplicationInfoImpl;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
// all file pointers we store in the tree with nodes corresponding to the file structure on disk
class FilePointerPartNode {
private static final FilePointerPartNode[] EMPTY_ARRAY = new FilePointerPartNode[0];
@NotNull private String part; // common prefix of all file pointers beneath
@NotNull private FilePointerPartNode[] children;
private FilePointerPartNode parent;
VirtualFilePointerImpl leaf; // file pointer for this exact path (e.g. concatenation of all "part" fields down from the root)
// in case there is file pointer exists for this part, its info is saved here
volatile Pair<VirtualFile, String> myFileAndUrl; // must not be both null
private volatile long myLastUpdated = -1;
volatile int useCount;
private int pointersUnder = 1; // number of alive pointers in this node plus all nodes beneath
private static final VirtualFileManager ourFileManager = VirtualFileManager.getInstance();
FilePointerPartNode(@NotNull String part, FilePointerPartNode parent, Pair<VirtualFile,String> fileAndUrl) {
this.part = part;
this.parent = parent;
children = EMPTY_ARRAY;
myFileAndUrl = fileAndUrl;
}
@Override
public String toString() {
return part + (children.length == 0 ? "" : " -> "+children.length);
}
// returns the node and length of matched characters in that node, or null if there is no match
int position(@Nullable VirtualFile parent, boolean separator, @NotNull CharSequence childName, @NotNull FilePointerPartNode[] outNode) {
checkConsistency();
int partStart;
if (parent == null) {
partStart = 0;
outNode[0] = this;
}
else {
VirtualFile gparent = parent.getParent();
partStart = position(gparent, gparent != null && !StringUtil.equals(gparent.getNameSequence(), "/"), parent.getNameSequence(), outNode);
if (partStart == -1) return -1;
}
boolean childSeparator = false;
if (separator) {
if (partStart == outNode[0].part.length()) {
childSeparator = true;
}
else {
int sepIndex = indexOfFirstDifferentChar("/", 0, outNode[0].part, partStart);
if (sepIndex != 1) return -1;
partStart++;
}
}
int index = indexOfFirstDifferentChar(childName, 0, outNode[0].part, partStart);
if (index == childName.length()) {
return partStart+index;
}
if (partStart + index == outNode[0].part.length()) {
// go to children
for (FilePointerPartNode child : outNode[0].children) {
int childPos = child.position(null, childSeparator, childName.subSequence(index, childName.length()), outNode);
if (childPos != -1) return childPos;
}
}
// else there is no match
return -1;
}
// appends to "out" all nodes under this node whose path (beginning from this node) starts in prefix.subSequence(start), then parent.getPath(), then childName
void getPointersUnder(@Nullable VirtualFile parent,
boolean separator,
@NotNull CharSequence childName,
@NotNull List<FilePointerPartNode> out) {
FilePointerPartNode[] outNode = new FilePointerPartNode[1];
int position = position(parent, separator, childName, outNode);
if (position != -1) {
FilePointerPartNode node = outNode[0];
addAllPointersUnder(node, out);
}
}
private static void addAllPointersUnder(@NotNull FilePointerPartNode node, @NotNull List<FilePointerPartNode> out) {
if (node.leaf != null) out.add(node);
for (FilePointerPartNode child : node.children) {
addAllPointersUnder(child, out);
}
}
boolean getPointersUnder(@NotNull String path, int start, @NotNull List<FilePointerPartNode> out) {
checkConsistency();
if (pointersUnder == 0) return false;
// invariant: upper nodes are matched
int index = indexOfFirstDifferentChar(path, start);
if (index - start == part.length() // part matched entirely, check children
|| index == path.length() // query matched entirely, add all children to matches
) {
if (index == path.length() && leaf != null) {
out.add(this);
}
for (FilePointerPartNode child : children) {
child.getPointersUnder(path, index, out);
}
}
// else there is no match
return false;
}
private static final boolean UNIT_TEST = ApplicationManager.getApplication().isUnitTestMode();
void checkConsistency() {
if (UNIT_TEST && !ApplicationInfoImpl.isInPerformanceTest()) {
doCheckConsistency();
}
}
private void doCheckConsistency() {
int childSum = 0;
for (FilePointerPartNode child : children) {
childSum += child.pointersUnder;
child.doCheckConsistency();
assert child.parent == this;
}
if (leaf != null) childSum++;
assert (useCount == 0) == (leaf == null) : useCount + " - " +leaf;
assert pointersUnder == childSum : "expected: "+pointersUnder+"; actual: "+childSum;
}
@NotNull
FilePointerPartNode findPointerOrCreate(@NotNull String path, int start, @NotNull Pair<VirtualFile, String> fileAndUrl) {
// invariant: upper nodes are matched
int index = indexOfFirstDifferentChar(path, start);
if (index == path.length() // query matched entirely
&& index - start == part.length()
) {
if (leaf == null) pointersUnder++;
return this;
}
if (index - start == part.length() // part matched entirely, check children
) {
for (FilePointerPartNode child : children) {
// find the right child (its part should start with ours)
int i = child.indexOfFirstDifferentChar(path, index);
if (i != index && (i > index+1 || path.charAt(index) != '/')) {
FilePointerPartNode node = child.findPointerOrCreate(path, index, fileAndUrl);
if (node.leaf == null) pointersUnder++; // the new node's been created
return node;
}
}
// cannot insert to children, create child node manually
String pathRest = path.substring(index);
FilePointerPartNode newNode = new FilePointerPartNode(pathRest, this, fileAndUrl);
children = ArrayUtil.append(children, newNode);
pointersUnder++;
return newNode;
}
// else there is no match
// split
// try to make "/" start the splitted part
if (index > start && index != path.length() && path.charAt(index-1)== '/') index--;
String pathRest = path.substring(index);
FilePointerPartNode newNode = pathRest.isEmpty() ? this : new FilePointerPartNode(pathRest, this, fileAndUrl);
String commonPredecessor = StringUtil.first(part, index - start, false);
FilePointerPartNode splittedAway = new FilePointerPartNode(part.substring(index - start), this, myFileAndUrl);
splittedAway.children = children;
for (FilePointerPartNode child : children) {
child.parent = splittedAway;
}
splittedAway.pointersUnder = pointersUnder;
splittedAway.useCount = useCount;
splittedAway.associate(leaf, myFileAndUrl);
associate(null, null);
useCount = 0;
part = commonPredecessor;
children = newNode == this ? new FilePointerPartNode[]{splittedAway} : new FilePointerPartNode[]{splittedAway, newNode};
pointersUnder++;
return newNode;
}
// return true if the root node must be deleted also
boolean remove() {
assert leaf != null : toString();
associate(null, null);
useCount = 0;
myLastUpdated = -1;
FilePointerPartNode node;
for (node = this; node.parent != null; node = node.parent) {
node.pointersUnder--;
}
if (--node.pointersUnder == 0) {
node.children = EMPTY_ARRAY; // clear root node, especially in tests
return true;
}
return false;
}
private int indexOfFirstDifferentChar(@NotNull CharSequence path, int start) {
return indexOfFirstDifferentChar(path, start, part, 0);
}
@NotNull
// returns pair.second != null always
Pair<VirtualFile, String> update() {
long lastUpdated = myLastUpdated;
Pair<VirtualFile, String> fileAndUrl = myFileAndUrl;
long fsModCount = ourFileManager.getModificationCount();
if (lastUpdated == fsModCount) return fileAndUrl;
VirtualFile file = fileAndUrl.first;
String url = fileAndUrl.second;
boolean changed = false;
if (url == null) {
url = file.getUrl();
if (!file.isValid()) file = null;
changed = true;
}
boolean fileIsValid = file != null && file.isValid();
if (file != null && !fileIsValid) {
file = null;
changed = true;
}
if (file == null) {
file = ourFileManager.findFileByUrl(url);
fileIsValid = file != null && file.isValid();
if (file != null) {
changed = true;
}
}
if (file != null) {
if (fileIsValid) {
url = file.getUrl(); // refresh url, it can differ
changed |= !url.equals(fileAndUrl.second);
}
else {
file = null; // can't find, try next time
changed = true;
}
}
Pair<VirtualFile, String> result;
if (changed) {
result = Pair.create(file, url);
myFileAndUrl = result;
}
else {
result = fileAndUrl;
}
myLastUpdated = fsModCount;
return result;
}
private static int indexOfFirstDifferentChar(@NotNull CharSequence s1, int start1, @NotNull String s2, int start2) {
boolean ignoreCase = !SystemInfo.isFileSystemCaseSensitive;
int len1 = s1.length();
int len2 = s2.length();
while (start1 < len1 && start2 < len2) {
char c1 = s1.charAt(start1);
char c2 = s2.charAt(start2);
if (!StringUtil.charsEqual(c1, c2, ignoreCase)) {
return start1;
}
start1++;
start2++;
}
return start1;
}
void associate(VirtualFilePointerImpl pointer, Pair<VirtualFile, String> fileAndUrl) {
if (pointer != null) {
pointer.myNode = this;
}
leaf = pointer;
myFileAndUrl = fileAndUrl;
myLastUpdated = -1;
}
int incrementUsageCount(int delta) {
return useCount+=delta;
}
int getPointersUnder() {
return pointersUnder;
}
}