| /* |
| * 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; |
| } |
| } |