| /* |
| * Copyright 2000-2013 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.vcs.changes; |
| |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.vcs.*; |
| import com.intellij.openapi.vcs.annotate.FileAnnotation; |
| import com.intellij.openapi.vcs.diff.DiffProvider; |
| import com.intellij.openapi.vcs.history.VcsRevisionNumber; |
| import com.intellij.openapi.vfs.LocalFileSystem; |
| import com.intellij.openapi.vfs.VfsUtilCore; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.util.Alarm; |
| import com.intellij.util.containers.MultiMap; |
| import com.intellij.util.messages.MessageBusConnection; |
| |
| import java.io.File; |
| import java.util.*; |
| |
| /** |
| * Created with IntelliJ IDEA. |
| * User: Irina.Chernushina |
| * Date: 11/20/12 |
| * Time: 11:31 AM |
| */ |
| public class VcsAnnotationLocalChangesListenerImpl implements Disposable, VcsAnnotationLocalChangesListener { |
| private final ZipperUpdater myUpdater; |
| private final MessageBusConnection myConnection; |
| |
| private final Runnable myUpdateStuff; |
| |
| private final Set<String> myDirtyPaths; |
| private final Set<VirtualFile> myDirtyFiles; |
| private final Map<String, VcsRevisionNumber> myDirtyChanges; |
| private final LocalFileSystem myLocalFileSystem; |
| private final ProjectLevelVcsManager myVcsManager; |
| private final Set<VcsKey> myVcsKeySet; |
| private final Object myLock; |
| |
| private final MultiMap<VirtualFile, FileAnnotation> myFileAnnotationMap; |
| |
| public VcsAnnotationLocalChangesListenerImpl(Project project, final ProjectLevelVcsManager vcsManager) { |
| myLock = new Object(); |
| myUpdateStuff = createUpdateStuff(); |
| myUpdater = new ZipperUpdater(ApplicationManager.getApplication().isUnitTestMode() ? 10 : 300, Alarm.ThreadToUse.POOLED_THREAD, project); |
| myConnection = project.getMessageBus().connect(); |
| myLocalFileSystem = LocalFileSystem.getInstance(); |
| VcsAnnotationRefresher handler = createHandler(); |
| myDirtyPaths = new HashSet<String>(); |
| myDirtyChanges = new HashMap<String, VcsRevisionNumber>(); |
| myDirtyFiles = new HashSet<VirtualFile>(); |
| myFileAnnotationMap = MultiMap.createSet(); |
| myVcsManager = vcsManager; |
| myVcsKeySet = new HashSet<VcsKey>(); |
| |
| myConnection.subscribe(VcsAnnotationRefresher.LOCAL_CHANGES_CHANGED, handler); |
| } |
| |
| private Runnable createUpdateStuff() { |
| return new Runnable() { |
| @Override |
| public void run() { |
| final Set<String> paths = new HashSet<String>(); |
| final Map<String, VcsRevisionNumber> changes = new HashMap<String, VcsRevisionNumber>(); |
| final Set<VirtualFile> files = new HashSet<VirtualFile>(); |
| Set<VcsKey> vcsToRefresh; |
| synchronized (myLock) { |
| vcsToRefresh = new HashSet<VcsKey>(myVcsKeySet); |
| |
| paths.addAll(myDirtyPaths); |
| changes.putAll(myDirtyChanges); |
| files.addAll(myDirtyFiles); |
| myDirtyPaths.clear(); |
| myDirtyChanges.clear(); |
| myVcsKeySet.clear(); |
| myDirtyFiles.clear(); |
| } |
| |
| closeForVcs(vcsToRefresh); |
| checkByDirtyScope(paths, changes, files); |
| } |
| }; |
| } |
| |
| private void checkByDirtyScope(Set<String> removed, Map<String, VcsRevisionNumber> refresh, Set<VirtualFile> files) { |
| for (String path : removed) { |
| refreshForPath(path, null); |
| } |
| for (Map.Entry<String, VcsRevisionNumber> entry : refresh.entrySet()) { |
| refreshForPath(entry.getKey(), entry.getValue()); |
| } |
| for (VirtualFile file : files) { |
| processUnderFile(file); |
| } |
| } |
| |
| private void processUnderFile(VirtualFile file) { |
| final MultiMap<VirtualFile, FileAnnotation> annotations = new MultiMap<VirtualFile, FileAnnotation>(); |
| synchronized (myLock) { |
| for (VirtualFile virtualFile : myFileAnnotationMap.keySet()) { |
| if (VfsUtilCore.isAncestor(file, virtualFile, true)) { |
| final Collection<FileAnnotation> values = myFileAnnotationMap.get(virtualFile); |
| for (FileAnnotation value : values) { |
| annotations.putValue(virtualFile, value); |
| } |
| } |
| } |
| } |
| if (! annotations.isEmpty()) { |
| for (Map.Entry<VirtualFile, Collection<FileAnnotation>> entry : annotations.entrySet()) { |
| final VirtualFile key = entry.getKey(); |
| final VcsRevisionNumber number = fromDiffProvider(key); |
| if (number == null) continue; |
| final Collection<FileAnnotation> fileAnnotations = entry.getValue(); |
| for (FileAnnotation annotation : fileAnnotations) { |
| if (annotation.isBaseRevisionChanged(number)) { |
| annotation.close(); |
| } |
| } |
| } |
| } |
| } |
| |
| private void refreshForPath(String path, VcsRevisionNumber number) { |
| final File file = new File(path); |
| VirtualFile vf = myLocalFileSystem.findFileByIoFile(file); |
| if (vf == null) { |
| vf = myLocalFileSystem.refreshAndFindFileByIoFile(file); |
| } |
| if (vf == null) return; |
| processFile(number, vf); |
| } |
| |
| private void processFile(VcsRevisionNumber number, VirtualFile vf) { |
| final Collection<FileAnnotation> annotations; |
| synchronized (myLock) { |
| annotations = myFileAnnotationMap.get(vf); |
| } |
| if (! annotations.isEmpty()) { |
| if (number == null) { |
| number = fromDiffProvider(vf); |
| } |
| if (number == null) return; |
| |
| for (FileAnnotation annotation : annotations) { |
| if (annotation.isBaseRevisionChanged(number)) { |
| annotation.close(); |
| } |
| } |
| } |
| } |
| |
| private VcsRevisionNumber fromDiffProvider(final VirtualFile vf) { |
| final VcsRoot vcsRoot = myVcsManager.getVcsRootObjectFor(vf); |
| DiffProvider diffProvider; |
| if (vcsRoot != null && vcsRoot.getVcs() != null && (diffProvider = vcsRoot.getVcs().getDiffProvider()) != null) { |
| return diffProvider.getCurrentRevision(vf); |
| } |
| return null; |
| } |
| |
| private void closeForVcs(final Set<VcsKey> refresh) { |
| if (refresh.isEmpty()) return; |
| final Set<FileAnnotation> copy = new HashSet<FileAnnotation>(); |
| synchronized (myLock) { |
| for (FileAnnotation annotation : myFileAnnotationMap.values()) { |
| final VcsKey key = annotation.getVcsKey(); |
| if (key != null && refresh.contains(key)) { |
| copy.add(annotation); |
| } |
| } |
| } |
| for (FileAnnotation annotation : copy) { |
| annotation.close(); |
| } |
| } |
| |
| // annotations for already committed revisions should not register with this method - they are not subject to refresh |
| @Override |
| public void registerAnnotation(final VirtualFile file, final FileAnnotation annotation) { |
| synchronized (myLock) { |
| myFileAnnotationMap.putValue(file, annotation); |
| } |
| } |
| |
| @Override |
| public void unregisterAnnotation(final VirtualFile file, final FileAnnotation annotation) { |
| synchronized (myLock) { |
| final Collection<FileAnnotation> annotations = myFileAnnotationMap.get(file); |
| if (!annotations.isEmpty()) { |
| annotations.remove(annotation); |
| } |
| if (annotations.isEmpty()) { |
| myFileAnnotationMap.remove(file); |
| } |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| myConnection.disconnect(); |
| myUpdater.stop(); |
| } |
| |
| private VcsAnnotationRefresher createHandler() { |
| return new VcsAnnotationRefresher() { |
| @Override |
| public void dirtyUnder(VirtualFile file) { |
| if (file == null) return; |
| synchronized (myLock) { |
| myDirtyFiles.add(file); |
| } |
| myUpdater.queue(myUpdateStuff); |
| } |
| |
| @Override |
| public void dirty(BaseRevision currentRevision) { |
| synchronized (myLock) { |
| myDirtyChanges.put(currentRevision.getPath(), currentRevision.getRevision()); |
| } |
| myUpdater.queue(myUpdateStuff); |
| } |
| |
| @Override |
| public void dirty(String path) { |
| synchronized (myLock) { |
| myDirtyPaths.add(path); |
| } |
| myUpdater.queue(myUpdateStuff); |
| } |
| |
| @Override |
| public void configurationChanged(VcsKey vcsKey) { |
| synchronized (myLock) { |
| myVcsKeySet.add(vcsKey); |
| } |
| myUpdater.queue(myUpdateStuff); |
| } |
| }; |
| } |
| } |