853f8bf1d28c6e6f94e7a3cc3f4bf53e7e0b8914
[idea/community.git] / plugins / svn4idea / src / org / jetbrains / idea / svn17 / commandLine / SvnStatusHandler.java
1 /*
2  * Copyright 2000-2012 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.svn17.commandLine;
17
18 import com.intellij.openapi.util.Getter;
19 import com.intellij.openapi.util.text.StringUtil;
20 import com.intellij.util.containers.Convertor;
21 import com.intellij.util.containers.MultiMap;
22 import org.jetbrains.idea.svn17.portable.PortableStatus;
23 import org.jetbrains.idea.svn17.portable.StatusCallbackConvertor;
24 import org.tmatesoft.svn.core.SVNNodeKind;
25 import org.tmatesoft.svn.core.internal.util.SVNDate;
26 import org.tmatesoft.svn.core.wc.SVNInfo;
27 import org.tmatesoft.svn.core.wc.SVNRevision;
28 import org.tmatesoft.svn.core.wc.SVNStatusType;
29 import org.xml.sax.Attributes;
30 import org.xml.sax.SAXException;
31 import org.xml.sax.helpers.DefaultHandler;
32
33 import java.io.File;
34 import java.util.*;
35
36 /**
37  * Created with IntelliJ IDEA.
38  * User: Irina.Chernushina
39  * Date: 1/25/12
40  * Time: 7:59 PM
41  */
42 public class SvnStatusHandler extends DefaultHandler {
43   private String myChangelistName;
44   private List<PortableStatus> myDefaultListStatuses;
45   private MultiMap<String, PortableStatus> myCurrentListChanges;
46   private PortableStatus myPending;
47
48   private final List<ElementHandlerBase> myParseStack;
49   private final Map<String, Getter<ElementHandlerBase>> myElementsMap;
50   private final DataCallback myDataCallback;
51   private final File myBase;
52   private final StringBuilder mySb;
53
54   public SvnStatusHandler(final DataCallback dataCallback, File base, final Convertor<File, SVNInfo> infoGetter) {
55     myBase = base;
56     myParseStack = new ArrayList<ElementHandlerBase>();
57     myParseStack.add(new Fake());
58
59     myElementsMap = new HashMap<String, Getter<ElementHandlerBase>>();
60     fillElements();
61
62     if (dataCallback != null) {
63       myDataCallback = new DataCallback() {
64         @Override
65         public void switchPath() {
66           dataCallback.switchPath();
67           newPending(infoGetter);
68         }
69
70         @Override
71         public void switchChangeList(String newList) {
72           dataCallback.switchChangeList(newList);
73         }
74       };
75     } else {
76       myDataCallback = new DataCallback() {
77         @Override
78         public void switchPath() {
79           if (myChangelistName == null) {
80             myDefaultListStatuses.add(myPending);
81           } else {
82             myCurrentListChanges.putValue(myChangelistName, myPending);
83           }
84           newPending(infoGetter);
85         }
86
87         @Override
88         public void switchChangeList(String newList) {
89           myChangelistName = newList;
90         }
91       };
92     }
93     newPending(infoGetter);
94     mySb = new StringBuilder();
95   }
96
97   private void newPending(final Convertor<File, SVNInfo> infoGetter) {
98     myPending = new PortableStatus();
99     myPending.setInfoGetter(new Getter<SVNInfo>() {
100       @Override
101       public SVNInfo get() {
102         return infoGetter.convert(myPending.getFile());
103       }
104     });
105   }
106
107   public PortableStatus getPending() {
108     return myPending;
109   }
110
111   public List<PortableStatus> getDefaultListStatuses() {
112     return myDefaultListStatuses;
113   }
114
115   public MultiMap<String, PortableStatus> getCurrentListChanges() {
116     return myCurrentListChanges;
117   }
118
119   private void fillElements() {
120     myElementsMap.put("status", new Getter<ElementHandlerBase>() {
121       @Override
122       public ElementHandlerBase get() {
123         return new Status();
124       }
125     });
126     myElementsMap.put("author", new Getter<ElementHandlerBase>() {
127       @Override
128       public ElementHandlerBase get() {
129         return new Author();
130       }
131     });
132     myElementsMap.put("changelist", new Getter<ElementHandlerBase>() {
133       @Override
134       public ElementHandlerBase get() {
135         return new Changelist();
136       }
137     });
138     myElementsMap.put("commit", new Getter<ElementHandlerBase>() {
139       @Override
140       public ElementHandlerBase get() {
141         return new Commit();
142       }
143     });
144     myElementsMap.put("date", new Getter<ElementHandlerBase>() {
145       @Override
146       public ElementHandlerBase get() {
147         return new Date();
148       }
149     });
150     myElementsMap.put("entry", new Getter<ElementHandlerBase>() {
151       @Override
152       public ElementHandlerBase get() {
153         return new Entry(myBase);
154       }
155     });
156     myElementsMap.put("target", new Getter<ElementHandlerBase>() {
157       @Override
158       public ElementHandlerBase get() {
159         return new Target();
160       }
161     });
162     myElementsMap.put("wc-status", new Getter<ElementHandlerBase>() {
163       @Override
164       public ElementHandlerBase get() {
165         return new WcStatus();
166       }
167     });
168   }
169
170   @Override
171   public void endElement(String uri, String localName, String qName) throws SAXException {
172     //
173   }
174
175   @Override
176   public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
177     assertSAX(! myParseStack.isEmpty());
178     ElementHandlerBase current = myParseStack.get(myParseStack.size() - 1);
179     if (mySb.length() > 0) {
180       current.characters(mySb.toString().trim(), myPending);
181       mySb.setLength(0);
182     }
183
184     while (true) {
185       final boolean createNewChild = current.startElement(uri, localName, qName, attributes);
186       if (createNewChild) {
187         assertSAX(myElementsMap.containsKey(qName));
188         final ElementHandlerBase newChild = myElementsMap.get(qName).get();
189         newChild.updateStatus(attributes, myPending);
190         myParseStack.add(newChild);
191         return;
192       } else {
193         // go up
194         current.postEffect(myDataCallback);
195         myParseStack.remove(myParseStack.size() - 1);
196         assertSAX(! myParseStack.isEmpty());
197         current = myParseStack.get(myParseStack.size() - 1);
198       }
199     }
200   }
201
202   @Override
203   public void characters(char[] ch, int start, int length) throws SAXException {
204     assertSAX(! myParseStack.isEmpty());
205     mySb.append(ch, start, length);
206   }
207
208   @Override
209   public void endDocument() throws SAXException {
210     assertSAX(! myParseStack.isEmpty());
211     for (int i = myParseStack.size() - 1; i >= 0; -- i) {
212       ElementHandlerBase current = myParseStack.get(i);
213       current.postEffect(myDataCallback);
214     }
215     myParseStack.clear();
216   }
217
218   private static void assertSAX(final boolean shouldBeTrue) throws SAXException {
219     if (! shouldBeTrue) {
220       throw new SAXException("can not parse output");
221     }
222   }
223
224   private static class Fake extends ElementHandlerBase {
225     private Fake() {
226       super(new String[]{"status"}, new String[]{});
227     }
228
229     @Override
230     protected void updateStatus(Attributes attributes, PortableStatus status) throws SAXException {
231     }
232
233     @Override
234     public void postEffect(DataCallback callback) {
235     }
236
237     @Override
238     public void preEffect(DataCallback callback) {
239     }
240
241     @Override
242     public void characters(String s, PortableStatus pending) {
243     }
244   }
245
246   private static class Date extends ElementHandlerBase {
247     private Date() {
248       super(new String[]{}, new String[]{});
249     }
250
251     @Override
252     protected void updateStatus(Attributes attributes, PortableStatus status) throws SAXException {
253     }
254
255     @Override
256     public void postEffect(DataCallback callback) {
257     }
258
259     @Override
260     public void preEffect(DataCallback callback) {
261     }
262
263     @Override
264     public void characters(String s, PortableStatus pending) {
265       final SVNDate date = SVNDate.parseDate(s);
266       //if (SVNDate.NULL.equals(date)) return;
267       pending.setRemoteDate(date);
268     }
269   }
270
271   private static class Author extends ElementHandlerBase {
272     private Author() {
273       super(new String[]{}, new String[]{});
274     }
275
276     @Override
277     protected void updateStatus(Attributes attributes, PortableStatus status) throws SAXException {
278     }
279
280     @Override
281     public void postEffect(DataCallback callback) {
282     }
283
284     @Override
285     public void preEffect(DataCallback callback) {
286     }
287
288     @Override
289     public void characters(String s, PortableStatus pending) {
290       pending.setRemoteAuthor(s);
291     }
292   }
293
294   /*        <commit
295               revision="25">
296             <author>admin</author>
297             <date>2011-11-09T12:21:02.401530Z</date>
298   */
299   private static class Commit extends ElementHandlerBase {
300     private Commit() {
301       super(new String[]{"author", "date"}, new String[]{});
302     }
303
304     @Override
305     protected void updateStatus(Attributes attributes, PortableStatus status) throws SAXException {
306       final String revision = attributes.getValue("revision");
307       if (! StringUtil.isEmptyOrSpaces(revision)) {
308         try {
309           final long number = Long.parseLong(revision);
310           status.setRemoteRevision(SVNRevision.create(number));
311         } catch (NumberFormatException e) {
312           throw new SAXException(e);
313         }
314       }
315     }
316
317     @Override
318     public void postEffect(DataCallback callback) {
319     }
320
321     @Override
322     public void preEffect(DataCallback callback) {
323     }
324
325     @Override
326     public void characters(String s, PortableStatus pending) {
327     }
328   }
329
330   /*      <wc-status   props="none"   copied="true"   tree-conflicted="true"   item="added">
331       <wc-status       props="none"   item="unversioned">
332       <wc-status       props="none"   item="added"   revision="-1">
333       <wc-status       props="none"   item="modified"   revision="112">
334       <wc-status       props="conflicted"  item="normal"  revision="112">
335   */
336   private static class WcStatus extends ElementHandlerBase {
337     private WcStatus() {
338       super(new String[]{"commit"}, new String[]{});
339     }
340
341     @Override
342     protected void updateStatus(Attributes attributes, PortableStatus status) throws SAXException {
343       final String props = attributes.getValue("props");
344       assertSAX(props != null);
345       final SVNStatusType propertiesStatus = StatusCallbackConvertor.convert(org.apache.subversion.javahl.types.Status.Kind.valueOf(props));
346       status.setPropertiesStatus(propertiesStatus);
347       final String item = attributes.getValue("item");
348       assertSAX(item != null);
349       final SVNStatusType contentsStatus = StatusCallbackConvertor.convert(org.apache.subversion.javahl.types.Status.Kind.valueOf(item));
350       status.setContentsStatus(contentsStatus);
351
352       if (SVNStatusType.STATUS_CONFLICTED.equals(propertiesStatus) || SVNStatusType.STATUS_CONFLICTED.equals(contentsStatus)) {
353         status.setIsConflicted(true);
354       }
355
356       // optional
357       final String copied = attributes.getValue("copied");
358       if (copied != null && Boolean.parseBoolean(copied)) {
359         status.setIsCopied(true);
360       }
361       final String treeConflicted = attributes.getValue("tree-conflicted");
362       if (treeConflicted != null && Boolean.parseBoolean(treeConflicted)) {
363         status.setIsConflicted(true);
364       }
365
366       final String revision = attributes.getValue("revision");
367       if (! StringUtil.isEmptyOrSpaces(revision)) {
368         try {
369           final long number = Long.parseLong(revision);
370           status.setRevision(SVNRevision.create(number));
371         } catch (NumberFormatException e) {
372           throw new SAXException(e);
373         }
374       }
375     }
376
377     @Override
378     public void postEffect(DataCallback callback) {
379     }
380
381     @Override
382     public void preEffect(DataCallback callback) {
383     }
384
385     @Override
386     public void characters(String s, PortableStatus pending) {
387     }
388   }
389
390   private static class Entry extends ElementHandlerBase {
391     private final File myBase;
392
393     private Entry(final File base) {
394       super(new String[]{"wc-status"}, new String[]{});
395       myBase = base;
396     }
397
398     @Override
399     protected void updateStatus(Attributes attributes, PortableStatus status) throws SAXException {
400       final String path = attributes.getValue("path");
401       assertSAX(path != null);
402       final File file = new File(myBase, path);
403       status.setFile(file);
404       status.setKind(file.isDirectory() ? SVNNodeKind.DIR : SVNNodeKind.FILE);
405       status.setPath(path);
406     }
407
408     @Override
409     public void postEffect(DataCallback callback) {
410       callback.switchPath();
411     }
412
413     @Override
414     public void preEffect(DataCallback callback) {
415     }
416
417     @Override
418     public void characters(String s, PortableStatus pending) {
419     }
420   }
421
422   private static class Changelist extends ElementHandlerBase {
423     private String myName;
424     
425     private Changelist() {
426       super(new String[]{}, new String[]{"entry"});
427     }
428
429     @Override
430     protected void updateStatus(Attributes attributes, PortableStatus status) throws SAXException {
431       final String name = attributes.getValue("name");
432       assertSAX(! StringUtil.isEmptyOrSpaces(name));
433       myName = name;
434     }
435
436     @Override
437     public void postEffect(DataCallback callback) {
438     }
439
440     @Override
441     public void preEffect(DataCallback callback) {
442       callback.switchChangeList(myName);
443     }
444
445     @Override
446     public void characters(String s, PortableStatus pending) {
447     }
448   }
449
450   private static class Target extends ElementHandlerBase {
451     private Target() {
452       super(new String[]{}, new String[]{"entry"});
453     }
454
455     @Override
456     protected void updateStatus(Attributes attributes, PortableStatus status) {
457     }
458
459     @Override
460     public void postEffect(DataCallback callback) {
461     }
462
463     @Override
464     public void preEffect(DataCallback callback) {
465     }
466
467     @Override
468     public void characters(String s, PortableStatus pending) {
469     }
470   }
471
472   private static class Status extends ElementHandlerBase {
473     private Status() {
474       super(new String[]{"target"}, new String[]{"changelist"});
475     }
476
477     @Override
478     protected void updateStatus(Attributes attributes, PortableStatus status) {
479     }
480
481     @Override
482     public void postEffect(final DataCallback callback) {
483     }
484
485     @Override
486     public void preEffect(DataCallback callback) {
487     }
488
489     @Override
490     public void characters(String s, PortableStatus pending) {
491     }
492   }
493
494   public abstract static class ElementHandlerBase {
495     private final Set<String> myAwaitedChildren;
496     private final Set<String> myAwaitedChildrenMultiple;
497
498     ElementHandlerBase(String[] awaitedChildren, String[] awaitedChildrenMultiple) {
499       myAwaitedChildren = new HashSet<String>(Arrays.asList(awaitedChildren));
500       myAwaitedChildrenMultiple = new HashSet<String>(Arrays.asList(awaitedChildrenMultiple));
501     }
502
503     protected abstract void updateStatus(Attributes attributes, PortableStatus status) throws SAXException;
504     public abstract void postEffect(final DataCallback callback);
505     public abstract void preEffect(final DataCallback callback);
506
507     public boolean startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
508       if (myAwaitedChildrenMultiple.contains(qName)) {
509         return true;
510       }
511       return myAwaitedChildren.remove(qName);
512     }
513
514     public abstract void characters(String s, PortableStatus pending);
515   }
516
517   public interface DataCallback {
518     void switchPath();
519     void switchChangeList(final String newList);
520   }
521 }