1
2
3
4
5
6
7
8
9
10 package de.funfried.netbeans.plugins.external.formatter;
11
12 import java.io.IOException;
13 import java.io.StringReader;
14 import java.util.SortedSet;
15 import java.util.TreeSet;
16 import java.util.logging.Level;
17 import java.util.logging.Logger;
18
19 import javax.swing.text.BadLocationException;
20 import javax.swing.text.StyledDocument;
21
22 import org.apache.commons.collections4.CollectionUtils;
23 import org.apache.commons.lang3.mutable.MutableInt;
24 import org.apache.commons.lang3.tuple.Pair;
25 import org.netbeans.api.annotations.common.NonNull;
26 import org.netbeans.api.diff.Difference;
27 import org.netbeans.api.editor.guards.GuardedSection;
28 import org.netbeans.api.editor.guards.GuardedSectionManager;
29 import org.netbeans.lib.editor.util.swing.DocumentUtilities;
30 import org.openide.text.NbDocument;
31
32 import de.funfried.netbeans.plugins.external.formatter.ui.editor.diff.Diff;
33
34
35
36
37
38
39
40 public abstract class AbstractFormatJob implements FormatJob {
41
42 private static final Logger log = Logger.getLogger(AbstractFormatJob.class.getName());
43
44
45 private static final Level logLevel = Level.FINER;
46
47
48 protected final SortedSet<Pair<Integer, Integer>> changedElements;
49
50
51 protected final StyledDocument document;
52
53
54
55
56
57
58
59 protected AbstractFormatJob(StyledDocument document, SortedSet<Pair<Integer, Integer>> changedElements) {
60 this.document = document;
61 this.changedElements = changedElements;
62 }
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77 protected boolean setFormattedCode(String code, String formattedContent) throws BadLocationException {
78
79 if (formattedContent != null && !formattedContent.equals(code)) {
80 try (StringReader original = new StringReader(code);
81 StringReader formatted = new StringReader(formattedContent)) {
82 Difference[] differences = Diff.diff(original, formatted);
83 if (differences != null && differences.length != 0) {
84 if (log.isLoggable(logLevel)) {
85 log.log(logLevel, "Unformatted: ''{0}''", code);
86 log.log(logLevel, "Formatted: ''{0}''", formattedContent);
87 }
88
89 for (Difference d : differences) {
90 int startLine = d.getSecondStart();
91
92 switch (d.getType()) {
93 case Difference.ADD: {
94 int start = NbDocument.findLineOffset(document, startLine - 1);
95 String addText = d.getSecondText();
96
97 if (log.isLoggable(logLevel)) {
98 log.log(logLevel, "ADD: {0} / Line {1}: {2}", new Object[] { start, startLine, addText });
99 }
100
101 document.insertString(start, addText, null);
102
103 break;
104 }
105 case Difference.CHANGE: {
106 int start = NbDocument.findLineOffset(document, startLine - 1);
107 String removeText = d.getFirstText();
108 int length = removeText.length();
109
110
111
112 length = Math.min(length, document.getLength());
113
114 String addText = d.getSecondText();
115
116 if (log.isLoggable(logLevel)) {
117 log.log(logLevel, "CHANGE: {0} - {1} / Line {2}: ''{3}'' <= ''{4}''", new Object[] { start, length, startLine, addText, removeText });
118 }
119
120 document.remove(start, length);
121 document.insertString(start, addText, null);
122
123 break;
124 }
125 case Difference.DELETE: {
126 int start = NbDocument.findLineOffset(document, startLine);
127 String removeText = d.getFirstText();
128 int length = removeText.length();
129
130
131
132 length = Math.min(length, document.getLength());
133
134 if (log.isLoggable(logLevel)) {
135 log.log(logLevel, "DELETE: {0} - {1} / Line {2}: ''{3}''", new Object[] { start, length, startLine, removeText });
136 }
137
138 document.remove(start, length);
139
140 break;
141 }
142 }
143 }
144
145 return true;
146 }
147 } catch (IOException ex) {
148 log.log(Level.WARNING, "Could not create diff", ex);
149 }
150 }
151
152 return false;
153 }
154
155
156
157
158
159
160 protected String getCode() {
161 try {
162
163 return document.getText(0, document.getLength());
164 } catch (BadLocationException ex) {
165 log.log(Level.WARNING, "Could not fetch text, falling back to utility method", ex);
166
167
168 return DocumentUtilities.getText(document).toString();
169 }
170 }
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185 @NonNull
186 protected SortedSet<Pair<Integer, Integer>> getFormatableSections(String code) {
187 SortedSet<Pair<Integer, Integer>> regions = changedElements;
188 if (CollectionUtils.isEmpty(changedElements)) {
189 regions = new TreeSet<>();
190
191 regions.add(Pair.of(0, code.length() - 1));
192 }
193
194 GuardedSectionManager guards = GuardedSectionManager.getInstance(document);
195 if (guards != null) {
196 SortedSet<Pair<Integer, Integer>> nonGuardedSections = new TreeSet<>();
197 Iterable<GuardedSection> guardedSections = guards.getGuardedSections();
198
199 if (log.isLoggable(logLevel)) {
200 {
201 StringBuilder sb = new StringBuilder();
202 regions.stream().forEach(section -> sb.append(section.getLeft()).append("/").append(section.getRight()).append(" "));
203 log.log(logLevel, "Formatting sections before guards: {0}", sb.toString().trim());
204 }
205
206 {
207 StringBuilder sb = new StringBuilder();
208 guardedSections.forEach(guard -> sb.append(guard.getStartPosition().getOffset()).append("/").append(guard.getEndPosition().getOffset()).append(" "));
209 log.log(logLevel, "Guarded sections: {0}", sb.toString().trim());
210 }
211 }
212
213 for (Pair<Integer, Integer> changedElement : regions) {
214 nonGuardedSections.addAll(avoidGuardedSection(changedElement, guardedSections));
215 }
216
217 regions = nonGuardedSections;
218 }
219
220 if (log.isLoggable(logLevel)) {
221 StringBuilder sb = new StringBuilder();
222 regions.stream().forEach(section -> sb.append(section.getLeft()).append("/").append(section.getRight()).append(" "));
223 log.log(logLevel, "Formatting sections: {0}", sb.toString().trim());
224 }
225
226 return regions;
227 }
228
229
230
231
232
233
234
235
236
237
238
239
240
241 protected SortedSet<Pair<Integer, Integer>> avoidGuardedSection(Pair<Integer, Integer> section, Iterable<GuardedSection> guardedSections) {
242 SortedSet<Pair<Integer, Integer>> ret = new TreeSet<>();
243
244 MutableInt start = new MutableInt(section.getLeft());
245 MutableInt end = new MutableInt(section.getRight());
246
247 if (guardedSections != null) {
248 try {
249 guardedSections.forEach(guardedSection -> {
250 if (start.getValue() >= guardedSection.getStartPosition().getOffset() && start.getValue() <= guardedSection.getEndPosition().getOffset()) {
251 if (end.getValue() > guardedSection.getEndPosition().getOffset()) {
252 start.setValue(guardedSection.getEndPosition().getOffset());
253 } else {
254 start.setValue(-1);
255 end.setValue(-1);
256
257 throw new BreakException();
258 }
259 } else if (end.getValue() >= guardedSection.getStartPosition().getOffset() && end.getValue() <= guardedSection.getEndPosition().getOffset()) {
260 if (start.getValue() < guardedSection.getStartPosition().getOffset()) {
261 end.setValue(guardedSection.getStartPosition().getOffset() - 1);
262 } else {
263 start.setValue(-1);
264 end.setValue(-1);
265
266 throw new BreakException();
267 }
268 } else if (start.getValue() < guardedSection.getStartPosition().getOffset() && end.getValue() > guardedSection.getEndPosition().getOffset()) {
269 ret.add(Pair.of(start.getValue(), guardedSection.getStartPosition().getOffset() - 1));
270
271 start.setValue(guardedSection.getEndPosition().getOffset());
272 }
273 });
274 } catch (BreakException ex) {
275
276 }
277 }
278
279 if (start.getValue() > -1 && end.getValue() > -1) {
280 ret.add(Pair.of(start.getValue(), end.getValue()));
281 }
282
283 return ret;
284 }
285
286
287
288
289
290 protected static class BreakException extends RuntimeException {
291 private static final long serialVersionUID = 1L;
292 }
293 }