| /* |
| * Copyright 2000-2009 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.application.ApplicationManager; |
| import com.intellij.openapi.components.ProjectComponent; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.progress.ProcessCanceledException; |
| import com.intellij.openapi.project.DumbAwareRunnable; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.startup.StartupManager; |
| import com.intellij.openapi.util.Computable; |
| import com.intellij.openapi.util.Ref; |
| import com.intellij.openapi.vcs.AbstractVcs; |
| import com.intellij.openapi.vcs.FilePath; |
| import com.intellij.openapi.vcs.ProjectLevelVcsManager; |
| import com.intellij.openapi.vcs.VcsRoot; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.util.Consumer; |
| import com.intellij.util.ReflectionUtil; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| |
| /** |
| * @author max |
| */ |
| public class VcsDirtyScopeManagerImpl extends VcsDirtyScopeManager implements ProjectComponent { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.vcs.changes.VcsDirtyScopeManagerImpl"); |
| |
| private final Project myProject; |
| private final ChangeListManager myChangeListManager; |
| private final ProjectLevelVcsManager myVcsManager; |
| |
| private final DirtBuilder myDirtBuilder; |
| private final VcsGuess myGuess; |
| private final SynchronizedLife myLife; |
| |
| private final MyProgressHolder myProgressHolder; |
| |
| public VcsDirtyScopeManagerImpl(Project project, ChangeListManager changeListManager, ProjectLevelVcsManager vcsManager) { |
| myProject = project; |
| myChangeListManager = changeListManager; |
| myVcsManager = vcsManager; |
| |
| myLife = new SynchronizedLife(); |
| myGuess = new VcsGuess(myProject); |
| myDirtBuilder = new DirtBuilder(myGuess); |
| |
| myProgressHolder = new MyProgressHolder(); |
| ((ChangeListManagerImpl) myChangeListManager).setDirtyScopeManager(this); |
| } |
| |
| public void projectOpened() { |
| if (ApplicationManager.getApplication().isUnitTestMode()) { |
| myLife.born(); |
| final AbstractVcs[] vcss = myVcsManager.getAllActiveVcss(); |
| if (vcss.length > 0) { |
| markEverythingDirty(); |
| } |
| } |
| else { |
| StartupManager.getInstance(myProject).registerPostStartupActivity(new DumbAwareRunnable() { |
| public void run() { |
| myLife.born(); |
| ApplicationManager.getApplication().executeOnPooledThread(new Runnable() { |
| public void run() { |
| markEverythingDirty(); |
| } |
| }); |
| } |
| }); |
| } |
| } |
| |
| public void suspendMe() { |
| myLife.suspendMe(); |
| } |
| |
| public void reanimate() { |
| final Ref<Boolean> wasNotEmptyRef = new Ref<Boolean>(); |
| myLife.releaseMe(new Runnable() { |
| public void run() { |
| wasNotEmptyRef.set(! myDirtBuilder.isEmpty()); |
| } |
| }); |
| if (Boolean.TRUE.equals(wasNotEmptyRef.get())) { |
| myChangeListManager.scheduleUpdate(); |
| } |
| } |
| |
| public void markEverythingDirty() { |
| if ((! myProject.isOpen()) || myProject.isDisposed() || myVcsManager.getAllActiveVcss().length == 0) return; |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("everything dirty: " + ReflectionUtil.findCallerClass(2)); |
| } |
| |
| final LifeDrop lifeDrop = myLife.doIfAlive(new Runnable() { |
| public void run() { |
| myDirtBuilder.everythingDirty(); |
| } |
| }); |
| |
| if (lifeDrop.isDone() && !lifeDrop.isSuspened()) { |
| myChangeListManager.scheduleUpdate(); |
| } |
| } |
| |
| public void projectClosed() { |
| killSelf(); |
| } |
| |
| @NotNull @NonNls |
| public String getComponentName() { |
| return "VcsDirtyScopeManager"; |
| } |
| |
| public void initComponent() {} |
| |
| private void killSelf() { |
| myLife.kill(new Runnable() { |
| public void run() { |
| myDirtBuilder.reset(); |
| } |
| }); |
| } |
| |
| public void disposeComponent() { |
| killSelf(); |
| } |
| |
| private void convertPaths(@Nullable final Collection<FilePath> from, final Collection<FilePathUnderVcs> to) { |
| if (from != null) { |
| for (FilePath fp : from) { |
| final AbstractVcs vcs = myGuess.getVcsForDirty(fp); |
| if (vcs != null) { |
| to.add(new FilePathUnderVcs(fp, vcs)); |
| } |
| } |
| } |
| } |
| |
| public void filePathsDirty(@Nullable final Collection<FilePath> filesDirty, @Nullable final Collection<FilePath> dirsRecursivelyDirty) { |
| try { |
| final ArrayList<FilePathUnderVcs> filesConverted = filesDirty == null ? null : new ArrayList<FilePathUnderVcs>(filesDirty.size()); |
| final ArrayList<FilePathUnderVcs> dirsConverted = dirsRecursivelyDirty == null ? null : new ArrayList<FilePathUnderVcs>(dirsRecursivelyDirty.size()); |
| |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| public void run() { |
| convertPaths(filesDirty, filesConverted); |
| convertPaths(dirsRecursivelyDirty, dirsConverted); |
| } |
| }); |
| final boolean haveStuff = filesConverted != null && ! filesConverted.isEmpty() |
| || dirsConverted != null && ! dirsConverted.isEmpty(); |
| if (! haveStuff) return; |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("paths dirty: " + filesConverted + "; " + dirsConverted + "; " + ReflectionUtil.findCallerClass(2)); |
| } |
| |
| takeDirt(new Consumer<DirtBuilder>() { |
| public void consume(final DirtBuilder dirt) { |
| if (filesConverted != null) { |
| for (FilePathUnderVcs root : filesConverted) { |
| dirt.addDirtyFile(root); |
| } |
| } |
| if (dirsConverted != null) { |
| for (FilePathUnderVcs root : dirsConverted) { |
| dirt.addDirtyDirRecursively(root); |
| } |
| } |
| } |
| }); |
| } catch (ProcessCanceledException ignore) { |
| } |
| } |
| |
| private void takeDirt(final Consumer<DirtBuilder> filler) { |
| final Ref<Boolean> wasNotEmptyRef = new Ref<Boolean>(); |
| final Runnable runnable = new Runnable() { |
| public void run() { |
| filler.consume(myDirtBuilder); |
| wasNotEmptyRef.set(!myDirtBuilder.isEmpty()); |
| } |
| }; |
| final LifeDrop lifeDrop = myLife.doIfAlive(runnable); |
| |
| if (lifeDrop.isDone() && !lifeDrop.isSuspened() && Boolean.TRUE.equals(wasNotEmptyRef.get())) { |
| myChangeListManager.scheduleUpdate(); |
| } |
| } |
| |
| private void convert(@Nullable final Collection<VirtualFile> from, final Collection<VcsRoot> to) { |
| if (from != null) { |
| for (VirtualFile vf : from) { |
| final AbstractVcs vcs = myGuess.getVcsForDirty(vf); |
| if (vcs != null) { |
| to.add(new VcsRoot(vcs, vf)); |
| } |
| } |
| } |
| } |
| |
| public void filesDirty(@Nullable final Collection<VirtualFile> filesDirty, @Nullable final Collection<VirtualFile> dirsRecursivelyDirty) { |
| try { |
| final ArrayList<VcsRoot> filesConverted = filesDirty == null ? null : new ArrayList<VcsRoot>(filesDirty.size()); |
| final ArrayList<VcsRoot> dirsConverted = dirsRecursivelyDirty == null ? null : new ArrayList<VcsRoot>(dirsRecursivelyDirty.size()); |
| |
| ApplicationManager.getApplication().runReadAction(new Runnable() { |
| public void run() { |
| convert(filesDirty, filesConverted); |
| convert(dirsRecursivelyDirty, dirsConverted); |
| } |
| }); |
| final boolean haveStuff = filesConverted != null && ! filesConverted.isEmpty() || dirsConverted != null && ! dirsConverted.isEmpty(); |
| if (! haveStuff) return; |
| |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("files dirty: " + filesConverted + "; " + dirsConverted + "; " + ReflectionUtil.findCallerClass(2)); |
| } |
| |
| takeDirt(new Consumer<DirtBuilder>() { |
| public void consume(final DirtBuilder dirt) { |
| if (filesConverted != null) { |
| for (VcsRoot root : filesConverted) { |
| dirt.addDirtyFile(root); |
| } |
| } |
| if (dirsConverted != null) { |
| for (VcsRoot root : dirsConverted) { |
| dirt.addDirtyDirRecursively(root); |
| } |
| } |
| } |
| }); |
| } catch (ProcessCanceledException ignore) { |
| } |
| } |
| |
| public void fileDirty(@NotNull final VirtualFile file) { |
| try { |
| final AbstractVcs vcs = myGuess.getVcsForDirty(file); |
| if (vcs == null) return; |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("file dirty: " + file + "; " + ReflectionUtil.findCallerClass(2)); |
| } |
| final VcsRoot root = new VcsRoot(vcs, file); |
| takeDirt(new Consumer<DirtBuilder>() { |
| public void consume(DirtBuilder dirtBuilder) { |
| dirtBuilder.addDirtyFile(root); |
| } |
| }); |
| } catch (ProcessCanceledException ignore) { |
| } |
| } |
| |
| public void fileDirty(@NotNull final FilePath file) { |
| try { |
| final AbstractVcs vcs = myGuess.getVcsForDirty(file); |
| if (vcs == null) return; |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("file dirty: " + file + "; " + ReflectionUtil.findCallerClass(1)); |
| } |
| final FilePathUnderVcs root = new FilePathUnderVcs(file, vcs); |
| takeDirt(new Consumer<DirtBuilder>() { |
| public void consume(DirtBuilder dirtBuilder) { |
| dirtBuilder.addDirtyFile(root); |
| } |
| }); |
| } catch (ProcessCanceledException ignore) { |
| } |
| } |
| |
| public void dirDirtyRecursively(final VirtualFile dir, final boolean scheduleUpdate) { |
| dirDirtyRecursively(dir); |
| } |
| |
| public void dirDirtyRecursively(final VirtualFile dir) { |
| try { |
| final AbstractVcs vcs = myGuess.getVcsForDirty(dir); |
| if (vcs == null) return; |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("dir dirty recursively: " + dir + "; " + ReflectionUtil.findCallerClass(2)); |
| } |
| final VcsRoot root = new VcsRoot(vcs, dir); |
| takeDirt(new Consumer<DirtBuilder>() { |
| public void consume(DirtBuilder dirtBuilder) { |
| dirtBuilder.addDirtyDirRecursively(root); |
| } |
| }); |
| } catch (ProcessCanceledException ignore) { |
| } |
| } |
| |
| public void dirDirtyRecursively(final FilePath path) { |
| try { |
| final AbstractVcs vcs = myGuess.getVcsForDirty(path); |
| if (vcs == null) return; |
| if (LOG.isDebugEnabled()) { |
| LOG.debug("dir dirty recursively: " + path + "; " + ReflectionUtil.findCallerClass(2)); |
| } |
| final FilePathUnderVcs root = new FilePathUnderVcs(path, vcs); |
| takeDirt(new Consumer<DirtBuilder>() { |
| public void consume(DirtBuilder dirtBuilder) { |
| dirtBuilder.addDirtyDirRecursively(root); |
| } |
| }); |
| } catch (ProcessCanceledException ignore) { |
| } |
| } |
| |
| private class MyProgressHolder { |
| private VcsInvalidated myInProgressState; |
| private DirtBuilderReader myInProgressDirtBuilder; |
| |
| public MyProgressHolder() { |
| myInProgressDirtBuilder = new DirtBuilder(myGuess); |
| myInProgressState = null; |
| } |
| |
| public void takeNext(final DirtBuilderReader dirtBuilder) { |
| myInProgressDirtBuilder = dirtBuilder; |
| myInProgressState = null; |
| } |
| |
| private MyProgressHolder(final DirtBuilderReader dirtBuilder, final VcsInvalidated vcsInvalidated) { |
| myInProgressDirtBuilder = dirtBuilder; |
| myInProgressState = vcsInvalidated; |
| } |
| |
| public VcsInvalidated calculateInvalidated() { |
| if (myInProgressDirtBuilder != null) { |
| return ApplicationManager.getApplication().runReadAction(new Computable<VcsInvalidated>() { |
| public VcsInvalidated compute() { |
| final Scopes scopes = new Scopes(myProject, myGuess); |
| scopes.takeDirt(myInProgressDirtBuilder); |
| return scopes.retrieveAndClear(); |
| } |
| }); |
| } |
| return myInProgressState; |
| } |
| |
| public void takeInvalidated(final VcsInvalidated invalidated) { |
| myInProgressState = invalidated; |
| myInProgressDirtBuilder = null; |
| } |
| |
| public void processed() { |
| myInProgressState = null; |
| myInProgressDirtBuilder = null; |
| } |
| |
| public MyProgressHolder copy() { |
| return new MyProgressHolder(myInProgressDirtBuilder, myInProgressState); |
| } |
| } |
| |
| @Nullable |
| public VcsInvalidated retrieveScopes() { |
| final LifeDrop lifeDrop = myLife.doIfAlive(new Runnable() { |
| public void run() { |
| myProgressHolder.takeNext(new DirtBuilder(myDirtBuilder)); |
| myDirtBuilder.reset(); |
| } |
| }); |
| |
| if (lifeDrop.isDone()) { |
| final VcsInvalidated invalidated = myProgressHolder.calculateInvalidated(); |
| |
| myLife.doIfAlive(new Runnable() { |
| public void run() { |
| myProgressHolder.takeInvalidated(invalidated); |
| } |
| }); |
| return invalidated; |
| } |
| return null; |
| } |
| |
| public void changesProcessed() { |
| myLife.doIfAlive(new Runnable() { |
| public void run() { |
| myProgressHolder.processed(); |
| } |
| }); |
| } |
| |
| @NotNull |
| @Override |
| public Collection<FilePath> whatFilesDirty(@NotNull final Collection<FilePath> files) { |
| final Collection<FilePath> result = new ArrayList<FilePath>(); |
| final Ref<MyProgressHolder> inProgressHolderRef = new Ref<MyProgressHolder>(); |
| final Ref<MyProgressHolder> currentHolderRef = new Ref<MyProgressHolder>(); |
| |
| myLife.doIfAlive(new Runnable() { |
| public void run() { |
| inProgressHolderRef.set(myProgressHolder.copy()); |
| currentHolderRef.set(new MyProgressHolder(new DirtBuilder(myDirtBuilder), null)); |
| } |
| }); |
| final VcsInvalidated inProgressInvalidated = inProgressHolderRef.get() == null ? null : inProgressHolderRef.get().calculateInvalidated(); |
| final VcsInvalidated currentInvalidated = currentHolderRef.get() == null ? null : currentHolderRef.get().calculateInvalidated(); |
| for (FilePath fp : files) { |
| if (inProgressInvalidated != null && inProgressInvalidated.isFileDirty(fp) |
| || currentInvalidated != null && currentInvalidated.isFileDirty(fp)) { |
| result.add(fp); |
| } |
| } |
| return result; |
| } |
| |
| private String toStringScopes(final VcsInvalidated vcsInvalidated) { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("is everything dirty: ").append(vcsInvalidated.isEverythingDirty()).append(";\n"); |
| for (VcsDirtyScope scope : vcsInvalidated.getScopes()) { |
| sb.append("|\nFiles: "); |
| for (FilePath path : scope.getDirtyFiles()) { |
| sb.append(path).append('\n'); |
| } |
| sb.append("\nDirs: "); |
| for (FilePath filePath : scope.getRecursivelyDirtyDirectories()) { |
| sb.append(filePath).append('\n'); |
| } |
| } |
| sb.append("-------------"); |
| return sb.toString(); |
| } |
| |
| private static class LifeDrop { |
| private final boolean myDone; |
| private final boolean mySuspened; |
| |
| private LifeDrop(boolean done, boolean suspened) { |
| myDone = done; |
| mySuspened = suspened; |
| } |
| |
| public boolean isDone() { |
| return myDone; |
| } |
| |
| public boolean isSuspened() { |
| return mySuspened; |
| } |
| } |
| |
| private static class SynchronizedLife { |
| private LifeStages myStage; |
| private final Object myLock; |
| private boolean mySuspended; |
| |
| private SynchronizedLife() { |
| myStage = LifeStages.NOT_BORN; |
| myLock = new Object(); |
| } |
| |
| public void born() { |
| synchronized (myLock) { |
| myStage = LifeStages.ALIVE; |
| } |
| } |
| |
| public void kill(Runnable runnable) { |
| synchronized (myLock) { |
| myStage = LifeStages.DEAD; |
| runnable.run(); |
| } |
| } |
| |
| public void suspendMe() { |
| synchronized (myLock) { |
| if (LifeStages.ALIVE.equals(myStage)) { |
| mySuspended = true; |
| } |
| } |
| } |
| |
| public void releaseMe(final Runnable runnable) { |
| synchronized (myLock) { |
| if (LifeStages.ALIVE.equals(myStage)) { |
| mySuspended = false; |
| runnable.run(); |
| } |
| } |
| } |
| |
| public LifeDrop doIfAliveAndNotSuspended(final Runnable runnable) { |
| synchronized (myLock) { |
| synchronized (myLock) { |
| if (LifeStages.ALIVE.equals(myStage) && ! mySuspended) { |
| runnable.run(); |
| return new LifeDrop(true, mySuspended); |
| } |
| return new LifeDrop(false, mySuspended); |
| } |
| } |
| } |
| |
| // allow work under inner lock: inner class, not wide scope |
| public LifeDrop doIfAlive(final Runnable runnable) { |
| synchronized (myLock) { |
| if (LifeStages.ALIVE.equals(myStage)) { |
| runnable.run(); |
| return new LifeDrop(true, mySuspended); |
| } |
| return new LifeDrop(false, mySuspended); |
| } |
| } |
| |
| private static enum LifeStages { |
| NOT_BORN, |
| ALIVE, |
| DEAD |
| } |
| } |
| } |