| /* |
| * 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 org.jetbrains.idea.svn.history; |
| |
| import com.intellij.lifecycle.PeriodicalTasksCloser; |
| import com.intellij.openapi.Disposable; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Disposer; |
| import com.intellij.openapi.vcs.RepositoryLocation; |
| import com.intellij.openapi.vcs.changes.committed.ChangesBunch; |
| import com.intellij.openapi.vcs.changes.committed.CommittedChangesAdapter; |
| import com.intellij.openapi.vcs.changes.committed.CommittedChangesCache; |
| import com.intellij.openapi.vcs.versionBrowser.CommittedChangeList; |
| import com.intellij.util.containers.SoftHashMap; |
| import com.intellij.util.messages.MessageBusConnection; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.*; |
| |
| public class LoadedRevisionsCache implements Disposable { |
| private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.history.LoadedRevisionsCache"); |
| |
| private final Project myProject; |
| private final Map<String, Bunch> myMap; |
| private final Object refreshLock = new Object(); |
| private long myRefreshTime; |
| private final MessageBusConnection myConnection; |
| |
| public static LoadedRevisionsCache getInstance(final Project project) { |
| return PeriodicalTasksCloser.getInstance().safeGetService(project, LoadedRevisionsCache.class); |
| } |
| |
| private LoadedRevisionsCache(final Project project) { |
| myProject = project; |
| myMap = (ApplicationManager.getApplication().isUnitTestMode()) ? new HashMap<String, Bunch>() : new SoftHashMap<String, Bunch>(); |
| |
| myConnection = project.getMessageBus().connect(); |
| myConnection.subscribe(CommittedChangesCache.COMMITTED_TOPIC, new CommittedChangesAdapter() { |
| |
| @Override |
| public void changesLoaded(final RepositoryLocation location, final List<CommittedChangeList> changes) { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| public void run() { |
| myMap.clear(); |
| setRefreshTime(System.currentTimeMillis()); |
| } |
| }); |
| } |
| }); |
| Disposer.register(myProject, this); |
| setRefreshTime(0); |
| } |
| |
| private long getRefreshTime() { |
| synchronized (refreshLock) { |
| return myRefreshTime; |
| } |
| } |
| |
| private void setRefreshTime(final long refreshTime) { |
| synchronized (refreshLock) { |
| myRefreshTime = refreshTime; |
| } |
| } |
| |
| private static void debugInfo(@NotNull List<CommittedChangeList> data, final boolean consistentWithPrevious, final Bunch bindTo) { |
| LOG.debug(">>> cache internal >>> consistent: " + consistentWithPrevious + " bindTo: " + bindTo + |
| " oldest list: " + data.get(data.size() - 1).getNumber() + ", youngest list: " + data.get(0).getNumber()); |
| } |
| |
| public void dispose() { |
| // TODO: Seems that dispose could be removed as connection will be disposed anyway on project dispose and clearing map is not necessary |
| myConnection.disconnect(); |
| myMap.clear(); |
| } |
| |
| @NotNull |
| private static List<List<CommittedChangeList>> split(final List<CommittedChangeList> list, final int size) { |
| final int listSize = list.size(); |
| if (listSize < size) { |
| return Collections.singletonList(list); |
| } |
| final int first = listSize % size; |
| |
| int start = 0; |
| int end = (first == 0) ? (Math.min(listSize, size)) : first; |
| final List<List<CommittedChangeList>> result = new ArrayList<List<CommittedChangeList>>(listSize / size + 1); |
| while (start < listSize) { |
| result.add(list.subList(start, end)); |
| start = end; |
| end += size; |
| } |
| return result; |
| } |
| |
| @Nullable |
| public Bunch put(final List<CommittedChangeList> data, final boolean consistentWithPrevious, final Bunch bindTo) { |
| if (data.isEmpty()) { |
| return null; |
| } |
| final SvnRepositoryLocation repositoryLocation = ((SvnChangeList) data.get(0)).getLocation(); |
| final String location = repositoryLocation.getURL(); |
| |
| final List<List<CommittedChangeList>> list = split(data, SvnRevisionsNavigationMediator.CHUNK_SIZE); |
| |
| Bunch bindToBunch = bindTo; |
| if (bindToBunch == null) { |
| final Bunch fromCache = myMap.get(location); |
| if (fromCache != null) { |
| final long passedSmallestNumber = data.get(data.size() - 1).getNumber(); |
| final List<CommittedChangeList> cachedList = fromCache.getList(); |
| final long greatestNumber = cachedList.get(0).getNumber(); |
| if (greatestNumber < passedSmallestNumber) { |
| bindToBunch = fromCache; |
| } |
| } |
| } |
| boolean consistent = consistentWithPrevious; |
| for (int i = list.size() - 1; i >= 0; -- i) { |
| final List<CommittedChangeList> changeLists = list.get(i); |
| debugInfo(changeLists, consistent, bindToBunch); |
| bindToBunch = new Bunch(changeLists, consistent, bindToBunch); |
| consistent = true; |
| } |
| |
| myMap.put(location, bindToBunch); |
| return bindToBunch; |
| } |
| |
| @Nullable |
| public Iterator<ChangesBunch> iterator(final String location) { |
| final Bunch bunch = myMap.get(location); |
| if (bunch == null) { |
| return null; |
| } |
| return new BunchIterator(bunch); |
| } |
| |
| private class BunchIterator implements Iterator<ChangesBunch> { |
| private final long myCreationTime; |
| private Bunch myBunch; |
| |
| private BunchIterator(final Bunch bunch) { |
| myBunch = bunch; |
| myCreationTime = System.currentTimeMillis(); |
| } |
| |
| private void checkValidity() { |
| ApplicationManager.getApplication().assertIsDispatchThread(); |
| |
| if (myCreationTime <= getRefreshTime()) { |
| throw new SwitchRevisionsProviderException(); |
| } |
| } |
| |
| public boolean hasNext() { |
| checkValidity(); |
| return myBunch != null; |
| } |
| |
| public ChangesBunch next() { |
| checkValidity(); |
| final Bunch current = myBunch; |
| myBunch = myBunch.myNext; |
| return current; |
| } |
| |
| public void remove() { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| |
| public static class Bunch extends ChangesBunch { |
| private final Bunch myNext; |
| |
| private Bunch(final List<CommittedChangeList> list, final boolean consistent, final Bunch next) { |
| super(list, consistent); |
| myNext = next; |
| } |
| |
| public Bunch getNext() { |
| return myNext; |
| } |
| } |
| } |