vcs: Moved several classes to [svn4idea]
[idea/community.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / integrate / SvnBranchPointsCalculator.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.jetbrains.idea.svn.integrate;
17
18 import com.intellij.openapi.application.PathManager;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.vcs.VcsException;
21 import org.jetbrains.idea.svn.SmallMapSerializer;
22 import com.intellij.util.io.DataExternalizer;
23 import com.intellij.util.io.EnumeratorStringDescriptor;
24 import org.jetbrains.annotations.NotNull;
25 import org.jetbrains.annotations.Nullable;
26 import org.jetbrains.idea.svn.SvnVcs;
27 import org.jetbrains.idea.svn.history.CopyData;
28 import org.jetbrains.idea.svn.history.FirstInBranch;
29 import org.tmatesoft.svn.core.SVNURL;
30
31 import java.io.DataInput;
32 import java.io.DataOutput;
33 import java.io.File;
34 import java.io.IOException;
35 import java.util.Map;
36 import java.util.NavigableMap;
37 import java.util.TreeMap;
38
39 import static com.intellij.util.containers.ContainerUtil.newTreeMap;
40
41 public class SvnBranchPointsCalculator {
42
43   private static final Logger LOG = Logger.getInstance(SvnBranchPointsCalculator.class);
44
45   @NotNull private final SmallMapSerializer<String, TreeMap<String, BranchCopyData>> myPersistentMap;
46   @NotNull private final Object myPersistenceLock = new Object();
47   @NotNull private final SvnVcs myVcs;
48
49   public SvnBranchPointsCalculator(@NotNull SvnVcs vcs) {
50     myVcs = vcs;
51     File directory = new File(new File(PathManager.getSystemPath(), "vcs"), "svn_copy_sources");
52     directory.mkdirs();
53     File file = new File(directory, myVcs.getProject().getLocationHash());
54     myPersistentMap = new SmallMapSerializer<>(file, EnumeratorStringDescriptor.INSTANCE, new BranchDataExternalizer());
55   }
56
57   @Nullable
58   public WrapperInvertor getBestHit(@NotNull String repoUrl, @NotNull String sourceUrl, @NotNull String targetUrl) {
59     synchronized (myPersistenceLock) {
60       WrapperInvertor result = null;
61       TreeMap<String, BranchCopyData> map = myPersistentMap.get(repoUrl);
62
63       if (map != null) {
64         BranchCopyData sourceData = getBranchData(map, sourceUrl);
65         BranchCopyData targetData = getBranchData(map, targetUrl);
66
67         if (sourceData != null && targetData != null) {
68           boolean inverted = sourceData.getTargetRevision() > targetData.getTargetRevision();
69           result = new WrapperInvertor(inverted, inverted ? sourceData : targetData);
70         }
71         else if (sourceData != null) {
72           result = new WrapperInvertor(true, sourceData);
73         }
74         else if (targetData != null) {
75           result = new WrapperInvertor(false, targetData);
76         }
77       }
78
79       return result;
80     }
81   }
82
83   public void deactivate() {
84     synchronized (myPersistenceLock) {
85       myPersistentMap.force();
86     }
87   }
88
89   private void persist(@NotNull String repoUrl, @NotNull BranchCopyData data) {
90     // todo - rewrite of rather big piece; consider rewriting
91     synchronized (myPersistenceLock) {
92       TreeMap<String, BranchCopyData> map = myPersistentMap.get(repoUrl);
93       if (map == null) {
94         map = newTreeMap();
95       }
96       map.put(data.getTarget(), data);
97       myPersistentMap.put(repoUrl, map);
98       myPersistentMap.force();
99     }
100   }
101
102   @Nullable
103   private static BranchCopyData getBranchData(@NotNull NavigableMap<String, BranchCopyData> map, @NotNull String url) {
104     Map.Entry<String, BranchCopyData> branchData = map.floorEntry(url);
105     return branchData != null && url.startsWith(branchData.getKey()) ? branchData.getValue() : null;
106   }
107
108   private static class BranchDataExternalizer implements DataExternalizer<TreeMap<String, BranchCopyData>> {
109     public void save(@NotNull DataOutput out, @NotNull TreeMap<String, BranchCopyData> value) throws IOException {
110       out.writeInt(value.size());
111       for (Map.Entry<String, BranchCopyData> entry : value.entrySet()) {
112         out.writeUTF(entry.getKey());
113         save(out, entry.getValue());
114       }
115     }
116
117     private static void save(@NotNull DataOutput out, @NotNull BranchCopyData value) throws IOException {
118       out.writeUTF(value.getSource());
119       out.writeUTF(value.getTarget());
120       out.writeLong(value.getSourceRevision());
121       out.writeLong(value.getTargetRevision());
122     }
123
124     @NotNull
125     public TreeMap<String, BranchCopyData> read(@NotNull DataInput in) throws IOException {
126       TreeMap<String, BranchCopyData> result = newTreeMap();
127       int size = in.readInt();
128
129       for (int i = 0; i < size; i++) {
130         result.put(in.readUTF(), readCopyPoint(in));
131       }
132
133       return result;
134     }
135
136     @NotNull
137     private static BranchCopyData readCopyPoint(@NotNull DataInput in) throws IOException {
138       String sourceUrl = in.readUTF();
139       String targetUrl = in.readUTF();
140       long sourceRevision = in.readLong();
141       long targetRevision = in.readLong();
142
143       return new BranchCopyData(sourceUrl, sourceRevision, targetUrl, targetRevision);
144     }
145   }
146
147   public static class WrapperInvertor {
148     private final BranchCopyData myWrapped;
149     private final boolean myInvertedSense;
150
151     public WrapperInvertor(boolean invertedSense, BranchCopyData wrapped) {
152       myInvertedSense = invertedSense;
153       myWrapped = wrapped;
154     }
155
156     public boolean isInvertedSense() {
157       return myInvertedSense;
158     }
159
160     public BranchCopyData getWrapped() {
161       return myWrapped;
162     }
163
164     public BranchCopyData getTrue() {
165       return myInvertedSense ? myWrapped.invertSelf() : myWrapped;
166     }
167
168     public BranchCopyData inverted() {
169       return myWrapped.invertSelf();
170     }
171
172     @Override
173     public String toString() {
174       return "inverted: " + myInvertedSense + " wrapped: " + myWrapped.toString();
175     }
176   }
177
178   @Nullable
179   public WrapperInvertor calculateCopyPoint(@NotNull SVNURL repoUrl, @NotNull String sourceUrl, @NotNull String targetUrl)
180     throws VcsException {
181     WrapperInvertor result = getBestHit(repoUrl.toDecodedString(), sourceUrl, targetUrl);
182
183     if (result == null) {
184       CopyData copyData = new FirstInBranch(myVcs, repoUrl, targetUrl, sourceUrl).run();
185
186       if (copyData != null) {
187         BranchCopyData branchCopyData =
188           copyData.isTrunkSupposedCorrect()
189           ? new BranchCopyData(sourceUrl, copyData.getCopySourceRevision(), targetUrl, copyData.getCopyTargetRevision())
190           : new BranchCopyData(targetUrl, copyData.getCopySourceRevision(), sourceUrl, copyData.getCopyTargetRevision());
191
192         persist(repoUrl.toDecodedString(), branchCopyData);
193         result = new WrapperInvertor(!copyData.isTrunkSupposedCorrect(), branchCopyData);
194       }
195     }
196
197     logCopyData(repoUrl.toDecodedString(), sourceUrl, targetUrl, result);
198
199     return result;
200   }
201
202   private static void logCopyData(@NotNull String repoUrl,
203                                   @NotNull String sourceUrl,
204                                   @NotNull String targetUrl,
205                                   @Nullable WrapperInvertor inverter) {
206     if (LOG.isDebugEnabled()) {
207       LOG.debug("repoURL: " + repoUrl + ", sourceUrl:" + sourceUrl + ", targetUrl: " + targetUrl + ", inverter: " + inverter);
208     }
209   }
210
211   public static class BranchCopyData {
212     private final String mySource;
213     private final String myTarget;
214     private final long mySourceRevision;
215     private final long myTargetRevision;
216
217     public BranchCopyData(String source, long sourceRevision, String target, long targetRevision) {
218       mySource = source;
219       mySourceRevision = sourceRevision;
220       myTarget = target;
221       myTargetRevision = targetRevision;
222     }
223
224     @Override
225     public String toString() {
226       return "source: " + mySource + "@" + mySourceRevision + " target: " + myTarget + "@" + myTargetRevision;
227     }
228
229     public String getSource() {
230       return mySource;
231     }
232
233     public long getSourceRevision() {
234       return mySourceRevision;
235     }
236
237     public String getTarget() {
238       return myTarget;
239     }
240
241     public long getTargetRevision() {
242       return myTargetRevision;
243     }
244
245     public BranchCopyData invertSelf() {
246       return new BranchCopyData(myTarget, myTargetRevision, mySource, mySourceRevision);
247     }
248   }
249 }