[devkit] make `UElementAsPsiInspection` report on return values
[idea/community.git] / platform / diff-impl / src / com / intellij / diff / tools / fragmented / UnifiedFragmentBuilder.java
1 /*
2  * Copyright 2000-2015 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 com.intellij.diff.tools.fragmented;
17
18 import com.intellij.diff.fragments.LineFragment;
19 import com.intellij.diff.util.LineRange;
20 import com.intellij.diff.util.Side;
21 import com.intellij.openapi.editor.Document;
22 import com.intellij.openapi.util.TextRange;
23 import org.jetbrains.annotations.NotNull;
24
25 import java.util.ArrayList;
26 import java.util.List;
27
28 public class UnifiedFragmentBuilder {
29   @NotNull protected final List<? extends LineFragment> myFragments;
30   @NotNull private final Document myDocument1;
31   @NotNull private final Document myDocument2;
32   @NotNull private final Side myMasterSide;
33
34   @NotNull private final StringBuilder myBuilder = new StringBuilder();
35   @NotNull private final List<UnifiedDiffChange> myChanges = new ArrayList<>();
36   @NotNull private final List<HighlightRange> myRanges = new ArrayList<>();
37   @NotNull private final LineNumberConvertor.Builder myConvertor1 = new LineNumberConvertor.Builder();
38   @NotNull private final LineNumberConvertor.Builder myConvertor2 = new LineNumberConvertor.Builder();
39   @NotNull private final List<LineRange> myChangedLines = new ArrayList<>();
40
41   public UnifiedFragmentBuilder(@NotNull List<? extends LineFragment> fragments,
42                                 @NotNull Document document1,
43                                 @NotNull Document document2,
44                                 @NotNull Side masterSide) {
45     myFragments = fragments;
46     myDocument1 = document1;
47     myDocument2 = document2;
48     myMasterSide = masterSide;
49   }
50
51   private int lastProcessedLine1 = -1;
52   private int lastProcessedLine2 = -1;
53   private int totalLines = 0;
54
55   @NotNull
56   public Side getMasterSide() {
57     return myMasterSide;
58   }
59
60   public UnifiedFragmentBuilder exec() {
61     if (myFragments.isEmpty()) {
62       appendTextMaster(0, 0, getLineCount(myDocument1) - 1, getLineCount(myDocument2) - 1);
63       return this;
64     }
65
66     for (int i = 0; i < myFragments.size(); i++) {
67       LineFragment fragment = myFragments.get(i);
68       processEquals(fragment.getStartLine1() - 1, fragment.getStartLine2() - 1);
69       processChanged(fragment, i);
70     }
71     processEquals(getLineCount(myDocument1) - 1, getLineCount(myDocument2) - 1);
72
73     return this;
74   }
75
76   private void processEquals(int endLine1, int endLine2) {
77     int startLine1 = lastProcessedLine1 + 1;
78     int startLine2 = lastProcessedLine2 + 1;
79
80     appendTextMaster(startLine1, startLine2, endLine1, endLine2);
81   }
82
83   private void processChanged(@NotNull LineFragment fragment, int fragmentIndex) {
84     int startLine1 = fragment.getStartLine1();
85     int endLine1 = fragment.getEndLine1() - 1;
86     int lines1 = endLine1 - startLine1;
87
88     int startLine2 = fragment.getStartLine2();
89     int endLine2 = fragment.getEndLine2() - 1;
90     int lines2 = endLine2 - startLine2;
91
92     int linesBefore = totalLines;
93     int linesAfter;
94
95     if (lines1 >= 0) {
96       int startOffset1 = myDocument1.getLineStartOffset(startLine1);
97       int endOffset1 = myDocument1.getLineEndOffset(endLine1);
98       appendText(Side.LEFT, startOffset1, endOffset1, lines1, lines2, startLine1, -1);
99     }
100
101     int linesBetween = totalLines;
102
103     if (lines2 >= 0) {
104       int startOffset2 = myDocument2.getLineStartOffset(startLine2);
105       int endOffset2 = myDocument2.getLineEndOffset(endLine2);
106       appendText(Side.RIGHT, startOffset2, endOffset2, lines2, lines2, -1, startLine2);
107     }
108
109     linesAfter = totalLines;
110
111     UnifiedDiffChange change = createDiffChange(linesBefore, linesBetween, linesAfter, fragmentIndex);
112     myChanges.add(change);
113     if (!change.isSkipped()) {
114       myChangedLines.add(new LineRange(linesBefore, linesAfter));
115     }
116
117     lastProcessedLine1 = endLine1;
118     lastProcessedLine2 = endLine2;
119   }
120
121   @NotNull
122   protected UnifiedDiffChange createDiffChange(int blockStart,
123                                                int insertedStart,
124                                                int blockEnd,
125                                                int fragmentIndex) {
126     return new UnifiedDiffChange(blockStart, insertedStart, blockEnd, myFragments.get(fragmentIndex));
127   }
128
129   private void appendTextMaster(int startLine1, int startLine2, int endLine1, int endLine2) {
130     // The slave-side line matching might be incomplete for non-fair line fragments (@see FairDiffIterable)
131     // If it ever became an issue, it could be fixed by explicit fair by-line comparing of "equal" regions
132
133     int lines1 = endLine1 - startLine1;
134     int lines2 = endLine2 - startLine2;
135     if (myMasterSide.select(lines1, lines2) >= 0) {
136       int startOffset = myMasterSide.isLeft() ? myDocument1.getLineStartOffset(startLine1) : myDocument2.getLineStartOffset(startLine2);
137       int endOffset = myMasterSide.isLeft() ? myDocument1.getLineEndOffset(endLine1) : myDocument2.getLineEndOffset(endLine2);
138
139       appendText(myMasterSide, startOffset, endOffset, lines1, lines2, startLine1, startLine2);
140     }
141   }
142
143   private void appendText(@NotNull Side side, int offset1, int offset2, int lines1, int lines2, int startLine1, int startLine2) {
144     int lines = side.select(lines1, lines2);
145     boolean notEmpty = lines >= 0;
146
147     int appendix = notEmpty ? 1 : 0;
148     if (startLine1 != -1) {
149       myConvertor1.put(totalLines, startLine1, lines + appendix, lines1 + appendix);
150     }
151     if (startLine2 != -1) {
152       myConvertor2.put(totalLines, startLine2, lines + appendix, lines2 + appendix);
153     }
154
155     if (notEmpty) {
156       Document document = side.select(myDocument1, myDocument2);
157
158       int newline = document.getTextLength() > offset2 + 1 ? 1 : 0;
159       TextRange base = new TextRange(myBuilder.length(), myBuilder.length() + offset2 - offset1 + newline);
160       TextRange changed = new TextRange(offset1, offset2 + newline);
161       myRanges.add(new HighlightRange(side, base, changed));
162
163       myBuilder.append(document.getCharsSequence().subSequence(offset1, offset2));
164       myBuilder.append('\n');
165
166       totalLines += lines + 1;
167     }
168   }
169
170   private static int getLineCount(@NotNull Document document) {
171     return Math.max(document.getLineCount(), 1);
172   }
173
174   //
175   // Result
176   //
177
178   @NotNull
179   public CharSequence getText() {
180     return myBuilder;
181   }
182
183   @NotNull
184   public List<UnifiedDiffChange> getChanges() {
185     return myChanges;
186   }
187
188   @NotNull
189   public List<HighlightRange> getRanges() {
190     return myRanges;
191   }
192
193   @NotNull
194   public LineNumberConvertor getConvertor1() {
195     return myConvertor1.build();
196   }
197
198   @NotNull
199   public LineNumberConvertor getConvertor2() {
200     return myConvertor2.build();
201   }
202
203   @NotNull
204   public List<LineRange> getChangedLines() {
205     return myChangedLines;
206   }
207 }