diff --git a/platform/platform-api/src/com/intellij/util/concurrency/QueueProcessorRemovePartner.java b/platform/platform-api/src/com/intellij/util/concurrency/QueueProcessorRemovePartner.java new file mode 100644 index 000000000000..9cecc5dd14ad --- /dev/null +++ b/platform/platform-api/src/com/intellij/util/concurrency/QueueProcessorRemovePartner.java @@ -0,0 +1,83 @@ +/* + * Copyright 2000-2011 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.util.concurrency; + +import com.intellij.openapi.project.Project; +import com.intellij.util.Consumer; + +import java.util.HashMap; +import java.util.Map; + +/** + * @author irengrig + * Date: 2/28/11 + * Time: 3:41 PM + */ +public class QueueProcessorRemovePartner { + private final Map myMap; + private final QueueProcessor myProcessor; + private final Consumer myConsumer; + private final Object myLock; + + public QueueProcessorRemovePartner(final Project project, Consumer consumer) { + myConsumer = consumer; + myMap = new HashMap(); + myLock = new Object(); + myProcessor = new QueueProcessor(new Consumer() { + @Override + public void consume(Key key) { + final Task task; + synchronized (myLock) { + task = myMap.remove(key); + } + if (task != null) { + myConsumer.consume(task); + } + } + }, project.getDisposed(), true); + } + + public void add(final Key key, final Task task) { + synchronized (myLock) { + myMap.put(key, task); + } + myProcessor.add(key); + } + + public void remove(final Key key) { + synchronized (myLock) { + myMap.remove(key); + } + } + + public boolean isEmpty() { + synchronized (myLock) { + return myMap.isEmpty(); + } + } + + public void clear() { + synchronized (myLock) { + myMap.clear(); + } + } + + public boolean containsKey(final Key key) { + synchronized (myLock) { + return myMap.containsKey(key); + } + } +} diff --git a/platform/platform-resources/src/componentSets/VCS.xml b/platform/platform-resources/src/componentSets/VCS.xml index 47511b72e194..23e5f063f4a7 100644 --- a/platform/platform-resources/src/componentSets/VCS.xml +++ b/platform/platform-resources/src/componentSets/VCS.xml @@ -101,5 +101,8 @@ com.intellij.openapi.vcs.impl.VcsFileStatusProvider + com.intellij.openapi.vcs.impl.VcsBaseContentProvider + com.intellij.openapi.vcs.impl.VcsFileStatusProvider + diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java index b9360ff07fa6..d9b6c8ad647b 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/ex/LineStatusTracker.java @@ -30,6 +30,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vcs.VcsBundle; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -60,6 +61,9 @@ public class LineStatusTracker { private boolean myBulkUpdate; private final Application myApplication; + @Nullable + private RevisionPack myBaseRevisionNumber; + private String myPreviousBaseRevision; private LineStatusTracker(final Document document, final Document upToDateDocument, final Project project) { myApplication = ApplicationManager.getApplication(); @@ -71,12 +75,16 @@ public class LineStatusTracker { myRanges = new ArrayList(); } - public void initialize(@NotNull final String upToDateContent) { + public void initialize(@NotNull final String upToDateContent, @NotNull RevisionPack baseRevisionNumber) { ApplicationManager.getApplication().assertIsDispatchThread(); synchronized (myLock) { - LOG.assertTrue(BaseLoadState.LOADING == myBaseLoaded); try { + if (myBaseRevisionNumber != null && myBaseRevisionNumber.after(baseRevisionNumber)) return; + + myBaseRevisionNumber = baseRevisionNumber; + myPreviousBaseRevision = null; + myUpToDateDocument.setReadOnly(false); myUpToDateDocument.replaceString(0, myUpToDateDocument.getTextLength(), upToDateContent); myUpToDateDocument.setReadOnly(true); @@ -93,6 +101,20 @@ public class LineStatusTracker { } } + public void useCachedBaseRevision(final RevisionPack number) { + synchronized (myLock) { + assert myBaseRevisionNumber != null; + if (myPreviousBaseRevision == null || myBaseRevisionNumber.after(number)) return; + initialize(myPreviousBaseRevision, number); + } + } + + public boolean canUseBaseRevision(final RevisionPack number) { + synchronized (myLock) { + return myBaseRevisionNumber != null && myBaseRevisionNumber.equals(number) && myPreviousBaseRevision != null; + } + } + private void reinstallRanges() { myApplication.assertReadAccessAllowed(); @@ -194,18 +216,22 @@ public class LineStatusTracker { * @return true if was cleared and base revision contents load should be started * false -> load was already started; after contents is loaded, */ - public boolean resetForBaseRevisionLoad() { + public void resetForBaseRevisionLoad() { myApplication.assertReadAccessAllowed(); synchronized (myLock) { - if (BaseLoadState.LOADING == myBaseLoaded) return false; + // there can be multiple resets before init -> take from document only firts time -> when right after install(), + // where myPreviousBaseRevision become null + if (BaseLoadState.LOADED.equals(myBaseLoaded) && myPreviousBaseRevision == null) { + myPreviousBaseRevision = myUpToDateDocument.getText(); + } myUpToDateDocument.setReadOnly(false); myUpToDateDocument.setText(""); myUpToDateDocument.setReadOnly(true); removeHighlightersFromMarkupModel(); myRanges.clear(); myBaseLoaded = BaseLoadState.LOADING; - return true; + return; } } @@ -546,10 +572,6 @@ public class LineStatusTracker { return new LineStatusTracker(doc, document, project); } - public BaseLoadState getBaseLoaded() { - return myBaseLoaded; - } - public void baseRevisionLoadFailed() { synchronized (myLock) { myBaseLoaded = BaseLoadState.FAILED; @@ -561,4 +583,42 @@ public class LineStatusTracker { FAILED, LOADED } + + public static class RevisionPack { + private final long myNumber; + private final VcsRevisionNumber myRevision; + + public RevisionPack(long number, VcsRevisionNumber revision) { + myNumber = number; + myRevision = revision; + } + + public long getNumber() { + return myNumber; + } + + public VcsRevisionNumber getRevision() { + return myRevision; + } + + public boolean after(final RevisionPack previous) { + if (myRevision.equals(previous.getRevision())) return false; + return myNumber > previous.getNumber(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + RevisionPack that = (RevisionPack)o; + + return myRevision.equals(that.getRevision()); + } + + @Override + public int hashCode() { + return myRevision.hashCode(); + } + } } diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/LineStatusTrackerManager.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/LineStatusTrackerManager.java index b257559e09ca..96dc1ff15c6f 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/LineStatusTrackerManager.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/LineStatusTrackerManager.java @@ -45,24 +45,21 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vcs.AbstractVcs; -import com.intellij.openapi.vcs.FileStatus; -import com.intellij.openapi.vcs.FileStatusListener; -import com.intellij.openapi.vcs.FileStatusManager; +import com.intellij.openapi.vcs.*; import com.intellij.openapi.vcs.changes.committed.AbstractCalledLater; import com.intellij.openapi.vcs.ex.LineStatusTracker; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileAdapter; import com.intellij.openapi.vfs.VirtualFileEvent; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.testFramework.LightVirtualFile; -import com.intellij.util.Alarm; +import com.intellij.util.Consumer; +import com.intellij.util.concurrency.QueueProcessorRemovePartner; import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import java.util.Collection; -import java.util.Collections; import java.util.Map; public class LineStatusTrackerManager implements ProjectComponent, LineStatusTrackerManagerI { @@ -81,25 +78,32 @@ public class LineStatusTrackerManager implements ProjectComponent, LineStatusTra private final Map myLineStatusTrackers; // !!! no state queries and self lock for add/remove // removal from here - not under write action - private final Map myLineStatusUpdateAlarms; + private final QueueProcessorRemovePartner myPartner; @NonNls protected static final String IGNORE_CHANGEMARKERS_KEY = "idea.ignore.changemarkers"; - private final ProjectLevelVcsManagerImpl myVcsManager; - private final VcsFileStatusProvider myStatusProvider; + private final ProjectLevelVcsManager myVcsManager; + private final VcsBaseContentProvider myStatusProvider; private final Application myApplication; private final FileEditorManager myFileEditorManager; private final Disposable myDisposable; + private long myLoadCounter; - public LineStatusTrackerManager(final Project project, final ProjectLevelVcsManagerImpl vcsManager, final VcsFileStatusProvider statusProvider, + public LineStatusTrackerManager(final Project project, final ProjectLevelVcsManager vcsManager, final VcsBaseContentProvider statusProvider, final Application application, final FileEditorManager fileEditorManager) { + myLoadCounter = 0; myProject = project; myVcsManager = vcsManager; myStatusProvider = statusProvider; myApplication = application; myFileEditorManager = fileEditorManager; myLineStatusTrackers = new HashMap(); - myLineStatusUpdateAlarms = Collections.synchronizedMap(new HashMap()); + myPartner = new QueueProcessorRemovePartner(myProject, new Consumer() { + @Override + public void consume(BaseRevisionLoader baseRevisionLoader) { + baseRevisionLoader.run(); + } + }); project.getMessageBus().connect().subscribe(DocumentBulkUpdateListener.TOPIC, new DocumentBulkUpdateListener.Adapter() { public void updateStarted(final Document doc) { @@ -119,16 +123,13 @@ public class LineStatusTrackerManager implements ProjectComponent, LineStatusTra synchronized (myLock) { for (final LineStatusTracker tracker : myLineStatusTrackers.values()) { final Document document = tracker.getDocument(); - final Alarm alarm = myLineStatusUpdateAlarms.remove(document); - if (alarm != null) { - alarm.cancelAllRequests(); - } + myPartner.remove(document); tracker.release(); } myLineStatusTrackers.clear(); - assert myLineStatusUpdateAlarms.isEmpty(); - myLineStatusUpdateAlarms.clear(); + assert myPartner.isEmpty(); + myPartner.clear(); } } }; @@ -214,8 +215,6 @@ public class LineStatusTrackerManager implements ProjectComponent, LineStatusTra if (! shouldBeInstalled) { releaseTracker(document); return; - } else if ((LineStatusTracker.BaseLoadState.LOADING == tracker.getBaseLoaded())) { - return; // will be recalculated } else { tracker.resetForBaseRevisionLoad(); startAlarm(document, virtualFile); @@ -230,10 +229,7 @@ public class LineStatusTrackerManager implements ProjectComponent, LineStatusTra if ((! myProject.isOpen()) || myProject.isDisposed()) return; synchronized (myLock) { - final Alarm alarm = myLineStatusUpdateAlarms.remove(document); - if (alarm != null) { - alarm.cancelAllRequests(); - } + myPartner.remove(document); final LineStatusTracker tracker = myLineStatusTrackers.remove(document); if (tracker != null) { tracker.release(); @@ -265,7 +261,7 @@ public class LineStatusTrackerManager implements ProjectComponent, LineStatusTra private void installTracker(final VirtualFile virtualFile, final Document document) { synchronized (myLock) { if (myLineStatusTrackers.containsKey(document)) return; - assert !myLineStatusUpdateAlarms.containsKey(document); + assert ! myPartner.containsKey(document); final LineStatusTracker tracker = LineStatusTracker.createOn(document, myProject); myLineStatusTrackers.put(document, tracker); @@ -277,33 +273,22 @@ public class LineStatusTrackerManager implements ProjectComponent, LineStatusTra private void startAlarm(final Document document, final VirtualFile virtualFile) { myApplication.assertReadAccessAllowed(); - final Alarm alarm = new Alarm(Alarm.ThreadToUse.SHARED_THREAD); synchronized (myLock) { - myLineStatusUpdateAlarms.put(document, alarm); + myPartner.add(document, new BaseRevisionLoader(document, virtualFile)); } - alarm.addRequest(new BaseRevisionLoader(alarm, document, virtualFile), 10); } private class BaseRevisionLoader implements Runnable { - private final Alarm myAlarm; private final VirtualFile myVirtualFile; private final Document myDocument; - private BaseRevisionLoader(final Alarm alarm, final Document document, final VirtualFile virtualFile) { - myAlarm = alarm; + private BaseRevisionLoader(final Document document, final VirtualFile virtualFile) { myDocument = document; myVirtualFile = virtualFile; } @Override public void run() { - myAlarm.cancelAllRequests(); - synchronized (myLock) { - final Alarm removed = myLineStatusUpdateAlarms.remove(myDocument); - if (removed == null) { - return; - } - } if ((! myProject.isOpen()) || myProject.isDisposed()) return; if (! myVirtualFile.isValid()) { @@ -312,6 +297,31 @@ public class LineStatusTrackerManager implements ProjectComponent, LineStatusTra return; } + final VcsRevisionNumber baseRevision = myStatusProvider.getBaseRevision(myVirtualFile); + if (baseRevision == null) { + log("installTracker() for file " + myVirtualFile.getPath() + " failed: null returned for base revision number"); + reportTrackerBaseLoadFailed(); + return; + } + // loads are sequential (in single threaded QueueProcessor); + // so myLoadCounter can't take less value for greater base revision -> the only thing we want from it + final LineStatusTracker.RevisionPack revisionPack = new LineStatusTracker.RevisionPack(myLoadCounter, baseRevision); + ++ myLoadCounter; + + synchronized (myLock) { + final LineStatusTracker tracker = myLineStatusTrackers.get(myDocument); + if (tracker != null && tracker.canUseBaseRevision(revisionPack)) { + nonModalAliveInvokeLater(new Runnable() { + @Override + public void run() { + log("installTracker() for file " + myVirtualFile.getPath() + " base revision number already in tracker"); + tracker.useCachedBaseRevision(revisionPack); + } + }); + // already ok revision + return; + } + } final String lastUpToDateContent = myStatusProvider.getBaseVersionContent(myVirtualFile); if (lastUpToDateContent == null) { log("installTracker() for file " + myVirtualFile.getPath() + " failed: no up to date content"); @@ -320,17 +330,22 @@ public class LineStatusTrackerManager implements ProjectComponent, LineStatusTra } final String converted = StringUtil.convertLineSeparators(lastUpToDateContent); - myApplication.invokeLater(new Runnable() { + final Runnable runnable = new Runnable() { public void run() { synchronized (myLock) { log("initializing tracker for file " + myVirtualFile.getPath()); final LineStatusTracker tracker = myLineStatusTrackers.get(myDocument); if (tracker != null) { - tracker.initialize(converted); + tracker.initialize(converted, revisionPack); } } } - }, new Condition() { + }; + nonModalAliveInvokeLater(runnable); + } + + private void nonModalAliveInvokeLater(Runnable runnable) { + myApplication.invokeLater(runnable, ModalityState.NON_MODAL, new Condition() { @Override public boolean value(final Object ignore) { return (! myProject.isOpen()) || myProject.isDisposed(); diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsBaseContentProvider.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsBaseContentProvider.java new file mode 100644 index 000000000000..4f6cd6688391 --- /dev/null +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsBaseContentProvider.java @@ -0,0 +1,32 @@ +/* + * Copyright 2000-2011 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.impl; + +import com.intellij.openapi.vcs.history.VcsRevisionNumber; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; + +/** + * @author irengrig + * Date: 2/28/11 + * Time: 1:12 PM + */ +public interface VcsBaseContentProvider { + @Nullable + String getBaseVersionContent(VirtualFile file); + @Nullable + VcsRevisionNumber getBaseRevision(VirtualFile file); +} diff --git a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsFileStatusProvider.java b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsFileStatusProvider.java index 8b151a35cb6d..0982ebbbf207 100644 --- a/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsFileStatusProvider.java +++ b/platform/vcs-impl/src/com/intellij/openapi/vcs/impl/VcsFileStatusProvider.java @@ -27,6 +27,7 @@ import com.intellij.openapi.vcs.FileStatus; import com.intellij.openapi.vcs.ProjectLevelVcsManager; import com.intellij.openapi.vcs.VcsException; import com.intellij.openapi.vcs.changes.*; +import com.intellij.openapi.vcs.history.VcsRevisionNumber; import com.intellij.openapi.vcs.readOnlyHandler.ReadonlyStatusHandlerImpl; import com.intellij.openapi.vcs.rollback.RollbackEnvironment; import com.intellij.openapi.vfs.VirtualFile; @@ -35,7 +36,7 @@ import org.jetbrains.annotations.Nullable; /** * @author yole */ -public class VcsFileStatusProvider implements FileStatusProvider { +public class VcsFileStatusProvider implements FileStatusProvider, VcsBaseContentProvider { private final Project myProject; private final FileStatusManagerImpl myFileStatusManager; private final ProjectLevelVcsManager myVcsManager; @@ -133,8 +134,9 @@ public class VcsFileStatusProvider implements FileStatusProvider { } } + @Override @Nullable - String getBaseVersionContent(final VirtualFile file) { + public String getBaseVersionContent(final VirtualFile file) { final Change change = ChangeListManager.getInstance(myProject).getChange(file); if (change != null) { final ContentRevision beforeRevision = change.getBeforeRevision(); @@ -166,4 +168,13 @@ public class VcsFileStatusProvider implements FileStatusProvider { return null; } + + @Override + public VcsRevisionNumber getBaseRevision(VirtualFile file) { + final Change change = ChangeListManager.getInstance(myProject).getChange(file); + if (change != null && change.getBeforeRevision() != null && (! FileStatus.ADDED.equals(change.getFileStatus()))) { + return change.getBeforeRevision().getRevisionNumber(); + } + return null; + } }