View Javadoc

1   /*
2    * Copyright (c) 2007 Creative Sphere Limited.
3    * All rights reserved. This program and the accompanying materials
4    * are made available under the terms of the Eclipse Public License v1.0
5    * which accompanies this distribution, and is available at
6    * http://www.eclipse.org/legal/epl-v10.html
7    *
8    * Contributors:
9    *
10   *   Creative Sphere - initial API and implementation
11   *
12   */
13  package org.abstracthorizon.aequo.text;
14  
15  import java.beans.PropertyChangeListener;
16  import java.beans.PropertyChangeSupport;
17  import java.io.File;
18  import java.io.FileReader;
19  import java.io.FileWriter;
20  import java.io.IOException;
21  import java.io.Reader;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.List;
25  
26  import org.abstracthorizon.aequo.CompareEntry;
27  import org.abstracthorizon.aequo.DefaultCompareModel;
28  import org.abstracthorizon.aequo.file.FileCompareEntry;
29  import org.incava.util.diff.Diff;
30  import org.incava.util.diff.Difference;
31  
32  /**
33   * Model of two sets of lines.
34   *
35   * @author Daniel Sendula
36   */
37  public class TextModel extends DefaultCompareModel<String, TextCompareEntry> {
38  
39      /** Read buffer size */
40      public static int BUFFER_SIZE = 10240;
41  
42      /** Empty string */
43      protected static final String EMPTY_STRING = "";
44  
45      /** Left file */
46      protected File fileLeft;
47      
48      /** Right file */
49      protected File fileRight;
50  
51      /** First difference block in model */
52      protected int firstDifferenceBlock;
53      
54      /** Last difference block in model */
55      protected int lastDifferenceBlock;
56      
57      /** Property change support */
58      protected PropertyChangeSupport propertyChangeSupport = new PropertyChangeSupport(this);
59      
60      /** Is left file dirty */
61      protected boolean leftFileDirty;
62      
63      /** Is right file dirty */
64      protected boolean rightFileDirty;
65      
66      /** File entry this text model is created of */
67      protected FileCompareEntry fileEntry;
68      
69      /**
70       * Constructor
71       * @param leftFile left file
72       * @param rightFile right file
73       */
74      public TextModel(File leftFile, File rightFile) {
75          super(2);
76          this.fileLeft = leftFile;
77          this.fileRight = rightFile;
78      }
79      
80      /**
81       * Constructor
82       * @param fileCompareEntry file compare entry
83       */
84      public TextModel(FileCompareEntry fileCompareEntry) {
85          this(fileCompareEntry.getData(0), fileCompareEntry.getData(1));
86          this.fileEntry = fileCompareEntry;
87      }
88      
89      /**
90       * Returns left file
91       * @return left file
92       */
93      public File getLeftFile() {
94          return fileLeft;
95      }
96      
97      /**
98       * Returns right file
99       * @return right file
100      */
101     public File getRightFile() {
102         return fileRight;
103     }
104 
105     /**
106      * Has left file been changed
107      * @return <code>true</code> if left file has been changed
108      */
109     public boolean isLeftFileDirty() {
110         return leftFileDirty;
111     }
112     
113     /**
114      * Sets if left file is dirty
115      * @param leftFileDirty is left file dirty
116      */
117     public void setLeftFileDirty(boolean leftFileDirty) {
118         if (this.leftFileDirty != leftFileDirty) {
119             boolean oldValue = this.leftFileDirty;
120             this.leftFileDirty = leftFileDirty;
121             propertyChangeSupport.firePropertyChange("leftFileDirty", oldValue, leftFileDirty);
122         }
123     }
124     
125     /**
126      * Has left file been changed
127      * @return <code>true</code> if left file has been changed
128      */
129     public boolean isRightFileDirty() {
130         return rightFileDirty;
131     }
132     
133     /**
134      * Sets if right file is dirty
135      * @param rightFileDirty is right file dirty
136      */
137     public void setRightFileDirty(boolean rightFileDirty) {
138         if (this.rightFileDirty != rightFileDirty) {
139             boolean oldValue = this.rightFileDirty;
140             this.rightFileDirty = rightFileDirty;
141             propertyChangeSupport.firePropertyChange("rightFileDirty", oldValue, rightFileDirty);
142         }
143     }
144     
145     /**
146      * Sets file for given column as dirty
147      * @param column column 
148      */
149     public void setFileDirty(int column) {
150         if (column == 0) {
151             setLeftFileDirty(true);
152         } else if (column == 1) {
153             setRightFileDirty(true);
154         }
155     }
156     
157     /**
158      * Refreshes file entry with new status if different after comparing
159      */
160     protected void refreshFileEntry() {
161         if (getSize() > 0) {
162             boolean equals = true;
163             int i = 0;
164             while (equals && (i < getSize())) {
165                 CompareEntry<String> entry = get(i);
166                 i++;
167                 equals = ((entry.getStatus(0) == entry.getStatus(1)) && (entry.getStatus(0) == CompareEntry.EQUAL));
168             }
169             
170             if (equals) {
171                 if ((fileEntry.getStatus(0) != CompareEntry.EQUAL)
172                     || (fileEntry.getStatus(1) != CompareEntry.EQUAL)) {
173                     fileEntry.setStatus(0, CompareEntry.EQUAL);
174                     fileEntry.setStatus(1, CompareEntry.EQUAL);
175                     fileEntry.updateEntryStatus(true);
176                 }
177             } else {
178                 if ((fileEntry.getStatus(0) == CompareEntry.EQUAL)
179                     || (fileEntry.getStatus(1) == CompareEntry.EQUAL)) {
180     
181                     fileEntry.setStatus(0, CompareEntry.DIFFERENT);
182                     fileEntry.setStatus(1, CompareEntry.DIFFERENT);
183                     fileEntry.updateEntryStatus(true);
184                 }
185             }
186         }
187     }
188     
189     /* Property support */
190     
191     /**
192      * Adds property change listener
193      * @param listener listener
194      */
195     public void addPropertyChangeListener(PropertyChangeListener listener) {
196         propertyChangeSupport.addPropertyChangeListener(listener);
197     }
198     
199     /**
200      * Adds property change listener
201      * @param propertyName property name
202      * @param listener listener
203      */
204     public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
205         propertyChangeSupport.addPropertyChangeListener(propertyName, listener);
206     }
207     
208     /**
209      * Removes property change listener
210      * @param listener listener
211      */
212     public void removePropertyChangeListener(PropertyChangeListener listener) {
213         propertyChangeSupport.removePropertyChangeListener(listener);
214     }
215     
216     /**
217      * Removes property change listener
218      * @param propertyName property name
219      * @param listener listener
220      */
221     public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
222         propertyChangeSupport.removePropertyChangeListener(propertyName, listener);
223     }
224 
225     
226     /* Methods */
227     
228     /**
229      * Saves changes of both files
230      * @throws IOException
231      */
232     public void saveAll() throws IOException {
233         saveLeft();
234         saveRight();
235     }
236 
237     /**
238      * Saves left file
239      * @throws IOException
240      */
241     public void saveLeft() throws IOException {
242         if (fileLeft == null) {
243             throw new IllegalArgumentException("File name is not suppliled");
244         }
245         List<String> dataLeft = getAsList(0);
246         saveFile(fileLeft, dataLeft);
247         setLeftFileDirty(false);
248         if (fileEntry != null) {
249             refreshFileEntry();
250         }
251     }
252 
253 
254     /**
255      * Saves right file
256      * @throws IOException
257      */
258     public void saveRight() throws IOException {
259         if (fileRight == null) {
260             throw new IllegalArgumentException("File name is not suppliled");
261         }
262         List<String> dataRight = getAsList(1);
263         saveFile(fileRight, dataRight);
264         setRightFileDirty(false);
265         if (fileEntry != null) {
266             refreshFileEntry();
267         }
268     }
269 
270     /**
271      * Loads both files
272      * @throws IOException
273      */
274     public void load() throws IOException {
275 
276         final List<String> dataLeft = loadFile(fileLeft);
277         final List<String> dataRight = loadFile(fileRight);
278         refresh(dataLeft, dataRight);
279         setLeftFileDirty(false);
280         setRightFileDirty(false);
281         if (fileEntry != null) {
282             refreshFileEntry();
283         }
284     }
285     
286     /**
287      * Refreshes the model by comparing two data lists
288      * @param dataLeft left list
289      * @param dataRight right list
290      */
291     public void refresh(final List<String> dataLeft, final List<String> dataRight) {
292         entries.clear();
293         Runnable process = new Runnable() {
294 
295             int ls = 0;
296             int rs = 0;
297             int le = 0;
298             int re = 0;
299 
300             int l = 0;
301             int r = 0;
302 
303             public void run() {
304                 Diff<String> diff = new Diff<String>(dataLeft, dataRight);
305                 List<Difference> diffs = diff.diff();
306 
307                 for (Difference d : diffs) {
308                     rs = d.getAddedStart();
309                     ls = d.getDeletedStart();
310                     progressTo(ls, rs);
311 
312                     re = d.getAddedEnd();
313                     le = d.getDeletedEnd();
314                     progressTo(le, re);
315                 }
316 
317                 int s1 = dataLeft.size();
318                 int s2 = dataRight.size();
319 
320                 progressTo(s1, s2);
321 
322             }
323 
324             protected void progressTo(int le, int re) {
325                 while ((le > l) || (re > r)) {
326                     TextCompareEntry entry;
327                     if ((le > l) && (re > r)) {
328                         entry = new TextCompareEntry(new String[]{dataLeft.get(l), dataRight.get(r)}, new int[]{l, r});
329                         entry.updateEntryStatus();
330                         l = l + 1;
331                         r = r + 1;
332                     } else if (le > l) {
333                         entry = new TextCompareEntry(new String[]{dataLeft.get(l), null}, new int[]{l, r});
334                         entry.updateEntryStatus();
335                         l = l + 1;
336                     } else {
337                         entry = new TextCompareEntry(new String[]{null, dataRight.get(r)}, new int[]{l, r});
338                         entry.updateEntryStatus();
339                         r = r + 1;
340                     }
341                     addImpl(entry);
342                 }
343             }
344         };
345 
346         process.run();
347     }
348 
349     
350     /**
351      * Refreshes the model by comparing two data lists
352      * @param dataLeft left list
353      * @param dataRight right list
354      */
355     public void refreshRange(final int start, final int end) {
356         Runnable process = new Runnable() {
357 
358             int ls = 0;
359             int rs = 0;
360             int le = 0;
361             int re = 0;
362 
363             int l = 0;
364             int r = 0;
365 
366             List<String> dataLeft;
367             List<String> dataRight;
368             
369             public void run() {
370                 dataLeft = getAsSubList(0, start, end);
371                 dataRight = getAsSubList(1, start, end);
372 
373                 for (int i = end; i >= start; i--) {
374                     removeImpl(i);
375                 }
376                 
377                 int current = start;
378                 
379                 Diff<String> diff = new Diff<String>(dataLeft, dataRight);
380                 List<Difference> diffs = diff.diff();
381 
382                 for (Difference d : diffs) {
383                     rs = d.getAddedStart();
384                     ls = d.getDeletedStart();
385                     current = progressTo(current, ls, rs);
386 
387                     re = d.getAddedEnd();
388                     le = d.getDeletedEnd();
389                     current = progressTo(current, le, re);
390                 }
391 
392                 int s1 = dataLeft.size();
393                 int s2 = dataRight.size();
394 
395                 current = progressTo(current, s1, s2);
396                 updateLineNumbers(start, end);
397                 getSelectionModel().setSelectionInterval(start, current - 1);
398             }
399 
400             protected int progressTo(int current, int le, int re) {
401                 while ((le > l) || (re > r)) {
402                     TextCompareEntry entry;
403                     int[] lineNumbers = new int[]{-1, -1};
404                     if ((le > l) && (re > r)) {
405                         entry = new TextCompareEntry(new String[]{dataLeft.get(l), dataRight.get(r)}, lineNumbers);
406                         entry.updateEntryStatus();
407                         l = l + 1;
408                         r = r + 1;
409                     } else if (le > l) {
410                         entry = new TextCompareEntry(new String[]{dataLeft.get(l), null}, lineNumbers);
411                         entry.updateEntryStatus();
412                         l = l + 1;
413                     } else {
414                         entry = new TextCompareEntry(new String[]{null, dataRight.get(r)}, lineNumbers);
415                         entry.updateEntryStatus();
416                         r = r + 1;
417                     }
418                     addImpl(current, entry);
419                     current++;
420                 }
421                 return current;
422             }
423         };
424 
425         process.run();
426     }
427 
428     /**
429      * Saves given list to a file
430      * @param file file
431      * @param data list
432      * @throws IOException
433      */
434     public static void saveFile(File file, List<String> data) throws IOException {
435         FileWriter fileWriter = new FileWriter(file);
436         try {
437             for (String line : data) {
438                 fileWriter.write(line);
439                 fileWriter.write('\n');
440             }
441         } finally {
442             fileWriter.close();
443         }
444     }
445 
446     /**
447      * Loads file to a list
448      * @param file file
449      * @return file as a list of lines
450      * @throws IOException
451      */
452     public static List<String> loadFile(File file) throws IOException {
453         if (file.exists() && file.isFile()) {
454             FileReader in = new FileReader(file);
455             return loadFile(in);
456         } else {
457             return Collections.emptyList();
458         }
459     }
460 
461     /**
462      * Loads lines from a reader to a list
463      * @param in reader
464      * @return list of lines
465      * @throws IOException
466      */
467     public static List<String> loadFile(Reader in) throws IOException {
468         ArrayList<String> list = new ArrayList<String>();
469 
470         StringBuffer sb = null;
471         char buf[] = new char[BUFFER_SIZE];
472         try {
473             int start = 0;
474             int r = in.read(buf);
475             while (r > 0) {
476                 int i = 0;
477                 while (i < r) {
478                     if (buf[i] == '\n') {
479                         if (i == start) {
480                             if (sb == null) {
481                                 list.add(EMPTY_STRING);
482                             } else {
483                                 list.add(sb.toString());
484                                 sb = null;
485                             }
486                         } else {
487                             if (sb == null) {
488                                 list.add(new String(buf, start, i - start));
489                             } else {
490                                 sb.append(buf, start, i - start);
491                                 list.add(sb.toString());
492                                 sb = null;
493                             }
494                         }
495 
496                         start = i + 1;
497                     }
498                     i = i + 1;
499                 } // while
500 
501                 if (start < r) {
502                     if (sb == null) {
503                         sb = new StringBuffer(buf.length - start + (buf.length - start) / 2);
504                         sb.append(buf, start, buf.length - start);
505                     }
506                     start = 0;
507                 }
508                 r = in.read(buf);
509             } // while
510         } finally {
511             in.close();
512         }
513 
514         return list;
515     }
516     
517     /**
518      * Updates line numbers with in given range
519      * @param start start index
520      * @param end end index. Can be -1 which means to the end of the list
521      */
522     public void updateLineNumbers(int start, int end) {
523         if (end < 0) {
524             end = getSize() - 1;
525         }
526         int leftLineNumber = 1;
527         int rightLineNumber = 1;
528         if (start > 0) {
529             TextCompareEntry entry = get(start -1);
530             leftLineNumber = entry.getLineNumber(0) + 1;
531             rightLineNumber = entry.getLineNumber(1) + 1;
532         }
533         for (int i = start; i <= end; i++) {
534             TextCompareEntry entry = get(i);
535             entry.setLineNumber(0, leftLineNumber);
536             entry.setLineNumber(1, rightLineNumber);
537             if (entry.getData(0) != null) {
538                 leftLineNumber++;
539             }
540             if (entry.getData(1) != null) {
541                 rightLineNumber++;
542             }
543         }
544     }
545 
546 }