1
2
3
4
5
6
7
8
9
10
11
12
13 package org.abstracthorizon.aequo.file;
14
15 import java.io.File;
16 import java.io.FileFilter;
17 import java.io.IOException;
18 import java.util.ArrayList;
19 import java.util.Arrays;
20 import java.util.HashMap;
21 import java.util.HashSet;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Set;
26 import java.util.regex.Pattern;
27
28 import javax.swing.DefaultListSelectionModel;
29 import javax.swing.ListSelectionModel;
30 import javax.swing.event.ListDataEvent;
31 import javax.swing.event.ListDataListener;
32 import javax.swing.event.TableModelEvent;
33 import javax.swing.event.TableModelListener;
34 import javax.swing.table.TableModel;
35
36 import org.abstracthorizon.aequo.CompareEntry;
37 import org.abstracthorizon.aequo.CompareModel;
38 import org.abstracthorizon.aequo.util.filters.Filter;
39
40
41
42
43
44
45 public class FilesModel extends FileCompareEntry implements TableModel, CompareModel<File, FileCompareEntry> {
46
47
48 protected ArrayList<TableModelListener> tableListeners = new ArrayList<TableModelListener>();
49
50
51 protected ArrayList<ListDataListener> listListeners = new ArrayList<ListDataListener>();
52
53
54 protected ArrayList<RefreshListener> refreshListeners = new ArrayList<RefreshListener>();
55
56
57 protected List<FileCompareEntry> visibleEntries = new ArrayList<FileCompareEntry>();
58
59
60 protected DefaultListSelectionModel selectionModel = new DefaultListSelectionModel();
61
62
63 private static final boolean SLOWDOWN_FOR_DEBUG = false;
64
65
66 protected Filter filter;
67
68
69 protected boolean expandDifferences = true;
70
71
72
73
74
75 public FilesModel(File leftDir, File rightDir) {
76 super(null, new File[]{leftDir, rightDir});
77 filter = new Filter();
78 }
79
80
81
82
83
84
85
86 public FilesModel(File leftDir, File rightDir, Filter filter) {
87 super(null, new File[]{leftDir, rightDir});
88 this.filter = filter;
89 }
90
91
92
93
94
95 public File getLeftFile() {
96 return data[0];
97 }
98
99
100
101
102
103 public File getRightFile() {
104 return data[1];
105 }
106
107
108
109
110
111 public Filter getFilter() {
112 return filter;
113 }
114
115
116
117
118
119 public void setFilter(Filter filter) {
120 this.filter = filter;
121 }
122
123
124
125
126
127 public boolean isExpandDifferences() {
128 return expandDifferences;
129 }
130
131
132
133
134
135 public void setExpandDifferences(boolean expandDifferences) {
136 this.expandDifferences = expandDifferences;
137 }
138
139
140
141
142
143
144 public void refresh() {
145 synchronized (visibleEntries) {
146 visibleEntries.clear();
147 }
148 refresh(this, 1, false);
149 if (children != null) {
150 for (FileCompareEntry e : children) {
151 e.updateEntryStatus();
152 }
153 expand(this);
154 }
155 if (children != null) {
156 for (FileCompareEntry e : children) {
157 refresh(e, 2, true);
158 }
159 }
160 notifyRefreshListeners(null);
161 }
162
163
164
165
166
167
168
169 public void refresh(FileCompareEntry entry, int level, boolean recursively) {
170 refreshRecursively(entry, level, recursively);
171 if (expandDifferences && entry.hasChildren()) {
172 expandChangedRecursively(entry);
173 }
174 }
175
176
177
178
179
180
181 protected void expandChangedRecursively(FileCompareEntry entry) {
182 int i = 0;
183 boolean dontExpand = true;
184 while (dontExpand && (i < entry.children.length)) {
185 if (entry.children[i].getStatus(0) != CompareEntry.EQUAL) {
186 dontExpand = false;
187 }
188 i++;
189 }
190 if (!dontExpand) {
191 expand(entry);
192 }
193 for (FileCompareEntry e : entry.children) {
194 if (e.hasChildren()) {
195 expandChangedRecursively(e);
196 }
197 }
198 }
199
200
201
202
203
204
205
206
207 protected void refreshRecursively(FileCompareEntry entry, int level, boolean recursively) {
208 boolean hasChanges = false;
209 Map<String, FileCompareEntry> entries = new HashMap<String, FileCompareEntry>();
210 if (entry.children != null) {
211 for (FileCompareEntry e : entry.children) {
212 File[] files = e.getData();
213 for (File f : files) {
214 if (f != null) {
215 entries.put(f.getName(), e);
216 break;
217 }
218 }
219 }
220 }
221
222 File[] files = entry.getData();
223 for (File dir : files) {
224 if (dir.exists() && dir.isDirectory()) {
225 File[] fs = null;
226 if (filter.isEmpty()) {
227 fs = dir.listFiles();
228 } else {
229 fs = dir.listFiles(new FileFilter() {
230 protected Pattern p = filter.getRegExpPattern();
231
232 public boolean accept(File pathname) {
233 return !p.matcher(pathname.getName()).matches();
234 }
235 });
236 }
237
238 if ((fs != null) && (fs.length > 0)) {
239 for (File f : fs) {
240 String name = f.getName();
241 FileCompareEntry e = entries.get(name);
242 if ((e == null) && (entry.children != null)) {
243 for (int i = 0; (i < entry.children.length)
244 && (e == null); i++) {
245 if (name.equals(entry.children[i].getData(0)
246 .getName())) {
247 e = entry.children[i];
248 }
249 }
250 }
251 if (e == null) {
252 File[] nfs = new File[files.length];
253 for (int i = 0; i < nfs.length; i++) {
254 nfs[i] = new File(files[i], name);
255 }
256 e = new FileCompareEntry(entry, nfs, level);
257
258 hasChanges = true;
259 entries.put(name, e);
260 } else {
261 boolean empty = true;
262 int i = 0;
263 File[] dfs = e.getData();
264 while (empty && i < dfs.length) {
265 if (dfs[i].exists()) {
266 empty = false;
267 }
268 i++;
269 }
270 if (empty) {
271 entries.remove(e.getData(0).getName());
272 hasChanges = true;
273 }
274 }
275 }
276 }
277 }
278 }
279 Iterator<FileCompareEntry> it = entries.values().iterator();
280 while (it.hasNext()) {
281 FileCompareEntry e = it.next();
282 boolean notExist = true;
283 File[] fs = e.getData();
284 int i = 0;
285 while (notExist && (i < fs.length)) {
286 if (fs[i].exists()) {
287 notExist = false;
288 }
289 i++;
290 }
291 if (notExist) {
292 it.remove();
293 hasChanges = true;
294 }
295 }
296
297 if (SLOWDOWN_FOR_DEBUG) {
298 try {
299 Thread.sleep(50);
300 } catch (Exception ignore) {
301 }
302 }
303 if (hasChanges && entries.size() > 0) {
304 notifyRefreshListeners(entry);
305
306 entry.children = new FileCompareEntry[entries.size()];
307
308 Set<String> set = entries.keySet();
309 String[] names = new String[set.size()];
310 names = set.toArray(names);
311 Arrays.sort(names);
312 int i = 0;
313 for (String name : names) {
314 entry.children[i] = entries.get(name);
315 i++;
316 }
317 if (recursively) {
318 for (FileCompareEntry e : entry.children) {
319 refreshRecursively(e, level + 1, true);
320 if (SLOWDOWN_FOR_DEBUG) {
321 try {
322 Thread.sleep(50);
323 } catch (Exception ignore) {
324 }
325 }
326 }
327 }
328 }
329 refreshVisible(entry, entries);
330 entry.updateEntryStatus();
331 }
332
333
334
335
336 public void filterOut() {
337 Pattern p = getFilter().getRegExpPattern();
338 filterOut(this, p);
339 }
340
341
342
343
344
345
346 protected void filterOut(FileCompareEntry entry, Pattern p) {
347 FileCompareEntry[] children = entry.getChildren();
348 if ((children != null) && (children.length > 0)) {
349 int first = Integer.MAX_VALUE;
350 int last = -1;
351
352 boolean removed = false;
353 ArrayList<FileCompareEntry> newChildren = new ArrayList<FileCompareEntry>(children.length);
354 ArrayList<FileCompareEntry> removedChildren = new ArrayList<FileCompareEntry>();
355 for (int i = 0; i < children.length; i++) {
356 FileCompareEntry c = children[i];
357 if (p.matcher(c.getData(0).getName()).matches()) {
358 removed = true;
359 if (c.index >= 0) {
360 removedChildren.add(c);
361 if (c.index < first) {
362 first = c.index;
363 }
364 }
365 } else {
366 newChildren.add(c);
367 }
368 }
369 if (removed) {
370 if (removedChildren.size() > 0) {
371 FileCompareEntry lastEntry = removedChildren.get(removedChildren.size() - 1);
372
373 last = findMax(lastEntry);
374 }
375
376 FileCompareEntry[] nc = new FileCompareEntry[newChildren.size()];
377 nc = newChildren.toArray(nc);
378 entry.children = nc;
379 }
380 children = entry.getChildren();
381 for (FileCompareEntry c : children) {
382 if (c.hasChildren()) {
383 filterOut(c, p);
384 }
385 }
386 if (removed && (last >= 0)) {
387 synchronized (visibleEntries) {
388 for (int i = removedChildren.size() - 1; i >= 0; i--) {
389 FileCompareEntry e = removedChildren.get(i);
390 if (e.index >= 0) {
391 collapse(e);
392 visibleEntries.remove(e.index);
393 }
394 }
395 }
396 renumber(entry);
397 }
398 fireChange(TableModelEvent.DELETE, first, last);
399 }
400 }
401
402
403
404
405
406
407 public boolean isExpanded(FileCompareEntry entry) {
408 return (entry.hasChildren() && entry.getChildren()[0].getIndex() >= 0);
409 }
410
411
412
413
414
415
416 public boolean isCollapsed(FileCompareEntry entry) {
417 return !entry.hasChildren() || (entry.hasChildren() && entry.getChildren()[0].getIndex() == -1);
418 }
419
420
421
422
423
424
425 public void refreshVisible(FileCompareEntry entry, Map<String, FileCompareEntry> entries) {
426 if ((entry.getIndex() >= 0) || (entry.getLevel() == 0)) {
427 int firstChanged = -1;
428 int lastChanged = -1;
429
430 int level = entry.getLevel() + 1;
431
432 boolean added = false;
433 boolean removed = false;
434 FileCompareEntry[] children = entry.getChildren();
435
436 int i = entry.getIndex() + 1;
437 int current = i;
438 int p = 0;
439
440 boolean loop = true;
441 boolean changed;
442
443 synchronized (visibleEntries) {
444 while (loop) {
445 changed = false;
446 if (i < visibleEntries.size()) {
447 FileCompareEntry e = visibleEntries.get(i);
448 if (e.getLevel() == level) {
449 if (!entries.containsValue(e)) {
450 visibleEntries.remove(i);
451 removed = true;
452 changed = true;
453 } else {
454 FileCompareEntry f = children[p];
455 if (e != f) {
456 visibleEntries.add(i, f);
457 added = true;
458 changed = true;
459 i++;
460 } else {
461
462 i++;
463 }
464 p++;
465 }
466 } else {
467 if (children != null) {
468
469 if (p >= children.length) {
470 loop = false;
471 } else {
472 FileCompareEntry f = children[p];
473 visibleEntries.add(i, f);
474 added = true;
475 changed = true;
476 i++;
477 p++;
478 }
479 } else {
480 loop = false;
481 }
482 }
483 } else {
484 if ((children != null) && (p < children.length)) {
485 FileCompareEntry e = children[p];
486 visibleEntries.add(e);
487 added = true;
488 changed = true;
489 p++;
490 if (p >= children.length) {
491 loop = false;
492 }
493 i++;
494 } else {
495 loop = false;
496 }
497 }
498 if (changed) {
499 if (firstChanged < 0) {
500 firstChanged = current;
501 }
502 lastChanged = current;
503 }
504 current++;
505 }
506 if (added || removed) {
507 renumber(entry);
508 }
509 }
510 if (added && removed) {
511 fireChange(TableModelEvent.UPDATE, firstChanged, lastChanged);
512 } else if (added) {
513 fireChange(TableModelEvent.INSERT, firstChanged, lastChanged);
514 } else if (removed) {
515 fireChange(TableModelEvent.DELETE, firstChanged, lastChanged);
516 }
517 }
518 }
519
520
521
522
523
524 public void expand(FileCompareEntry entry) {
525
526 if (entry.children != null) {
527 synchronized (visibleEntries) {
528 int i = entry.index + 1;
529 int level = entry.getLevel() + 1;
530 for (FileCompareEntry e : entry.children) {
531 if (visibleEntries.size() > i) {
532 if (visibleEntries.get(i) != e) {
533 visibleEntries.add(i, e);
534 }
535 } else {
536 visibleEntries.add(e);
537 }
538 i++;
539 }
540 if (visibleEntries.size() > i) {
541 FileCompareEntry e = visibleEntries.get(i);
542 while ((e != null) && (e.getLevel() == level)) {
543 visibleEntries.remove(i);
544 if (visibleEntries.size() > i) {
545 e = visibleEntries.get(i);
546 } else {
547 e = null;
548 }
549 }
550 }
551 renumber(entry);
552 }
553 int first = entry.children[0].index;
554 int last = first + entry.children.length - 1;
555 fireChange(TableModelEvent.INSERT, first, last);
556 }
557 }
558
559
560
561
562
563 public void expandAll(FileCompareEntry entry) {
564 if (entry.hasChildren()) {
565 expand(entry);
566 for (FileCompareEntry e : entry.getChildren()) {
567 if (e.hasChildren()) {
568 expandAll(e);
569 }
570 }
571 }
572 }
573
574
575
576
577
578 public void collapse(FileCompareEntry entry) {
579 if (entry.children != null) {
580 int first = entry.index + 1;
581 int last = 0;
582 boolean notifyChange = false;
583 synchronized (visibleEntries) {
584 if (first < visibleEntries.size()) {
585 int i = first;
586 FileCompareEntry e = visibleEntries.get(i);
587 while ((e != null) && (e.level > entry.level)) {
588 e.index = -1;
589 i++;
590 if (i < visibleEntries.size()) {
591 e = visibleEntries.get(i);
592 } else {
593 e = null;
594 }
595 }
596 last = i - 1;
597 for (i = last; i >= first; i--) {
598 visibleEntries.remove(i);
599 }
600 renumber(entry);
601 notifyChange = true;
602 }
603 }
604 if (notifyChange) {
605 fireChange(TableModelEvent.DELETE, first, last);
606 }
607 }
608 }
609
610
611
612
613
614 public void remove(FileCompareEntry entry) {
615 if (entry.index >= 0) {
616 collapse(entry);
617 int index = entry.index;
618 synchronized (visibleEntries) {
619 visibleEntries.remove(entry.index);
620
621 FileCompareEntry parent = entry.parentEntry;
622 if (parent != null) {
623 if (parent.children.length == 1) {
624 parent.children = null;
625 } else {
626 FileCompareEntry[] newChildren = new FileCompareEntry[parent.children.length - 1];
627 int i = 0;
628 int j = 0;
629 while (i < parent.children.length) {
630 if (entry == parent.children[i]) {
631 } else {
632 newChildren[j] = parent.children[i];
633 j++;
634 }
635 i++;
636 }
637 parent.children = newChildren;
638 }
639 parent.updateEntryStatus();
640 }
641
642 renumber(entry);
643 entry.index = -1;
644 }
645 fireChange(TableModelEvent.DELETE, index, index);
646 }
647 }
648
649
650
651
652
653
654 protected int findMax(FileCompareEntry entry) {
655 if (entry.hasChildren()) {
656 FileCompareEntry[] children = entry.getChildren();
657 for (int i = children.length - 1; i >= 0; i--) {
658 if (children[i].index >= 0) {
659 return findMax(children[i]);
660 }
661 }
662 }
663 return entry.index;
664 }
665
666
667
668
669
670 protected void renumber(FileCompareEntry entry) {
671 for (int i = entry.index + 1; i < visibleEntries.size(); i++) {
672 visibleEntries.get(i).index = i;
673 }
674 }
675
676
677
678
679
680
681
682 public void addRefreshListener(RefreshListener refreshListener) {
683 refreshListeners.add(refreshListener);
684 }
685
686
687
688
689
690
691 public void removeRefreshListener(RefreshListener refreshListener) {
692 refreshListeners.remove(refreshListener);
693 }
694
695
696
697
698
699 protected void notifyRefreshListeners(FileCompareEntry entry) {
700 for (RefreshListener refreshListener : refreshListeners) {
701 refreshListener.currentlyProcessing(entry);
702 }
703 }
704
705
706
707
708
709
710
711 public FileCompareEntry get(int index) {
712 synchronized (visibleEntries) {
713 return visibleEntries.get(index);
714 }
715 }
716
717
718
719
720
721 public ListSelectionModel getSelectionModel() {
722 return selectionModel;
723 }
724
725
726
727
728
729 public void addListDataListener(ListDataListener l) {
730 listListeners.add(l);
731 }
732
733
734
735
736
737 public void removeListDataListener(ListDataListener l) {
738 listListeners.remove(l);
739 }
740
741
742
743
744
745 public synchronized Object getElementAt(int index) {
746 synchronized (visibleEntries) {
747 return visibleEntries.get(index);
748 }
749 }
750
751
752
753
754
755 public synchronized int getSize() {
756 synchronized (visibleEntries) {
757 return visibleEntries.size();
758 }
759 }
760
761
762
763
764
765
766
767 public void addTableModelListener(TableModelListener l) {
768 tableListeners.add(l);
769 }
770
771
772
773
774
775 public void removeTableModelListener(TableModelListener l) {
776 tableListeners.remove(l);
777 }
778
779
780
781
782
783
784 public Class<?> getColumnClass(int columnIndex) {
785 if (columnIndex < 3) {
786 return FileCompareEntry.class;
787 } else {
788 return null;
789 }
790 }
791
792
793
794
795
796 public int getColumnCount() {
797 return 3;
798 }
799
800
801
802
803
804 public String getColumnName(int columnIndex) {
805 if (columnIndex == 0) {
806 return "Name";
807 } else if (columnIndex == 1) {
808 return "Size";
809 } else if (columnIndex == 2) {
810 return "Modified";
811 } else {
812 return null;
813 }
814 }
815
816
817
818
819
820 public synchronized int getRowCount() {
821 synchronized (visibleEntries) {
822 return visibleEntries.size();
823 }
824 }
825
826
827
828
829
830
831
832 public synchronized Object getValueAt(int rowIndex, int columnIndex) {
833 if (columnIndex < 3) {
834 synchronized (visibleEntries) {
835 FileCompareEntry entry = (FileCompareEntry)visibleEntries.get(rowIndex);
836 return entry;
837 }
838 } else {
839 return null;
840 }
841 }
842
843
844
845
846
847
848
849 public boolean isCellEditable(int rowIndex, int columnIndex) {
850 return false;
851 }
852
853
854
855
856
857
858
859 public void setValueAt(Object value, int rowIndex, int columnIndex) {
860 }
861
862
863
864
865
866
867 protected void contentsChanged(FileCompareEntry entry) {
868 fireChange(TableModelEvent.UPDATE, entry.index, entry.index);
869 }
870
871
872
873
874
875
876 protected void intervalAdded(FileCompareEntry entry) {
877 fireChange(TableModelEvent.INSERT, entry.index, entry.index);
878 }
879
880
881
882
883
884
885 protected void intervalRemoved(FileCompareEntry entry) {
886 fireChange(TableModelEvent.DELETE, entry.index, entry.index);
887 }
888
889
890
891
892
893
894 public void notifyOfChange(int start, int end) {
895 fireChange(TableModelEvent.UPDATE, start, end);
896 }
897
898
899
900
901
902
903 public void notifyOfRemoved(int start, int end) {
904 fireChange(TableModelEvent.DELETE, start, end);
905 }
906
907
908
909
910
911
912 public void notifyOfInserted(int start, int end) {
913 fireChange(TableModelEvent.INSERT, start, end);
914 }
915
916
917
918
919
920
921
922 protected void fireChange(int type, int start, int end) {
923 if (tableListeners.size() > 0) {
924 TableModelEvent event = new TableModelEvent(this, start, end, TableModelEvent.ALL_COLUMNS, type);
925 for (TableModelListener listener : tableListeners) {
926 listener.tableChanged(event);
927 }
928 }
929
930 if (listListeners.size() > 0) {
931 ListDataEvent event = null;
932 if (type == TableModelEvent.UPDATE) {
933 event = new ListDataEvent(this, ListDataEvent.CONTENTS_CHANGED, start, end);
934 } else if (type == TableModelEvent.INSERT) {
935 event = new ListDataEvent(this, ListDataEvent.INTERVAL_ADDED, start, end);
936 } else if (type == TableModelEvent.DELETE) {
937 event = new ListDataEvent(this, ListDataEvent.INTERVAL_REMOVED, start, end);
938 }
939 for (ListDataListener listener : listListeners) {
940 if (type == TableModelEvent.UPDATE) {
941 listener.contentsChanged(event);
942 } else if (type == TableModelEvent.INSERT) {
943 listener.intervalAdded(event);
944 } else if (type == TableModelEvent.DELETE) {
945 listener.intervalRemoved(event);
946 }
947 }
948 }
949 }
950
951
952
953
954
955
956
957 protected void notifyEntryChanged(int index) {
958
959 delayedEventThread.notifyOfChange(index);
960 }
961
962
963
964 protected DelayedEventThread delayedEventThread = new DelayedEventThread();
965
966
967
968
969 protected class DelayedEventThread implements Runnable {
970
971
972 protected Set<Integer> indexes = new HashSet<Integer>();
973
974
975 boolean triggered = false;
976
977
978 protected Thread thread;
979
980
981
982
983 public DelayedEventThread() {
984 thread = new Thread(this);
985 thread.setDaemon(true);
986 thread.start();
987 }
988
989
990
991
992
993
994
995 public synchronized void notifyOfChange(int index) {
996 indexes.add(index);
997 if (!triggered) {
998 triggered = true;
999 notify();
1000 }
1001 }
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011 public synchronized void run() {
1012 while (true) {
1013 while (!triggered) {
1014 try {
1015 wait();
1016 } catch (InterruptedException ignore) {
1017 }
1018 }
1019 try {
1020 wait(300);
1021 } catch (InterruptedException ignore) {
1022 }
1023 for (int index : indexes) {
1024 FilesModel.this.notifyOfChange(index, index);
1025 }
1026 indexes.clear();
1027 triggered = false;
1028 }
1029 }
1030 }
1031
1032
1033
1034
1035 public interface RefreshListener {
1036
1037
1038
1039
1040
1041
1042 public void currentlyProcessing(FileCompareEntry entry);
1043 }
1044 }