1 /**
2  * @copyright
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  * @endcopyright
22  */
23 package org.tigris.subversion.javahl;
24 
25 import java.io.*;
26 import java.util.HashMap;
27 import java.util.Iterator;
28 import java.util.Map;
29 import java.util.Date;
30 
31 import junit.framework.Assert;
32 /**
33  * This class describe the expected state of the working copy
34  */
35 public class WC
36 {
37     /**
38      * the map of the items of the working copy. The relative path is the key
39      * for the map
40      */
41     Map items = new HashMap();
42 
43     /**
44      * Generate from the expected state of the working copy a new working copy
45      * @param root      the working copy directory
46      * @throws IOException
47      */
materialize(File root)48     public void materialize(File root) throws IOException
49     {
50         // generate all directories first
51         Iterator it = items.values().iterator();
52         while (it.hasNext())
53         {
54             Item item = (Item) it.next();
55             if (item.myContent == null) // is a directory
56             {
57                 File dir = new File(root, item.myPath);
58                 if (!dir.exists())
59                     dir.mkdirs();
60             }
61         }
62         // generate all files with the content in the second run
63         it = items.values().iterator();
64         while (it.hasNext())
65         {
66             Item item = (Item) it.next();
67             if (item.myContent != null) // is a file
68             {
69                 File file = new File(root, item.myPath);
70                 PrintWriter pw = new PrintWriter(new FileOutputStream(file));
71                 pw.print(item.myContent);
72                 pw.close();
73             }
74         }
75     }
76     /**
77      * Add a new item to the working copy
78      * @param path      the path of the item
79      * @param content   the content of the item. A null content signifies a
80      *                  directory
81      * @return          the new Item object
82      */
addItem(String path, String content)83     public Item addItem(String path, String content)
84     {
85         return new Item(path, content);
86     }
87 
88     /**
89      * Returns the item at a path
90      * @param path  the path, where the item is searched
91      * @return  the found item
92      */
getItem(String path)93     public Item getItem(String path)
94     {
95         return (Item) items.get(path);
96     }
97 
98     /**
99      * Remove the item at a path
100      * @param path  the path, where the item is removed
101      */
removeItem(String path)102     public void removeItem(String path)
103     {
104         items.remove(path);
105     }
106 
107     /**
108      * Set text (content) status of the item at a path
109      * @param path      the path, where the status is set
110      * @param status    the new text status
111      */
setItemTextStatus(String path, int status)112     public void setItemTextStatus(String path, int status)
113     {
114         ((Item) items.get(path)).textStatus = status;
115     }
116 
117     /**
118      * Set property status of the item at a path
119      * @param path      the path, where the status is set
120      * @param status    the new property status
121      */
setItemPropStatus(String path, int status)122     public void setItemPropStatus(String path, int status)
123     {
124         ((Item) items.get(path)).propStatus = status;
125     }
126 
127     /**
128      * Set the revision number of the item at a path
129      * @param path      the path, where the revision number is set
130      * @param revision  the new revision number
131      */
setItemWorkingCopyRevision(String path, long revision)132     public void setItemWorkingCopyRevision(String path, long revision)
133     {
134         ((Item) items.get(path)).workingCopyRev = revision;
135     }
136 
137     /**
138      * Set the revision number of all paths in a working copy.
139      * @param revision The revision number to associate with all
140      * paths.
141      */
setRevision(long revision)142     public void setRevision(long revision)
143     {
144         Iterator iter = this.items.values().iterator();
145 
146         while (iter.hasNext())
147         {
148             Item item = (Item) iter.next();
149             item.workingCopyRev = revision;
150         }
151     }
152 
153     /**
154      * Returns the file content of the item at a path
155      * @param path  the path, where the content is retrieved
156      * @return  the content of the file
157      */
getItemContent(String path)158     public String getItemContent(String path)
159     {
160         return ((Item) items.get(path)).myContent;
161     }
162 
163     /**
164      * Set the file content of the item at a path
165      * @param path      the path, where the content is set
166      * @param content   the new content
167      */
setItemContent(String path, String content)168     public void setItemContent(String path, String content)
169     {
170         // since having no content signals a directory, changes of removing the
171         // content or setting a former not set content is not allowed. That
172         // would change the type of the item.
173         Assert.assertNotNull("cannot unset content", content);
174         Item i = (Item) items.get(path);
175         Assert.assertNotNull("cannot set content on directory", i.myContent);
176         i.myContent = content;
177     }
178 
179     /**
180      * set the flag to check the content of item at a path during next check.
181      * @param path      the path, where the flag is set
182      * @param check     the flag
183      */
setItemCheckContent(String path, boolean check)184     public void setItemCheckContent(String path, boolean check)
185     {
186         Item i = (Item) items.get(path);
187         i.checkContent = check;
188     }
189 
190     /**
191      * Set the expected node kind at a path
192      * @param path      the path, where the node kind is set
193      * @param nodeKind  the expected node kind
194      */
setItemNodeKind(String path, int nodeKind)195     public void setItemNodeKind(String path, int nodeKind)
196     {
197         Item i = (Item) items.get(path);
198         i.nodeKind = nodeKind;
199     }
200 
201     /**
202      * Set the expected lock state at a path
203      * @param path      the path, where the lock state is set
204      * @param isLocked  the flag
205      */
setItemIsLocked(String path, boolean isLocked)206     public void setItemIsLocked(String path, boolean isLocked)
207     {
208         Item i = (Item) items.get(path);
209         i.isLocked = isLocked;
210     }
211 
212     /**
213      * Set the expected switched flag at a path
214      * @param path          the path, where the switch flag is set
215      * @param isSwitched    the flag
216      */
setItemIsSwitched(String path, boolean isSwitched)217     public void setItemIsSwitched(String path, boolean isSwitched)
218     {
219         Item i = (Item) items.get(path);
220         i.isSwitched = isSwitched;
221     }
222 
223     /**
224      * Set the youngest committed revision of an out of date item at
225      * <code>path</code>.
226      * @param path The path to set the last revision for.
227      * @param revision The last revision number for <code>path</code>
228      * known to the repository.
229      */
setItemReposLastCmtRevision(String path, long revision)230     public void setItemReposLastCmtRevision(String path, long revision)
231     {
232         ((Item) items.get(path)).reposLastCmtRevision = revision;
233     }
234 
235     /**
236      * Set the youngest committed revision of an out of date item at
237      * <code>path</code>.
238      * @param path The path to set the last author for.
239      * @param revision The last author for <code>path</code> known to
240      * the repository.
241      */
setItemReposLastCmtAuthor(String path, String author)242     public void setItemReposLastCmtAuthor(String path, String author)
243     {
244         ((Item) items.get(path)).reposLastCmtAuthor = author;
245     }
246 
247     /**
248      * Set the youngest committed revision of an out of date item at
249      * <code>path</cod>.
250      * @param path The path to set the last date for.
251      * @param revision The last date for <code>path</code> known to
252      * the repository.
253      */
setItemReposLastCmtDate(String path, long date)254     public void setItemReposLastCmtDate(String path, long date)
255     {
256         ((Item) items.get(path)).reposLastCmtDate = date;
257     }
258 
259     /**
260      * Set the youngest committed node kind of an out of date item at
261      * <code>path</code>.
262      * @param path The path to set the last node kind for.
263      * @param revision The last node kind for <code>path</code> known
264      * to the repository.
265      */
setItemReposKind(String path, int nodeKind)266     public void setItemReposKind(String path, int nodeKind)
267     {
268         ((Item) items.get(path)).reposKind = nodeKind;
269     }
270 
271     /**
272      * Set the youngest committed rev, author, date, and node kind of an
273      * out of date item at <code>path</code>.
274      *
275      * @param path The path to set the repos info for.
276      * @param revision The last revision number.
277      * @param revision The last author.
278      * @param date The last date.
279      * @param nodeKind The last node kind.
280      */
setItemOODInfo(String path, long revision, String author, long date, int nodeKind)281     public void setItemOODInfo(String path, long revision, String author,
282                                long date, int nodeKind)
283     {
284         this.setItemReposLastCmtRevision(path, revision);
285         this.setItemReposLastCmtAuthor(path, author);
286         this.setItemReposLastCmtDate(path, date);
287         this.setItemReposKind(path, nodeKind);
288     }
289 
290     /**
291      * Copy an expected working copy state
292      * @return the copy of the exiting object
293      */
copy()294     public WC copy()
295     {
296         WC c = new WC();
297         Iterator it = items.values().iterator();
298         while (it.hasNext())
299         {
300             ((Item) it.next()).copy(c);
301         }
302         return c;
303     }
304 
305     /**
306      * Check the result of a single file SVNClient.list call
307      * @param tested            the result array
308      * @param singleFilePath    the path to be checked
309      * @throws Exception
310      */
check(DirEntry[] tested, String singleFilePath)311     void check(DirEntry[] tested, String singleFilePath)
312     {
313         Assert.assertEquals("not a single dir entry", 1, tested.length);
314         Item item = (Item)items.get(singleFilePath);
315         Assert.assertNotNull("not found in working copy", item);
316         Assert.assertNotNull("not a file", item.myContent);
317         Assert.assertEquals("state says file, working copy not",
318                 tested[0].getNodeKind(),
319                 item.nodeKind == -1 ? NodeKind.file : item.nodeKind);
320     }
321 
322     /**
323      * Check the result of a directory SVNClient.list call
324      * @param tested        the result array
325      * @param basePath      the path of the directory
326      * @param recursive     the recursive flag of the call
327      * @throws Exception
328      */
check(DirEntry[] tested, String basePath, boolean recursive)329     void check(DirEntry[] tested, String basePath, boolean recursive)
330     {
331         // clear the touched flag of all items
332         Iterator it = items.values().iterator();
333         while (it.hasNext())
334         {
335             Item item = (Item) it.next();
336             item.touched = false;
337         }
338 
339         // normalize directory path
340         if (basePath != null && basePath.length() > 0)
341         {
342             basePath += '/';
343         }
344         else
345         {
346             basePath = "";
347         }
348         // check all returned DirEntry's
349         for (int i = 0; i < tested.length; i++)
350         {
351             String name = basePath + tested[i].getPath();
352             Item item = (Item) items.get(name);
353             Assert.assertNotNull("null paths won't be found in working copy",
354                                  item);
355             if (item.myContent != null)
356             {
357                 Assert.assertEquals("Expected '" + tested[i] + "' to be file",
358                         tested[i].getNodeKind(),
359                         item.nodeKind == -1 ? NodeKind.file : item.nodeKind);
360             }
361             else
362             {
363                 Assert.assertEquals("Expected '" + tested[i] + "' to be dir",
364                         tested[i].getNodeKind(),
365                         item.nodeKind == -1 ? NodeKind.dir : item.nodeKind);
366             }
367             item.touched = true;
368         }
369 
370         // all items should have been in items, should had their touched flag
371         // set
372         it = items.values().iterator();
373         while (it.hasNext())
374         {
375             Item item = (Item) it.next();
376             if (!item.touched)
377             {
378                 if (item.myPath.startsWith(basePath) &&
379                         !item.myPath.equals(basePath))
380                 {
381                     // Non-recursive checks will fail here.
382                     Assert.assertFalse("Expected path '" + item.myPath +
383                                        "' not found in dir entries",
384                                        recursive);
385 
386                     // Look deeper under the tree.
387                     boolean found = false;
388                     for (int i = 0; i < tested.length; i++)
389                     {
390                         if (tested[i].getNodeKind() == NodeKind.dir)
391                         {
392                             if (item.myPath.startsWith(basePath +
393                                                        tested[i].getPath()))
394                             {
395                                 found = true;
396                                 break;
397                             }
398                         }
399                     }
400                     Assert.assertTrue("Expected path '" + item.myPath +
401                                        "' not found in dir entries", found);
402                 }
403             }
404         }
405     }
406 
407     /**
408      * Check the result of a SVNClient.status() versus the expected
409      * state.  Does not extract "out of date" information from the
410      * repository.
411      *
412      * @param tested            the result to be tested
413      * @param workingCopyPath   the path of the working copy
414      * @throws IOException If there is a problem finding or reading
415      * the WC.
416      * @see #check(Status[], String, boolean)
417      */
check(Status[] tested, String workingCopyPath)418     void check(Status[] tested, String workingCopyPath)
419         throws IOException
420     {
421         check(tested, workingCopyPath, false);
422     }
423 
424     /**
425      * Check the result of a SVNClient.status() versus the expected state.
426      *
427      * @param tested The result to be tested.
428      * @param workingCopyPath The path of the working copy.
429      * @param checkRepos Whether to compare the "out of date" statii.
430      * @throws IOException If there is a problem finding or reading
431      * the WC.
432      */
check(Status[] tested, String workingCopyPath, boolean checkRepos)433     void check(Status[] tested, String workingCopyPath, boolean checkRepos)
434         throws IOException
435     {
436         // clear the touched flag of all items
437         Iterator it = items.values().iterator();
438         while (it.hasNext())
439         {
440             Item item = (Item) it.next();
441             item.touched = false;
442         }
443 
444         String normalizeWCPath =
445                 workingCopyPath.replace(File.separatorChar, '/');
446 
447         // check all result Staus object
448         for (int i = 0; i < tested.length; i++)
449         {
450             String path = tested[i].getPath();
451             Assert.assertTrue("status path starts not with working copy path",
452                     path.startsWith(normalizeWCPath));
453 
454             // we calculate the relative path to the working copy root
455             if (path.length() > workingCopyPath.length() + 1)
456             {
457                 Assert.assertEquals("missing '/' in status path",
458                         path.charAt(workingCopyPath.length()), '/');
459                 path = path.substring(workingCopyPath.length() + 1);
460             }
461             else
462                 // this is the working copy root itself
463                 path = "";
464 
465             Item item = (Item) items.get(path);
466             Assert.assertNotNull("status not found in working copy: " + path,
467                     item);
468             Assert.assertEquals("wrong text status in working copy: " + path,
469                     item.textStatus, tested[i].getTextStatus());
470             if (item.workingCopyRev != -1)
471                 Assert.assertEquals("wrong revision number in working copy: "
472                             + path,
473                         item.workingCopyRev, tested[i].getRevisionNumber());
474             Assert.assertEquals("lock status wrong: " + path,
475                     item.isLocked, tested[i].isLocked());
476             Assert.assertEquals("switch status wrong: " + path,
477                     item.isSwitched, tested[i].isSwitched());
478             Assert.assertEquals("wrong prop status in working copy: " + path,
479                     item.propStatus, tested[i].getPropStatus());
480             if (item.myContent != null)
481             {
482                 Assert.assertEquals("state says file, working copy not: " + path,
483                         tested[i].getNodeKind(),
484                         item.nodeKind == -1 ? NodeKind.file : item.nodeKind);
485                 if (tested[i].getTextStatus() == Status.Kind.normal ||
486                         item.checkContent)
487                 {
488                     File input = new File(workingCopyPath, item.myPath);
489                     Reader rd =
490                             new InputStreamReader(new FileInputStream(input));
491                     StringBuffer buffer = new StringBuffer();
492                     int ch;
493                     while ((ch = rd.read()) != -1)
494                     {
495                         buffer.append((char) ch);
496                     }
497                     rd.close();
498                     Assert.assertEquals("content mismatch: " + path,
499                             buffer.toString(), item.myContent);
500                 }
501             }
502             else
503             {
504                 Assert.assertEquals("state says dir, working copy not: " + path,
505                         tested[i].getNodeKind(),
506                         item.nodeKind == -1 ? NodeKind.dir : item.nodeKind);
507             }
508 
509             if (checkRepos)
510             {
511                 Assert.assertEquals("Last commit revisions for OOD path '"
512                                     + item.myPath + "' don't match:",
513                                     item.reposLastCmtRevision,
514                                     tested[i].getReposLastCmtRevisionNumber());
515                 Assert.assertEquals("Last commit kinds for OOD path '"
516                                     + item.myPath + "' don't match:",
517                                     item.reposKind, tested[i].getReposKind());
518 
519                 // Only the last committed rev and kind is available for
520                 // paths deleted in the repos.
521                 if (tested[i].getRepositoryTextStatus() != Status.Kind.deleted)
522                 {
523                     long lastCmtTime =
524                         (tested[i].getReposLastCmtDate() == null ?
525                          0 : tested[i].getReposLastCmtDate().getTime());
526                     Assert.assertEquals("Last commit dates for OOD path '" +
527                                         item.myPath + "' don't match:",
528                                         new Date(item.reposLastCmtDate),
529                                         new Date(lastCmtTime));
530                     Assert.assertEquals("Last commit authors for OOD path '"
531                                         + item.myPath + "' don't match:",
532                                         item.reposLastCmtAuthor,
533                                         tested[i].getReposLastCmtAuthor());
534                 }
535             }
536             item.touched = true;
537         }
538 
539         // all items which have the touched flag not set, are missing in the
540         // result array
541         it = items.values().iterator();
542         while (it.hasNext())
543         {
544             Item item = (Item) it.next();
545             Assert.assertTrue("item in working copy not found in status",
546                     item.touched);
547         }
548     }
549 
550     /**
551      * internal class to discribe a single working copy item
552      */
553     public class Item
554     {
555         /**
556          * the content of a file. A directory has a null content
557          */
558         String myContent;
559 
560         /**
561          * the relative path of the item
562          */
563         String myPath;
564 
565         /**
566          * the text (content) status of the item
567          */
568         int textStatus = Status.Kind.normal;
569 
570         /**
571          * the property status of the item.
572          */
573         int propStatus = Status.Kind.none;
574 
575         /**
576          * the expected revision number. -1 means do not check.
577          */
578         long workingCopyRev = -1;
579 
580         /**
581          * flag if item has been touched. To detect missing items.
582          */
583         boolean touched;
584 
585         /**
586          * flag if the content will be checked
587          */
588         boolean checkContent;
589 
590         /**
591          * expected node kind. -1 means do not check.
592          */
593         int nodeKind = -1;
594 
595         /**
596          * expected locked status
597          */
598         boolean isLocked;
599 
600         /**
601          * expected switched status
602          */
603         boolean isSwitched;
604 
605         /**
606          * youngest committed revision on repos if out of date
607          */
608         long reposLastCmtRevision = Revision.SVN_INVALID_REVNUM;
609 
610         /**
611          * most recent commit date on repos if out of date
612          */
613         long reposLastCmtDate = 0;
614 
615         /**
616          * node kind of the youngest commit if out of date
617          */
618         int reposKind = NodeKind.none;
619 
620         /**
621          * author of the youngest commit if out of date.
622          */
623         String reposLastCmtAuthor;
624 
625         /**
626          * create a new item
627          * @param path      the path of the item.
628          * @param content   the content of the item. A null signals a directory.
629          */
630         @SuppressWarnings("unchecked")
Item(String path, String content)631         private Item(String path, String content)
632         {
633             myPath = path;
634             myContent = content;
635             items.put(path, this);
636         }
637 
638         /**
639          * copy constructor
640          * @param source    the copy source.
641          * @param owner     the WC of the copy
642          */
643         @SuppressWarnings("unchecked")
Item(Item source, WC owner)644         private Item(Item source, WC owner)
645         {
646             myPath = source.myPath;
647             myContent = source.myContent;
648             textStatus = source.textStatus;
649             propStatus = source.propStatus;
650             owner.items.put(myPath, this);
651         }
652 
653         /**
654          * copy this item
655          * @param owner the new WC
656          * @return  the copied item
657          */
copy(WC owner)658         private Item copy(WC owner)
659         {
660             return new Item(this, owner);
661         }
662     }
663 }
664