/* * Copyright 2000-2010 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 org.community.intellij.plugins.communitycase.vfs; import com.intellij.openapi.project.Project; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vfs.*; import com.intellij.util.containers.HashSet; import org.community.intellij.plugins.communitycase.Util; import org.community.intellij.plugins.communitycase.Vcs; import java.util.*; import java.util.concurrent.atomic.AtomicReference; /** * The tracker for the references */ public class ReferenceTracker { /** * The root references that must be tracked */ private static final Set<String> myRootReferences = new HashSet<String>(); static { myRootReferences.add("HEAD"); myRootReferences.add("refs"); myRootReferences.add("MERGE_HEAD"); // note, they are here intentionally, since their presence changes the way HEAD is interpreted myRootReferences.add("rebase_merge"); myRootReferences.add("rebase_apply"); } /** * The context project */ private final Project myProject; /** * The vcs instance that requested the tracking */ private final Vcs myVcs; /** * The event multicaster */ private final ReferenceListener myListener; /** * The listener for vcs roots */ private final RootsListener myRootsListener; /** * The list of roots */ private final AtomicReference<List<VirtualFile>> myRoots = new AtomicReference<List<VirtualFile>>(Collections.<VirtualFile>emptyList()); /** * The list of roots */ private final AtomicReference<State> myState = new AtomicReference<State>(State.DEACTIVATED); /** * File system listener */ private final MyVfsListener myVfsListener; /** * The reference tracker * * @param project the project * @param vcs the vcs that created tracker * @param listener the listener to use for notifications (multicaster is expected) */ public ReferenceTracker(Project project, Vcs vcs, ReferenceListener listener) { myProject = project; myVcs = vcs; myListener = listener; myVfsListener = new MyVfsListener(); myRootsListener = new RootsListener() { @Override public void RootsChanged() { checkRoots(); } }; } /** * Start listening for events */ public void activate() { if (!myState.compareAndSet(State.DEACTIVATED, State.ACTIVATED)) { return; } if (myProject.isDefault()) { return; } checkRoots(); LocalFileSystem.getInstance().addVirtualFileListener(myVfsListener); myVcs.addRootsListener(myRootsListener); } /** * Deactivate service */ public void deactivate() { if (!myState.compareAndSet(State.ACTIVATED, State.DEACTIVATED)) { return; } removeListeners(); } /** * Remove listeners from services */ private void removeListeners() { myVcs.removeRootsListener(myRootsListener); LocalFileSystem.getInstance().removeVirtualFileListener(myVfsListener); } /** * Dispose the tracker removing all listeners */ public void dispose() { if (myState.getAndSet(State.DISPOSED) != State.ACTIVATED) { return; } removeListeners(); } /** * Visit roots so file listener will be modified if special status changed */ private void checkRoots() { try { List<VirtualFile> roots = Util.getRoots(myProject, myVcs); myRoots.set(roots); for (VirtualFile root : roots) { final VirtualFile vc = root.findChild("."); if (vc != null) { for (String name : myRootReferences) { final VirtualFile child = vc.findChild(name); if (child != null) { child.getTimeStamp(); } } final VirtualFile infoRefs = root.findFileByRelativePath("info/refs"); if (infoRefs != null) { infoRefs.getTimeStamp(); } visitRecursively(vc.findChild("refs")); } } notifyChanges(null); } catch (VcsException e) { myRoots.set(Collections.<VirtualFile>emptyList()); } } private void notifyChanges(VirtualFile root) { myListener.referencesChanged(root); } /** * Visit recursively * * @param child the child directory to visit */ private static void visitRecursively(VirtualFile child) { if (child == null) { return; } child.getTimeStamp(); LinkedList<VirtualFile> toVisit = new LinkedList<VirtualFile>(); toVisit.add(child); while (!toVisit.isEmpty()) { VirtualFile file = toVisit.removeLast(); if (file.isValid() && file.isDirectory()) { toVisit.addAll(Arrays.asList(file.getChildren())); } } } /** * The listener for vfs events */ class MyVfsListener extends VirtualFileAdapter { @Override public void contentsChanged(VirtualFileEvent event) { checkFile(event.getParent(), event.getFileName()); } @Override public void fileCreated(VirtualFileEvent event) { checkFile(event.getParent(), event.getFileName()); } @Override public void fileDeleted(VirtualFileEvent event) { checkFile(event.getParent(), event.getFileName()); } @Override public void fileMoved(VirtualFileMoveEvent event) { checkFile(event.getNewParent(), event.getFileName()); checkFile(event.getOldParent(), event.getFileName()); } @Override public void fileCopied(VirtualFileCopyEvent event) { checkFile(event.getParent(), event.getFileName()); } @Override public void propertyChanged(VirtualFilePropertyEvent event) { if (event.getPropertyName() == VirtualFile.PROP_NAME) { checkFile(event.getParent(), (String)event.getOldValue()); checkFile(event.getParent(), (String)event.getNewValue()); } } /** * Check if the file change might affect reference mapping * * @param parent the file parent to check * @param name the name check */ private void checkFile(VirtualFile parent, String name) { VirtualFile vc = parent; while (vc != null && !vc.getName().equals(".")) { vc = vc.getParent(); } if (vc == null) { return; } if (parent == vc && myRootReferences.contains(name)) { notifyChanges(vc.getParent()); } else if (parent.getParent() == vc && name.equals("refs") && parent.getName().equals("info")) { notifyChanges(vc.getParent()); } else { while (parent != null && parent.getParent() != vc) { parent = parent.getParent(); } if (parent != null && parent.isDirectory() && parent.getName().equals("refs")) { notifyChanges(vc.getParent()); } } } } /** * The component state */ private enum State { /** * Initial state */ DEACTIVATED, /** * Started state */ ACTIVATED, /** * Disposed */ DISPOSED } }