1. Project Clover database Tue Dec 20 2016 21:24:09 CET
  2. Package org.xwiki.diff.display.internal

File DefaultUnifiedDiffDisplayer.java

 

Coverage histogram

../../../../../img/srcFileCovDistChart10.png
0% of files have more coverage

Code metrics

18
66
14
2
333
161
29
0.44
4.71
7
2.07

Classes

Class Line # Actions
DefaultUnifiedDiffDisplayer 58 61 0% 24 2
0.9772727597.7%
DefaultUnifiedDiffDisplayer.State 66 5 0% 5 0
1.0100%
 

Contributing tests

This file is covered by 15 tests. .

Source view

1    /*
2    * See the NOTICE file distributed with this work for additional
3    * information regarding copyright ownership.
4    *
5    * This is free software; you can redistribute it and/or modify it
6    * under the terms of the GNU Lesser General Public License as
7    * published by the Free Software Foundation; either version 2.1 of
8    * the License, or (at your option) any later version.
9    *
10    * This software is distributed in the hope that it will be useful,
11    * but WITHOUT ANY WARRANTY; without even the implied warranty of
12    * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13    * Lesser General Public License for more details.
14    *
15    * You should have received a copy of the GNU Lesser General Public
16    * License along with this software; if not, write to the Free
17    * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
18    * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
19    */
20    package org.xwiki.diff.display.internal;
21   
22    import java.util.ArrayList;
23    import java.util.List;
24    import java.util.Stack;
25   
26    import javax.inject.Inject;
27    import javax.inject.Singleton;
28   
29    import org.xwiki.component.annotation.Component;
30    import org.xwiki.diff.Chunk;
31    import org.xwiki.diff.Delta;
32    import org.xwiki.diff.DiffException;
33    import org.xwiki.diff.DiffManager;
34    import org.xwiki.diff.DiffResult;
35    import org.xwiki.diff.display.InlineDiffChunk;
36    import org.xwiki.diff.display.InlineDiffDisplayer;
37    import org.xwiki.diff.display.UnifiedDiffBlock;
38    import org.xwiki.diff.display.UnifiedDiffConfiguration;
39    import org.xwiki.diff.display.UnifiedDiffDisplayer;
40    import org.xwiki.diff.display.UnifiedDiffElement;
41    import org.xwiki.diff.display.UnifiedDiffElement.Type;
42   
43    /**
44    * Displays a {@link DiffResult} as a <a href="http://en.wikipedia.org/wiki/Diff#Unified_format">unified diff</a>. The
45    * unified diff consists in a sequence of blocks, each having elements marked as either added or removed, padded with
46    * unmodified elements that put changes in context.
47    * <p>
48    * NOTE: This class was greatly inspired by the <a href="http
49    * ://cvsgrab.cvs.sourceforge.net/viewvc/cvsgrab/cvsgrab/src/java/org/apache/commons/jrcs/diff/print/UnifiedPrint
50    * .java">{@code UnifiedPrint}</a> class written by <a href="mailto:ludovicc@users.sourceforge.net">Ludovic Claude</a>
51    * for the <a href="http://cvsgrab.sourceforge.net/">CVSGrab</a> project under the Apache Software License version 1.1.
52    *
53    * @version $Id: 203bf6446892c0a38e8b04f38323f87a4cd5e938 $
54    * @since 4.1RC1
55    */
56    @Component
57    @Singleton
 
