svn: Refactoring - moved "ShowPropertiesDiffAction" logic to base class
[idea/community.git] / plugins / svn4idea / src / org / jetbrains / idea / svn / actions / AbstractShowPropertiesDiffAction.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.actions;
17
18 import com.intellij.diff.DiffManager;
19 import com.intellij.openapi.actionSystem.AnAction;
20 import com.intellij.openapi.actionSystem.AnActionEvent;
21 import com.intellij.openapi.progress.PerformInBackgroundOption;
22 import com.intellij.openapi.progress.ProgressIndicator;
23 import com.intellij.openapi.progress.ProgressManager;
24 import com.intellij.openapi.progress.Task;
25 import com.intellij.openapi.project.DumbAware;
26 import com.intellij.openapi.project.Project;
27 import com.intellij.openapi.ui.Messages;
28 import com.intellij.openapi.util.text.StringUtil;
29 import com.intellij.openapi.vcs.FilePath;
30 import com.intellij.openapi.vcs.VcsDataKeys;
31 import com.intellij.openapi.vcs.VcsException;
32 import com.intellij.openapi.vcs.changes.*;
33 import org.jetbrains.annotations.NotNull;
34 import org.jetbrains.annotations.Nullable;
35 import org.jetbrains.idea.svn.SvnBundle;
36 import org.jetbrains.idea.svn.SvnRevisionNumber;
37 import org.jetbrains.idea.svn.SvnVcs;
38 import org.jetbrains.idea.svn.api.Depth;
39 import org.jetbrains.idea.svn.difftool.properties.SvnPropertiesDiffRequest;
40 import org.jetbrains.idea.svn.difftool.properties.SvnPropertiesDiffRequest.PropertyContent;
41 import org.jetbrains.idea.svn.history.SvnRepositoryContentRevision;
42 import org.jetbrains.idea.svn.properties.PropertyConsumer;
43 import org.jetbrains.idea.svn.properties.PropertyData;
44 import org.jetbrains.idea.svn.properties.PropertyValue;
45 import org.tmatesoft.svn.core.SVNErrorCode;
46 import org.tmatesoft.svn.core.SVNErrorMessage;
47 import org.tmatesoft.svn.core.SVNException;
48 import org.tmatesoft.svn.core.SVNURL;
49 import org.tmatesoft.svn.core.wc.SVNRevision;
50 import org.tmatesoft.svn.core.wc2.SvnTarget;
51
52 import java.io.File;
53 import java.util.ArrayList;
54 import java.util.Collections;
55 import java.util.Comparator;
56 import java.util.List;
57
58 import static com.intellij.util.ObjectUtils.notNull;
59 import static com.intellij.util.containers.ContainerUtil.exists;
60
61 public abstract class AbstractShowPropertiesDiffAction extends AnAction implements DumbAware {
62   protected AbstractShowPropertiesDiffAction(String name) {
63     super(name);
64   }
65
66   @Override
67   public void update(@NotNull AnActionEvent e) {
68     Change[] changes = e.getData(VcsDataKeys.CHANGES);
69     boolean showAction = checkThatChangesAreUnderSvn(changes);
70
71     e.getPresentation().setVisible(changes != null && showAction);
72     e.getPresentation().setEnabled(showAction);
73   }
74
75   private static boolean checkThatChangesAreUnderSvn(@Nullable Change[] changes) {
76     return changes != null && exists(changes, change -> isUnderSvn(change.getBeforeRevision()) || isUnderSvn(change.getAfterRevision()));
77   }
78
79   private static boolean isUnderSvn(@Nullable ContentRevision revision) {
80     return revision instanceof MarkerVcsContentRevision && SvnVcs.getKey().equals(((MarkerVcsContentRevision)revision).getVcsKey());
81   }
82
83   @Override
84   public void actionPerformed(@NotNull AnActionEvent e) {
85     Change[] changes = e.getData(VcsDataKeys.CHANGE_LEAD_SELECTION);
86
87     if (checkThatChangesAreUnderSvn(changes)) {
88       new CalculateAndShow(e.getProject(), changes[0], e.getPresentation().getText()).queue();
89     }
90   }
91
92   private static class CalculateAndShow extends Task.Backgroundable {
93     private final Change myChange;
94     private List<PropertyData> myBeforeContent;
95     private List<PropertyData> myAfterContent;
96     private SVNRevision myBeforeRevisionValue;
97     private SVNRevision myAfterRevision;
98     private Exception myException;
99     private final String myErrorTitle;
100
101     private CalculateAndShow(@Nullable final Project project, final Change change, final String errorTitle) {
102       super(project, SvnBundle.message("fetching.properties.contents.progress.title"), true, PerformInBackgroundOption.DEAF);
103       myChange = change;
104       myErrorTitle = errorTitle;
105     }
106
107     public void run(@NotNull final ProgressIndicator indicator) {
108       final SvnVcs vcs = SvnVcs.getInstance(myProject);
109
110       try {
111         myBeforeRevisionValue = getBeforeRevisionValue(myChange);
112         myAfterRevision = getAfterRevisionValue(myChange);
113
114         myBeforeContent = getPropertyList(vcs, myChange.getBeforeRevision(), myBeforeRevisionValue);
115         indicator.checkCanceled();
116         // gets exactly WORKING revision property
117         myAfterContent = getPropertyList(vcs, myChange.getAfterRevision(), myAfterRevision);
118       }
119       catch (SVNException exc) {
120         myException = exc;
121       }
122       catch (VcsException exc) {
123         myException = exc;
124       }
125     }
126
127     @Override
128     public void onSuccess() {
129       if (myException != null) {
130         Messages.showErrorDialog(myException.getMessage(), myErrorTitle);
131         return;
132       }
133       if (myBeforeContent != null && myAfterContent != null && myBeforeRevisionValue != null && myAfterRevision != null) {
134         SvnPropertiesDiffRequest diffRequest;
135         if (compareRevisions(myBeforeRevisionValue, myAfterRevision) > 0) {
136           diffRequest = new SvnPropertiesDiffRequest(getDiffWindowTitle(myChange),
137                                                      new PropertyContent(myAfterContent), new PropertyContent(myBeforeContent),
138                                                      revisionToString(myAfterRevision), revisionToString(myBeforeRevisionValue));
139         }
140         else {
141           diffRequest = new SvnPropertiesDiffRequest(getDiffWindowTitle(myChange),
142                                                      new PropertyContent(myBeforeContent), new PropertyContent(myAfterContent),
143                                                      revisionToString(myBeforeRevisionValue), revisionToString(myAfterRevision));
144         }
145         DiffManager.getInstance().showDiff(myProject, diffRequest);
146       }
147     }
148   }
149
150   @NotNull
151   private static SVNRevision getBeforeRevisionValue(@NotNull Change change) throws SVNException {
152     ContentRevision beforeRevision = change.getBeforeRevision();
153     if (beforeRevision != null) {
154       return ((SvnRevisionNumber)beforeRevision.getRevisionNumber()).getRevision();
155     }
156     else {
157       return SVNRevision.create(((SvnRevisionNumber)notNull(change.getAfterRevision()).getRevisionNumber()).getRevision().getNumber() - 1);
158     }
159   }
160
161   @NotNull
162   private static SVNRevision getAfterRevisionValue(@NotNull Change change) throws SVNException {
163     ContentRevision afterRevision = change.getAfterRevision();
164     if (afterRevision != null) {
165       // CurrentContentRevision will be here, for instance, if invoked from changes dialog for "Compare with Branch" action
166       return afterRevision instanceof CurrentContentRevision
167              ? SVNRevision.WORKING
168              : ((SvnRevisionNumber)afterRevision.getRevisionNumber()).getRevision();
169     }
170     else {
171       return SVNRevision.create(((SvnRevisionNumber)notNull(change.getBeforeRevision()).getRevisionNumber()).getRevision().getNumber() + 1);
172     }
173   }
174
175   @NotNull
176   private static String getDiffWindowTitle(@NotNull Change change) {
177     if (change.isMoved() || change.isRenamed()) {
178       final FilePath beforeFilePath = ChangesUtil.getBeforePath(change);
179       final FilePath afterFilePath = ChangesUtil.getAfterPath(change);
180
181       final String beforePath = beforeFilePath == null ? "" : beforeFilePath.getPath();
182       final String afterPath = afterFilePath == null ? "" : afterFilePath.getPath();
183       return SvnBundle.message("action.Subversion.properties.difference.diff.for.move.title", beforePath, afterPath);
184     } else {
185       return SvnBundle.message("action.Subversion.properties.difference.diff.title", ChangesUtil.getFilePath(change).getPath());
186     }
187   }
188
189   private static int compareRevisions(@NotNull SVNRevision revision1, @NotNull SVNRevision revision2) {
190     if (revision1.equals(revision2)) {
191       return 0;
192     }
193     // working(local) ahead of head
194     if (SVNRevision.WORKING.equals(revision1)) {
195       return 1;
196     }
197     if (SVNRevision.WORKING.equals(revision2)) {
198       return -1;
199     }
200     if (SVNRevision.HEAD.equals(revision1)) {
201       return 1;
202     }
203     if (SVNRevision.HEAD.equals(revision2)) {
204       return -1;
205     }
206     return revision1.getNumber() > revision2.getNumber() ? 1 : -1;
207   }
208
209   @NotNull
210   private static String revisionToString(@Nullable SVNRevision revision) {
211     return revision == null ? "not exists" : revision.toString();
212   }
213
214   private final static String ourPropertiesDelimiter = "\n";
215
216   @NotNull
217   private static List<PropertyData> getPropertyList(@NotNull SvnVcs vcs,
218                                                     @Nullable final ContentRevision contentRevision,
219                                                     @Nullable final SVNRevision revision)
220   throws SVNException, VcsException {
221     if (contentRevision == null) {
222       return Collections.emptyList();
223     }
224
225     SvnTarget target;
226     if (contentRevision instanceof SvnRepositoryContentRevision) {
227       final SvnRepositoryContentRevision svnRevision = (SvnRepositoryContentRevision)contentRevision;
228       target = SvnTarget.fromURL(SVNURL.parseURIEncoded(svnRevision.getFullPath()), revision);
229     } else {
230       final File ioFile = contentRevision.getFile().getIOFile();
231       target = SvnTarget.fromFile(ioFile, revision);
232     }
233
234     return getPropertyList(vcs, target, revision);
235   }
236
237   @NotNull
238   public static List<PropertyData> getPropertyList(@NotNull SvnVcs vcs, @NotNull final SVNURL url, @Nullable final SVNRevision revision)
239     throws VcsException {
240     return getPropertyList(vcs, SvnTarget.fromURL(url, revision), revision);
241   }
242
243   @NotNull
244   public static List<PropertyData> getPropertyList(@NotNull SvnVcs vcs, @NotNull final File ioFile, @Nullable final SVNRevision revision)
245     throws SVNException {
246     try {
247       return getPropertyList(vcs, SvnTarget.fromFile(ioFile, revision), revision);
248     }
249     catch (VcsException e) {
250       throw new SVNException(SVNErrorMessage.create(SVNErrorCode.FS_GENERAL, e), e);
251     }
252   }
253
254   @NotNull
255   private static List<PropertyData> getPropertyList(@NotNull SvnVcs vcs, @NotNull SvnTarget target, @Nullable SVNRevision revision)
256     throws VcsException {
257     final List<PropertyData> lines = new ArrayList<>();
258     final PropertyConsumer propertyHandler = createHandler(revision, lines);
259
260     vcs.getFactory(target).createPropertyClient().list(target, revision, Depth.EMPTY, propertyHandler);
261
262     return lines;
263   }
264
265   @NotNull
266   private static PropertyConsumer createHandler(SVNRevision revision, @NotNull final List<PropertyData> lines) {
267     final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
268     if (indicator != null) {
269       indicator.checkCanceled();
270       indicator.setText(SvnBundle.message("show.properties.diff.progress.text.revision.information", revision.toString()));
271     }
272
273     return new PropertyConsumer() {
274       public void handleProperty(final File path, final PropertyData property) throws SVNException {
275         registerProperty(property);
276       }
277
278       public void handleProperty(final SVNURL url, final PropertyData property) throws SVNException {
279         registerProperty(property);
280       }
281
282       public void handleProperty(final long revision, final PropertyData property) throws SVNException {
283         // revision properties here
284       }
285
286       private void registerProperty(@NotNull PropertyData property) {
287         if (indicator != null) {
288           indicator.checkCanceled();
289           indicator.setText2(SvnBundle.message("show.properties.diff.progress.text2.property.information", property.getName()));
290         }
291         lines.add(property);
292       }
293     };
294   }
295
296   @NotNull
297   public static String toSortedStringPresentation(@NotNull List<PropertyData> lines) {
298     StringBuilder sb = new StringBuilder();
299
300     Collections.sort(lines, Comparator.comparing(PropertyData::getName));
301
302     for (PropertyData line : lines) {
303       addPropertyPresentation(line, sb);
304     }
305
306     return sb.toString();
307   }
308
309   private static void addPropertyPresentation(@NotNull PropertyData property, @NotNull StringBuilder sb) {
310     if (sb.length() != 0) {
311       sb.append(ourPropertiesDelimiter);
312     }
313     sb.append(property.getName()).append("=").append(StringUtil.notNullize(PropertyValue.toString(property.getValue())));
314   }
315 }