blob: 600eb1a04e7141dbe3380ec3fac21434ac5577ef [file] [log] [blame]
/*
* Copyright 2000-2014 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.dialogs;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.TransparentlyFailedValue;
import com.intellij.openapi.vcs.changes.TransparentlyFailedValueI;
import com.intellij.openapi.vcs.persistent.SmallMapSerializer;
import com.intellij.util.Consumer;
import com.intellij.util.ThrowableConvertor;
import com.intellij.util.ValueHolder;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.continuation.TaskDescriptor;
import com.intellij.util.io.DataExternalizer;
import com.intellij.util.io.EnumeratorStringDescriptor;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.SvnVcs;
import org.jetbrains.idea.svn.history.CopyData;
import org.jetbrains.idea.svn.history.FirstInBranch;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class SvnBranchPointsCalculator {
private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.dialogs.SvnBranchPointsCalculator");
private FactsCalculator<KeyData, WrapperInvertor, VcsException> myCalculator;
private PersistentHolder myPersistentHolder;
private File myFile;
private final Project myProject;
public SvnBranchPointsCalculator(final Project project) {
myProject = project;
final File vcs = new File(PathManager.getSystemPath(), "vcs");
File file = new File(vcs, "svn_copy_sources");
file.mkdirs();
myFile = file;
myFile = new File(file, project.getLocationHash());
}
public void activate() {
ValueHolder<WrapperInvertor, KeyData> cache = null;
myPersistentHolder = new PersistentHolder(myFile);
cache = new ValueHolder<WrapperInvertor, KeyData>() {
public WrapperInvertor getValue(KeyData dataHolder) {
final WrapperInvertor result =
myPersistentHolder.getBestHit(dataHolder.getRepoUrl(), dataHolder.getSourceUrl(), dataHolder.getTargetUrl());
if (LOG.isDebugEnabled()) {
LOG.debug("Persistent for: " + dataHolder.toString() + " returned: " + (result == null ? null : result.toString()));
}
return result;
}
public void setValue(WrapperInvertor value, KeyData dataHolder) {
if (LOG.isDebugEnabled()) {
LOG.debug("Put into persistent: key: " + dataHolder.toString() + " value: " + value.toString());
}
myPersistentHolder.put(dataHolder.getRepoUrl(), value.getWrapped().getTarget(), value.getWrapped());
}
};
myCalculator = new FactsCalculator<KeyData, WrapperInvertor, VcsException>(
myProject, "Looking for branch origin", cache, new Loader(myProject));
}
public void deactivate() {
myPersistentHolder.close();
myCalculator = null;
myPersistentHolder = null;
}
private static class BranchDataExternalizer implements DataExternalizer<TreeMap<String,BranchCopyData>> {
public void save(@NotNull DataOutput out, TreeMap<String,BranchCopyData> value) throws IOException {
out.writeInt(value.size());
for (Map.Entry<String, BranchCopyData> entry : value.entrySet()) {
out.writeUTF(entry.getKey());
final BranchCopyData entryValue = entry.getValue();
out.writeUTF(entryValue.getSource());
out.writeUTF(entryValue.getTarget());
out.writeLong(entryValue.getSourceRevision());
out.writeLong(entryValue.getTargetRevision());
}
}
public TreeMap<String,BranchCopyData> read(@NotNull DataInput in) throws IOException {
final TreeMap<String,BranchCopyData> result = new TreeMap<String, BranchCopyData>();
final int num = in.readInt();
for (int i = 0; i < num; i++) {
final String key = in.readUTF();
final String source = in.readUTF();
final String target = in.readUTF();
final long sourceRevision = in.readLong();
final long targetRevision = in.readLong();
result.put(key, new BranchCopyData(source, sourceRevision, target, targetRevision));
}
return result;
}
}
public static class WrapperInvertor {
private final BranchCopyData myWrapped;
private final boolean myInvertedSense;
public WrapperInvertor(boolean invertedSense, BranchCopyData wrapped) {
myInvertedSense = invertedSense;
myWrapped = wrapped;
}
public boolean isInvertedSense() {
return myInvertedSense;
}
public BranchCopyData getWrapped() {
return myWrapped;
}
public BranchCopyData getTrue() {
return myInvertedSense ? myWrapped.invertSelf() : myWrapped;
}
public BranchCopyData inverted() {
return myWrapped.invertSelf();
}
@Override
public String toString() {
return "inverted: " + myInvertedSense + " wrapped: " + myWrapped.toString();
}
}
private static class PersistentHolder {
private final SmallMapSerializer<String, TreeMap<String, BranchCopyData>> myPersistentMap;
private final MultiMap<String, String> myForSearchMap;
private final Object myLock;
PersistentHolder(final File file) {
myLock = new Object();
myPersistentMap = new SmallMapSerializer<String, TreeMap<String, BranchCopyData>>(
file, new EnumeratorStringDescriptor(), new BranchDataExternalizer());
// list for values by default
myForSearchMap = new MultiMap<String, String>();
for (String s : myPersistentMap.keySet()) {
final TreeMap<String, BranchCopyData> map = myPersistentMap.get(s);
if (map != null) {
myForSearchMap.put(s, new ArrayList<String>(map.keySet()));
}
}
for (String key : myForSearchMap.keySet()) {
Collections.sort((List<String>) myForSearchMap.get(key));
}
}
public void close() {
myPersistentMap.force();
}
public void put(final String uid, final String target, final BranchCopyData data) {
// todo - rewrite of rather big piece; consider rewriting
synchronized (myLock) {
TreeMap<String, BranchCopyData> map = myPersistentMap.get(uid);
if (map == null) {
map = new TreeMap<String, BranchCopyData>();
}
map.put(target, data);
myPersistentMap.put(uid, map);
if (myForSearchMap.containsKey(uid)) {
final List<String> list = (List<String>)myForSearchMap.get(uid);
final int idx = Collections.binarySearch(list, target);
if (idx < 0) {
final int insertionIdx = - idx - 1;
list.add(insertionIdx, target);
}
} else {
myForSearchMap.putValue(uid, target);
}
}
myPersistentMap.force();
}
@Nullable
public WrapperInvertor getBestHit(final String repoUrl, final String source, final String target) {
final List<String> keys;
synchronized (myLock) {
keys = (List<String>) myForSearchMap.get(repoUrl);
}
// keys are never removed, so we can use 2 synchronized blocks
final String sourceMatching = getMatchingUrl(keys, source);
final String targetMatching = getMatchingUrl(keys, target);
if (sourceMatching == null && targetMatching == null) return null;
synchronized (myLock) {
final TreeMap<String, BranchCopyData> map = myPersistentMap.get(repoUrl);
final boolean sourceIsOut = sourceMatching == null;
if (sourceIsOut || targetMatching == null) {
// if found by "target" url - we correctly thought that target of copy is target
return sourceIsOut ? new WrapperInvertor(false, map.get(targetMatching)) :
new WrapperInvertor(true, map.get(sourceMatching));
}
final BranchCopyData sourceData = map.get(sourceMatching);
final BranchCopyData targetData = map.get(targetMatching);
final boolean inverted = sourceData.getTargetRevision() > targetData.getTargetRevision();
return new WrapperInvertor(inverted, inverted ? sourceData : targetData);
}
}
@Nullable
private String getMatchingUrl(List<String> keys, String source) {
final int idx = Collections.binarySearch(keys, source);
if (idx >= 0) return keys.get(idx);
final int beforeInsertionIdx = - idx - 2;
if (beforeInsertionIdx < 0) return null;
final String candidate = keys.get(beforeInsertionIdx);
if (source.startsWith(candidate)) return candidate;
return null;
}
}
private static class Loader implements ThrowableConvertor<KeyData, WrapperInvertor, VcsException> {
private SvnVcs myVcs;
private Loader(final Project project) {
myVcs = SvnVcs.getInstance(project);
}
@Override
public WrapperInvertor convert(final KeyData keyData) throws VcsException {
final TransparentlyFailedValue<CopyData, VcsException> consumer = new TransparentlyFailedValue<CopyData, VcsException>();
new FirstInBranch(myVcs, keyData.getRepoUrl(), keyData.getTargetUrl(), keyData.getSourceUrl(), consumer).run();
final CopyData copyData = consumer.get();
if (copyData != null) {
final boolean correct = copyData.isTrunkSupposedCorrect();
final BranchCopyData branchCopyData;
if (correct) {
branchCopyData = new BranchCopyData(keyData.getSourceUrl(), copyData.getCopySourceRevision(), keyData.getTargetUrl(),
copyData.getCopyTargetRevision());
} else {
branchCopyData = new BranchCopyData(keyData.getTargetUrl(), copyData.getCopySourceRevision(), keyData.getSourceUrl(),
copyData.getCopyTargetRevision());
}
WrapperInvertor invertor = new WrapperInvertor(! correct, branchCopyData);
if (LOG.isDebugEnabled()) {
LOG.debug("Loader17 returned: for key: " + keyData.toString() + " result: " + (invertor.toString()));
}
return invertor;
}
if (LOG.isDebugEnabled()) {
LOG.debug("Loader17 returned: for key: " + keyData.toString() + " result: null");
}
return null;
}
}
private static class KeyData {
private final String myRepoUrl;
private final String mySourceUrl;
private final String myTargetUrl;
public KeyData(final String repoUID, final String sourceUrl, final String targetUrl) {
myRepoUrl = repoUID;
mySourceUrl = sourceUrl;
myTargetUrl = targetUrl;
}
public String getRepoUrl() {
return myRepoUrl;
}
public String getSourceUrl() {
return mySourceUrl;
}
public String getTargetUrl() {
return myTargetUrl;
}
@Override
public String toString() {
return "repoURL: " + myRepoUrl + " sourceUrl:" + mySourceUrl + " targetUrl: " + myTargetUrl;
}
}
public static class BranchCopyData {
private final String mySource;
private final String myTarget;
private final long mySourceRevision;
private final long myTargetRevision;
public BranchCopyData(String source, long sourceRevision, String target, long targetRevision) {
mySource = source;
mySourceRevision = sourceRevision;
myTarget = target;
myTargetRevision = targetRevision;
}
@Override
public String toString() {
return "source: " + mySource + "@" + mySourceRevision + " target: " + myTarget + "@" + myTargetRevision;
}
public String getSource() {
return mySource;
}
public long getSourceRevision() {
return mySourceRevision;
}
public String getTarget() {
return myTarget;
}
public long getTargetRevision() {
return myTargetRevision;
}
public BranchCopyData invertSelf() {
return new BranchCopyData(myTarget, myTargetRevision, mySource, mySourceRevision);
}
}
public TaskDescriptor getFirstCopyPointTask(final String repoUID, final String sourceUrl, final String targetUrl,
final Consumer<TransparentlyFailedValueI<WrapperInvertor, VcsException>> consumer) {
return myCalculator.getTask(new KeyData(repoUID, sourceUrl, targetUrl), consumer, VcsException.class);
}
}