EA-60956 - assert: DocumentImpl.doRemoveDocumentListener
[idea/community.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / branchConfig / SvnBranchConfigurationNew.java
1 /*
2  * Copyright 2000-2009 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.branchConfig;
17
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.project.Project;
20 import com.intellij.openapi.vcs.ObjectsConvertor;
21 import com.intellij.openapi.vfs.VirtualFile;
22 import com.intellij.util.containers.Convertor;
23 import org.jetbrains.annotations.Nullable;
24 import org.jetbrains.idea.svn.SvnUtil;
25 import org.jetbrains.idea.svn.SvnVcs;
26 import org.jetbrains.idea.svn.info.Info;
27 import org.tmatesoft.svn.core.SVNException;
28 import org.tmatesoft.svn.core.SVNURL;
29 import org.tmatesoft.svn.core.internal.util.SVNPathUtil;
30 import org.tmatesoft.svn.core.internal.util.SVNURLUtil;
31
32 import java.io.File;
33 import java.util.*;
34
35 public class SvnBranchConfigurationNew {
36   private static final Logger LOG = Logger.getInstance("#org.jetbrains.idea.svn.branchConfig.SvnBranchConfigurationNew");
37   private String myTrunkUrl;
38   // need public for serialization
39   public Map<String, InfoStorage<List<SvnBranchItem>>> myBranchMap;
40   private boolean myUserinfoInUrl;
41
42   public SvnBranchConfigurationNew() {
43     myTrunkUrl = "";
44     myBranchMap = new HashMap<String, InfoStorage<List<SvnBranchItem>>>();
45   }
46
47   public boolean isUserinfoInUrl() {
48     return myUserinfoInUrl;
49   }
50
51   public void setUserinfoInUrl(final boolean userinfoInUrl) {
52     myUserinfoInUrl = userinfoInUrl;
53   }
54
55   public void setTrunkUrl(final String trunkUrl) {
56     myTrunkUrl = trunkUrl;
57   }
58
59   public String getTrunkUrl() {
60     return myTrunkUrl;
61   }
62
63   public List<String> getBranchUrls() {
64     final ArrayList<String> result = new ArrayList<String>(myBranchMap.keySet());
65     final List<String> cutList = ObjectsConvertor.convert(result, new Convertor<String, String>() {
66       @Override
67       public String convert(String s) {
68         return cutEndSlash(s);
69       }
70     });
71     Collections.sort(cutList);
72     return cutList;
73   }
74
75   public void addBranches(String branchParentName, final InfoStorage<List<SvnBranchItem>> items) {
76     branchParentName = ensureEndSlash(branchParentName);
77     InfoStorage<List<SvnBranchItem>> current = myBranchMap.get(branchParentName);
78     if (current != null) {
79       LOG.info("Branches list not added for : '" + branchParentName + "; this branch parent URL is already present.");
80       return;
81     }
82     myBranchMap.put(branchParentName, items);
83   }
84
85   public static String ensureEndSlash(String name) {
86     return name.trim().endsWith("/") ? name : name + "/";
87   }
88   
89   private static String cutEndSlash(String name) {
90     return name.endsWith("/") && name.length() > 0 ? name.substring(0, name.length() - 1) : name;
91   }
92
93   public void updateBranch(String branchParentName, final InfoStorage<List<SvnBranchItem>> items) {
94     branchParentName = ensureEndSlash(branchParentName);
95     final InfoStorage<List<SvnBranchItem>> current = myBranchMap.get(branchParentName);
96     if (current == null) {
97       LOG.info("Branches list not updated for : '" + branchParentName + "; since config has changed.");
98       return;
99     }
100     current.accept(items);
101   }
102
103   public Map<String, InfoStorage<List<SvnBranchItem>>> getBranchMap() {
104     return myBranchMap;
105   }
106
107   public List<SvnBranchItem> getBranches(String url) {
108     url = ensureEndSlash(url);
109     return myBranchMap.get(url).getValue();
110   }
111
112   public SvnBranchConfigurationNew copy() {
113     SvnBranchConfigurationNew result = new SvnBranchConfigurationNew();
114     result.myUserinfoInUrl = myUserinfoInUrl;
115     result.myTrunkUrl = myTrunkUrl;
116     result.myBranchMap = new HashMap<String, InfoStorage<List<SvnBranchItem>>>();
117     for (Map.Entry<String, InfoStorage<List<SvnBranchItem>>> entry : myBranchMap.entrySet()) {
118       final InfoStorage<List<SvnBranchItem>> infoStorage = entry.getValue();
119       result.myBranchMap.put(entry.getKey(), new InfoStorage<List<SvnBranchItem>>(
120         new ArrayList<SvnBranchItem>(infoStorage.getValue()), infoStorage.getInfoReliability()));
121     }
122     return result;
123   }
124
125   @Nullable
126   public String getBaseUrl(String url) {
127     if (myTrunkUrl != null) {
128       if (SVNPathUtil.isAncestor(myTrunkUrl, url)) {
129         return cutEndSlash(myTrunkUrl);
130       }
131     }
132     for(String branchUrl: myBranchMap.keySet()) {
133       if (SVNPathUtil.isAncestor(branchUrl, url)) {
134         String relativePath = SVNPathUtil.getRelativePath(branchUrl, url);
135         int secondSlash = relativePath.indexOf("/");
136         return cutEndSlash(branchUrl + (secondSlash == -1 ? relativePath : relativePath.substring(0, secondSlash)));
137       }
138     }
139     return null;
140   }
141
142   @Nullable
143   public String getBaseName(String url) {
144     String baseUrl = getBaseUrl(url);
145     if (baseUrl == null) {
146       return null;
147     }
148     int lastSlash = baseUrl.lastIndexOf("/");
149     return lastSlash == -1 ? baseUrl : baseUrl.substring(lastSlash + 1);
150   }
151
152   @Nullable
153   public String getRelativeUrl(String url) {
154     String baseUrl = getBaseUrl(url);
155     return baseUrl == null ? null : url.substring(baseUrl.length());
156   }
157
158   @Nullable
159   public SVNURL getWorkingBranch(final SVNURL someUrl) throws SVNException {
160     String baseUrl = getBaseUrl(someUrl.toString());
161     return baseUrl == null ? null : SVNURL.parseURIEncoded(baseUrl);
162   }
163
164   // todo not checked
165   // todo +-
166   @Nullable
167   public String getGroupToLoadToReachUrl(final SVNURL url) throws SVNException {
168     final BranchSearcher branchSearcher = new BranchSearcher(url);
169     for (String group : myBranchMap.keySet()) {
170       if (branchSearcher.accept(group)) {
171         return group;
172       }
173     }
174     return null;
175   }
176
177   private void iterateUrls(final UrlListener listener) throws SVNException {
178     if (listener.accept(myTrunkUrl)) {
179       return;
180     }
181
182     for (String branchUrl : myBranchMap.keySet()) {
183       // use more exact comparison first (paths longer)
184       final List<SvnBranchItem> children = myBranchMap.get(branchUrl).getValue();
185       for (SvnBranchItem child : children) {
186         if (listener.accept(child.getUrl())) {
187           return;
188         }
189       }
190
191       /*if (listener.accept(branchUrl)) {
192         return;
193       }*/
194     }
195   }
196
197   // to retrieve mappings between existing in the project working copies and their URLs
198   @Nullable
199   public Map<String,String> getUrl2FileMappings(final Project project, final VirtualFile root) {
200     try {
201       final BranchRootSearcher searcher = new BranchRootSearcher(SvnVcs.getInstance(project), root);
202       iterateUrls(searcher);
203       return searcher.getBranchesUnder();
204     } catch (SVNException e) {
205       return null;
206     }
207   }
208
209   public void removeBranch(String url) {
210     url = ensureEndSlash(url);
211     myBranchMap.remove(url);
212   }
213
214   private static class BranchRootSearcher implements UrlListener {
215     private final VirtualFile myRoot;
216     private final SVNURL myRootUrl;
217     // url path to file path
218     private final Map<String, String> myBranchesUnder;
219
220     private BranchRootSearcher(final SvnVcs vcs, final VirtualFile root) throws SVNException {
221       myRoot = root;
222       myBranchesUnder = new HashMap<String, String>();
223       final Info info = vcs.getInfo(myRoot.getPath());
224       myRootUrl = info != null ? info.getURL() : null;
225     }
226
227     public boolean accept(final String url) throws SVNException {
228       if (myRootUrl != null) {
229         final File baseDir = new File(myRoot.getPath());
230         final String baseUrl = myRootUrl.getPath();
231
232         final SVNURL branchUrl = SVNURL.parseURIEncoded(url);
233         if (myRootUrl.equals(SVNURLUtil.getCommonURLAncestor(myRootUrl, branchUrl))) {
234           final File file = SvnUtil.fileFromUrl(baseDir, baseUrl, branchUrl.getPath());
235           myBranchesUnder.put(url, file.getAbsolutePath());
236         }
237       }
238       return false; // iterate everything
239     }
240
241     public Map<String, String> getBranchesUnder() {
242       return myBranchesUnder;
243     }
244   }
245
246   private interface UrlListener {
247     boolean accept(final String url) throws SVNException;
248   }
249
250   // todo not checked
251   private static class BranchSearcher implements UrlListener {
252     private final SVNURL mySomeUrl;
253     private SVNURL myResult;
254
255     private BranchSearcher(final SVNURL someUrl) {
256       mySomeUrl = someUrl;
257     }
258
259     public boolean accept(final String url) throws SVNException {
260       myResult = urlIsParent(url, mySomeUrl);
261       return myResult != null;
262     }
263
264     public SVNURL getResult() {
265       return myResult;
266     }
267   }
268
269   @Nullable
270   private static SVNURL urlIsParent(final String parentCandidate, final SVNURL child) throws SVNException {
271     final SVNURL parentUrl = SVNURL.parseURIEncoded(parentCandidate);
272     if(parentUrl.equals(SVNURLUtil.getCommonURLAncestor(parentUrl, child))) {
273       return parentUrl;
274     }
275     return null;
276   }
277 }