58    public class DefaultUnifiedDiffDisplayer implements UnifiedDiffDisplayer
59    {
60    /**
61    * The state of the displayer.
62    *
63    * @param <E> the type of composite elements that are compared to produce the first level diff
64    * @param <F> the type of sub-elements that are compared to produce the second-level diff
65    */
 
66    private static class State<E, F>
67    {
68    /**
69    * The collection of unified diff blocks build so far.
70    */
71    private final Stack<UnifiedDiffBlock<E, F>> blocks = new Stack<UnifiedDiffBlock<E, F>>();
72   
73    /**
74    * The previous version, used to take the unmodified elements from.
75    */
76    private final List<E> previous;
77   
78    /**
79    * The last change processed by the displayer.
80    */
81    private Delta<E> lastDelta;
82   
83    /**
84    * Creates a new instance.
85    *
86    * @param previous the previous version used to take the unmodified elements from
87    */
 
88  23 toggle State(List<E> previous)
89    {
90  23 this.previous = previous;
91    }
92   
93    /**
94    * @return the last processed change
95    */
 
96  77 toggle public Delta<E> getLastDelta()
97    {
98  77 return this.lastDelta;
99    }
100   
101    /**
102    * Sets the last processed change.
103    *
104    * @param lastDelta the last processed change
105    */
 
106  24 toggle public void setLastDelta(Delta<E> lastDelta)
107    {
108  24 this.lastDelta = lastDelta;
109    }
110   
111    /**
112    * @return the collection of unified diff blocks build so far
113    */
 
114  181 toggle public Stack<UnifiedDiffBlock<E, F>> getBlocks()
115    {
116  181 return this.blocks;
117    }
118   
119    /**
120    * @return the previous version, used to take the unmodified elements from
121    */
 
122  66 toggle public List<E> getPrevious()
123    {
124  66 return this.previous;
125    }
126    }
127   
128    /**
129    * The component used to determine the second level of changes, inside a modified element.
130    */
131    @Inject
132    private DiffManager diffManager;
133   
134    /**
135    * The component used to display the second level of changes, between modified elements.
136    */
137    @Inject
138    private InlineDiffDisplayer inlineDisplayer;
139   
 
140  23 toggle @Override
141    public <E, F> UnifiedDiffConfiguration<E, F> getDefaultConfiguration()
142    {
143  23 return new UnifiedDiffConfiguration<E, F>();
144    }
145   
 
146  9 toggle @Override
147    public <E, F> List<UnifiedDiffBlock<E, F>> display(DiffResult<E> diffResult)
148    {
149  9 return display(diffResult, this.<E, F>getDefaultConfiguration());
150    }
151   
 
152  23 toggle @Override
153    public <E, F> List<UnifiedDiffBlock<E, F>> display(DiffResult<E> diffResult, UnifiedDiffConfiguration<E, F> config)
154    {
155  23 State<E, F> state = new State<E, F>(diffResult.getPrevious());
156   
157  23 for (Delta<E> delta : diffResult.getPatch()) {
158    // Add unmodified elements before the current delta. Start a new block if the distance between the current
159    // delta and the last one is greater than or equal to 2 * context size.
160  24 maybeStartBlock(delta, state, config.getContextSize());
161   
162    // Add changed elements.
163  24 switch (delta.getType()) {
164  17 case CHANGE:
165  17 state.getBlocks().peek().addAll(this.<E, F>getModifiedElements(delta, config));
166  17 break;
167  3 case DELETE:
168  3 state.getBlocks().peek().addAll(this.<E, F>getElements(delta.getPrevious(), Type.DELETED));
169  3 break;
170  4 case INSERT:
171  4 state.getBlocks().peek().addAll(this.<E, F>getElements(delta.getNext(), Type.ADDED));
172  4 break;
173  0 default:
174  0 break;
175    }
176   
177  24 state.setLastDelta(delta);
178    }
179   
180    // Add unmodified elements after the last delta.
181  23 maybeEndBlock(state, config.getContextSize());
182   
183  23 return state.getBlocks();
184    }
185   
186    /**
187    * Starts a new {@link UnifiedDiffBlock} if the provided change is in a different context. The distance between two
188    * changes inside the same block is less than 2 * context size.
189    *
190    * @param delta the change
191    * @param state the state of the displayer
192    * @param contextSize the number of unmodified elements to display before and after each change
193    * @param <E> the type of composite elements that are compared to produce the first level diff
194    * @param <F> the type of sub-elements that are compared to produce the second-level diff when a composite element
195    * is modified
196    */
 
197  24 toggle private <E, F> void maybeStartBlock(Delta<E> delta, State<E, F> state, int contextSize)
198    {
199  24 if (state.getLastDelta() == null
200    || state.getLastDelta().getPrevious().getLastIndex() < delta.getPrevious().getIndex() - contextSize * 2) {
201  21 maybeEndBlock(state, contextSize);
202  21 state.getBlocks().push(new UnifiedDiffBlock<E, F>());
203    }
204   
205    // Add the unmodified elements before the given delta.
206  24 int count = state.getBlocks().peek().isEmpty() ? contextSize : contextSize * 2;
207  24 int lastChangeIndex = state.getLastDelta() == null ? -1 : state.getLastDelta().getPrevious().getLastIndex();
208  24 int end = delta.getPrevious().getIndex();
209  24 int start = Math.max(end - count, lastChangeIndex + 1);
210  24 state.getBlocks().peek().addAll(this.<E, F>getUnmodifiedElements(state.getPrevious(), start, end));
211    }
212   
213    /**
214    * Processes a change. In a unified diff the modified elements are either added or removed so we model a change by
215    * listing the removed elements (from the previous version) followed by the added elements (from the next version).
216    * If a splitter is provided through the given configuration object then we use it to split the changed elements (if
217    * the number of removed elements equals the number of added elements) in sub-elements and produce an in-line diff
218    * for the changes inside the modified elements.
219    *
220    * @param delta the change
221    * @param config the configuration used to access the splitter
222    * @param <E> the type of composite elements that are compared to produce the first level diff
223    * @param <F> the type of sub-elements that are compared to produce the second-level diff when a composite element
224    * is modified
225    * @return the list of unified diff elements corresponding to the elements modified in the given delta
226    */
 
227  17 toggle private <E, F> List<UnifiedDiffElement<E, F>> getModifiedElements(Delta<E> delta,
228    UnifiedDiffConfiguration<E, F> config)
229    {
230  17 List<UnifiedDiffElement<E, F>> elements = new ArrayList<UnifiedDiffElement<E, F>>();
231  17 elements.addAll(this.<E, F>getElements(delta.getPrevious(), Type.DELETED));
232  17 elements.addAll(this.<E, F>getElements(delta.getNext(), Type.ADDED));
233   
234    // Compute the in-line diff if the number of removed elements equals the number of added elements.
235  17 if (config.getSplitter() != null && delta.getPrevious().size() == delta.getNext().size()) {
236  11 int changeSize = delta.getPrevious().size();
237  24 for (int i = 0; i < changeSize; i++) {
238  13 displayInlineDiff(elements.get(i), elements.get(changeSize + i), config);
239    }
240    }
241   
242  17 return elements;
243    }
244   
245    /**
246    * @param chunk the modified elements (both added and deleted)
247    * @param changeType the change type
248    * @param <E> the type of composite elements that are compared to produce the first level diff
249    * @param <F> the type of sub-elements that are compared to produce the second-level diff when a composite element
250    * is modified
251    * @return the list of corresponding unified diff elements, matching the change type
252    */
 
253  41 toggle private <E, F> List<UnifiedDiffElement<E, F>> getElements(Chunk<E> chunk, Type changeType)
254    {
255  41 int index = chunk.getIndex();
256  41 List<UnifiedDiffElement<E, F>> elements = new ArrayList<UnifiedDiffElement<E, F>>();
257  41 for (E element : chunk.getElements()) {
258  52 elements.add(new UnifiedDiffElement<E, F>(index++, changeType, element));
259    }
260  41 return elements;
261    }
262   
263    /**
264    * @param previous the previous version
265    * @param start the index of the first unmodified element to return
266    * @param end the index to stop at
267    * @param <E> the type of composite elements that are compared to produce the first level diff
268    * @param <F> the type of sub-elements that are compared to produce the second-level diff when a composite element
269    * is modified
270    * @return the list of unmodified elements between the given start and end index
271    */
 
272  45 toggle private <E, F> List<UnifiedDiffElement<E, F>> getUnmodifiedElements(List<E> previous, int start, int end)
273    {
274  45 List<UnifiedDiffElement<E, F>> unmodifiedElements = new ArrayList<UnifiedDiffElement<E, F>>();
275  97 for (int i = start; i < end; i++) {
276  52 unmodifiedElements.add(new UnifiedDiffElement<E, F>(i, Type.CONTEXT, previous.get(i)));
277    }
278  45 return unmodifiedElements;
279    }
280   
281    /**
282    * Ends the last {@link UnifiedDiffBlock} by adding a number of unmodified elements.
283    *
284    * @param state the state of the displayer
285    * @param contextSize the number of unmodified elements to display at the end of a block
286    * @param <E> the type of composite elements that are compared to produce the first level diff
287    * @param <F> the type of sub-elements that are compared to produce the second-level diff when a composite element
288    * is modified
289    */
 
290  44 toggle private <E, F> void maybeEndBlock(State<E, F> state, int contextSize)
291    {
292  44 if (!state.getBlocks().isEmpty()) {
293  21 int start = state.getLastDelta().getPrevious().getLastIndex() + 1;
294  21 int end = Math.min(start + contextSize, state.getPrevious().size());
295  21 state.getBlocks().peek().addAll(this.<E, F>getUnmodifiedElements(state.getPrevious(), start, end));
296    }
297    }
298   
299    /**
300    * Computes the changes between two versions of an element by splitting the element into sub-elements and displays
301    * the result using the in-line format.
302    *
303    * @param previous the previous version
304    * @param next the next version version
305    * @param config the configuration for the in-line diff
306    * @param <E> the type of composite elements that are compared to produce the first level diff
307    * @param <F> the type of sub-elements that are compared to produce the second-level diff when a composite element
308    * is modified
309    */
 
310  13 toggle private <E, F> void displayInlineDiff(UnifiedDiffElement<E, F> previous, UnifiedDiffElement<E, F> next,
311    UnifiedDiffConfiguration<E, F> config)
312    {
313  13 try {
314  13 List<F> previousSubElements = config.getSplitter().split(previous.getValue());
315  13 List<F> nextSubElements = config.getSplitter().split(next.getValue());
316  13 DiffResult<F> diffResult = this.diffManager.diff(previousSubElements, nextSubElements, config);
317   
318  13 List<InlineDiffChunk<F>> chunks = this.inlineDisplayer.display(diffResult);
319  13 previous.setChunks(new ArrayList<InlineDiffChunk<F>>());
320  13 next.setChunks(new ArrayList<InlineDiffChunk<F>>());
321  13 for (InlineDiffChunk<F> chunk : chunks) {
322  54 if (!chunk.isAdded()) {
323  35 previous.getChunks().add(chunk);
324    }
325  54 if (!chunk.isDeleted()) {
326  44 next.getChunks().add(chunk);
327    }
328    }
329    } catch (DiffException e) {
330    // Do nothing.
331    }
332    }
333    }