/platform/testFramework/src/com/intellij/testFramework/ExpectedHighlightingData.java

https://bitbucket.org/nbargnesi/idea · Java · 588 lines · 485 code · 74 blank · 29 comment · 101 complexity · 96942bf7b5e5af4df7fe8a94ade168d3 MD5 · raw file

  1. /*
  2. * Copyright 2000-2012 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. /**
  17. * @author cdr
  18. */
  19. package com.intellij.testFramework;
  20. import com.intellij.codeHighlighting.Pass;
  21. import com.intellij.codeInsight.daemon.LineMarkerInfo;
  22. import com.intellij.codeInsight.daemon.impl.HighlightInfo;
  23. import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
  24. import com.intellij.codeInsight.daemon.impl.SeveritiesProvider;
  25. import com.intellij.lang.annotation.HighlightSeverity;
  26. import com.intellij.openapi.application.ApplicationManager;
  27. import com.intellij.openapi.command.WriteCommandAction;
  28. import com.intellij.openapi.diagnostic.Logger;
  29. import com.intellij.openapi.editor.Document;
  30. import com.intellij.openapi.editor.RangeMarker;
  31. import com.intellij.openapi.editor.colors.TextAttributesKey;
  32. import com.intellij.openapi.editor.markup.EffectType;
  33. import com.intellij.openapi.editor.markup.GutterIconRenderer;
  34. import com.intellij.openapi.editor.markup.TextAttributes;
  35. import com.intellij.openapi.extensions.Extensions;
  36. import com.intellij.openapi.util.Comparing;
  37. import com.intellij.openapi.util.Pair;
  38. import com.intellij.openapi.util.Ref;
  39. import com.intellij.openapi.util.TextRange;
  40. import com.intellij.openapi.util.text.StringUtil;
  41. import com.intellij.psi.PsiElement;
  42. import com.intellij.psi.PsiFile;
  43. import com.intellij.rt.execution.junit.FileComparisonFailure;
  44. import com.intellij.util.ConstantFunction;
  45. import com.intellij.util.Function;
  46. import com.intellij.util.NullableFunction;
  47. import com.intellij.util.containers.ContainerUtil;
  48. import gnu.trove.THashMap;
  49. import gnu.trove.THashSet;
  50. import junit.framework.Assert;
  51. import org.jetbrains.annotations.NonNls;
  52. import org.jetbrains.annotations.NotNull;
  53. import org.jetbrains.annotations.Nullable;
  54. import java.awt.*;
  55. import java.lang.reflect.Field;
  56. import java.util.*;
  57. import java.util.List;
  58. import java.util.regex.Matcher;
  59. import java.util.regex.Pattern;
  60. public class ExpectedHighlightingData {
  61. private static final Logger LOG = Logger.getInstance("#com.intellij.testFramework.ExpectedHighlightingData");
  62. @NonNls private static final String ERROR_MARKER = "error";
  63. @NonNls private static final String WARNING_MARKER = "warning";
  64. @NonNls private static final String WEAK_WARNING_MARKER = "weak_warning";
  65. @NonNls private static final String INFO_MARKER = "info";
  66. @NonNls private static final String END_LINE_HIGHLIGHT_MARKER = "EOLError";
  67. @NonNls private static final String END_LINE_WARNING_MARKER = "EOLWarning";
  68. @NonNls private static final String LINE_MARKER = "lineMarker";
  69. @NotNull private final Document myDocument;
  70. private final PsiFile myFile;
  71. @NonNls private static final String ANY_TEXT = "*";
  72. private final String myText;
  73. public static class ExpectedHighlightingSet {
  74. private final HighlightSeverity severity;
  75. private final boolean endOfLine;
  76. private final boolean enabled;
  77. private final Set<HighlightInfo> infos;
  78. public ExpectedHighlightingSet(@NotNull HighlightSeverity severity, boolean endOfLine, boolean enabled) {
  79. this.severity = severity;
  80. this.endOfLine = endOfLine;
  81. this.enabled = enabled;
  82. infos = new THashSet<HighlightInfo>();
  83. }
  84. }
  85. @SuppressWarnings("WeakerAccess")
  86. protected final Map<String,ExpectedHighlightingSet> highlightingTypes;
  87. private final Map<RangeMarker, LineMarkerInfo> lineMarkerInfos = new THashMap<RangeMarker, LineMarkerInfo>();
  88. public void init() {
  89. ApplicationManager.getApplication().runWriteAction(new Runnable() {
  90. public void run() {
  91. extractExpectedLineMarkerSet(myDocument);
  92. extractExpectedHighlightsSet(myDocument);
  93. refreshLineMarkers();
  94. }
  95. });
  96. }
  97. public ExpectedHighlightingData(@NotNull Document document,boolean checkWarnings, boolean checkInfos) {
  98. this(document, checkWarnings, false, checkInfos);
  99. }
  100. public ExpectedHighlightingData(@NotNull Document document,
  101. boolean checkWarnings,
  102. boolean checkWeakWarnings,
  103. boolean checkInfos) {
  104. this(document, checkWarnings, checkWeakWarnings, checkInfos, null);
  105. }
  106. public ExpectedHighlightingData(@NotNull final Document document, PsiFile file) {
  107. myDocument = document;
  108. myFile = file;
  109. myText = document.getText();
  110. highlightingTypes = new LinkedHashMap<String, ExpectedHighlightingSet>();
  111. new WriteCommandAction.Simple(file == null ? null : file.getProject()) {
  112. public void run() {
  113. boolean checkWarnings = false;
  114. boolean checkWeakWarnings = false;
  115. boolean checkInfos = false;
  116. highlightingTypes.put(ERROR_MARKER, new ExpectedHighlightingSet(HighlightSeverity.ERROR, false, true));
  117. highlightingTypes.put(WARNING_MARKER, new ExpectedHighlightingSet(HighlightSeverity.WARNING, false, checkWarnings));
  118. highlightingTypes.put(WEAK_WARNING_MARKER, new ExpectedHighlightingSet(HighlightSeverity.WEAK_WARNING, false, checkWeakWarnings));
  119. highlightingTypes.put("inject", new ExpectedHighlightingSet(HighlightInfoType.INJECTED_FRAGMENT_SEVERITY, false, checkInfos));
  120. highlightingTypes.put(INFO_MARKER, new ExpectedHighlightingSet(HighlightSeverity.INFORMATION, false, checkInfos));
  121. highlightingTypes.put("symbolName", new ExpectedHighlightingSet(HighlightInfoType.SYMBOL_TYPE_SEVERITY, false, false));
  122. for (SeveritiesProvider provider : Extensions.getExtensions(SeveritiesProvider.EP_NAME)) {
  123. for (HighlightInfoType type : provider.getSeveritiesHighlightInfoTypes()) {
  124. final HighlightSeverity severity = type.getSeverity(null);
  125. highlightingTypes.put(severity.toString(), new ExpectedHighlightingSet(severity, false, true));
  126. }
  127. }
  128. highlightingTypes.put(END_LINE_HIGHLIGHT_MARKER, new ExpectedHighlightingSet(HighlightSeverity.ERROR, true, true));
  129. highlightingTypes.put(END_LINE_WARNING_MARKER, new ExpectedHighlightingSet(HighlightSeverity.WARNING, true, checkWarnings));
  130. initAdditionalHighlightingTypes();
  131. }
  132. }.execute().throwException();
  133. }
  134. public ExpectedHighlightingData(@NotNull final Document document,
  135. final boolean checkWarnings,
  136. final boolean checkWeakWarnings,
  137. final boolean checkInfos,
  138. @Nullable final PsiFile file) {
  139. this(document, file);
  140. if (checkWarnings) checkWarnings();
  141. if (checkWeakWarnings) checkWeakWarnings();
  142. if (checkInfos) checkInfos();
  143. }
  144. public void checkWarnings() {
  145. highlightingTypes.put(WARNING_MARKER, new ExpectedHighlightingSet(HighlightSeverity.WARNING, false, true));
  146. highlightingTypes.put(END_LINE_WARNING_MARKER, new ExpectedHighlightingSet(HighlightSeverity.WARNING, true, true));
  147. }
  148. public void checkWeakWarnings() {
  149. highlightingTypes.put(WEAK_WARNING_MARKER, new ExpectedHighlightingSet(HighlightSeverity.WEAK_WARNING, false, true));
  150. }
  151. public void checkInfos() {
  152. highlightingTypes.put(INFO_MARKER, new ExpectedHighlightingSet(HighlightSeverity.INFORMATION, false, true));
  153. highlightingTypes.put("inject", new ExpectedHighlightingSet(HighlightInfoType.INJECTED_FRAGMENT_SEVERITY, false, true));
  154. }
  155. public void checkSymbolNames() {
  156. highlightingTypes.put("symbolName", new ExpectedHighlightingSet(HighlightInfoType.SYMBOL_TYPE_SEVERITY, false, true));
  157. }
  158. private void refreshLineMarkers() {
  159. for (Map.Entry<RangeMarker, LineMarkerInfo> entry : lineMarkerInfos.entrySet()) {
  160. RangeMarker rangeMarker = entry.getKey();
  161. int startOffset = rangeMarker.getStartOffset();
  162. int endOffset = rangeMarker.getEndOffset();
  163. final LineMarkerInfo value = entry.getValue();
  164. LineMarkerInfo markerInfo = new LineMarkerInfo<PsiElement>(value.getElement(), new TextRange(startOffset,endOffset), null, value.updatePass, new Function<PsiElement,String>() {
  165. @Override
  166. public String fun(PsiElement psiElement) {
  167. return value.getLineMarkerTooltip();
  168. }
  169. }, null, GutterIconRenderer.Alignment.RIGHT);
  170. entry.setValue(markerInfo);
  171. }
  172. }
  173. private void extractExpectedLineMarkerSet(Document document) {
  174. String text = document.getText();
  175. @NonNls String pat = ".*?((<" + LINE_MARKER + ")(?: descr=\"((?:[^\"\\\\]|\\\\\")*)\")?>)(.*)";
  176. final Pattern p = Pattern.compile(pat, Pattern.DOTALL);
  177. final Pattern pat2 = Pattern.compile("(.*?)(</" + LINE_MARKER + ">)(.*)", Pattern.DOTALL);
  178. while (true) {
  179. Matcher m = p.matcher(text);
  180. if (!m.matches()) break;
  181. int startOffset = m.start(1);
  182. final String descr = m.group(3) != null ? m.group(3) : ANY_TEXT;
  183. String rest = m.group(4);
  184. document.replaceString(startOffset, m.end(1), "");
  185. final Matcher matcher2 = pat2.matcher(rest);
  186. LOG.assertTrue(matcher2.matches(), "Cannot find closing </" + LINE_MARKER + ">");
  187. String content = matcher2.group(1);
  188. int endOffset = startOffset + matcher2.start(3);
  189. String endTag = matcher2.group(2);
  190. document.replaceString(startOffset, endOffset, content);
  191. endOffset -= endTag.length();
  192. LineMarkerInfo markerInfo = new LineMarkerInfo<PsiElement>(myFile, new TextRange(startOffset, endOffset), null, Pass.LINE_MARKERS,
  193. new ConstantFunction<PsiElement, String>(descr), null,
  194. GutterIconRenderer.Alignment.RIGHT);
  195. lineMarkerInfos.put(document.createRangeMarker(startOffset, endOffset), markerInfo);
  196. text = document.getText();
  197. }
  198. }
  199. /**
  200. * Override in order to register special highlighting
  201. */
  202. protected void initAdditionalHighlightingTypes() {}
  203. /**
  204. * remove highlights (bounded with <marker>...</marker>) from test case file
  205. * @param document document to process
  206. */
  207. private void extractExpectedHighlightsSet(final Document document) {
  208. final String text = document.getText();
  209. final Set<String> markers = highlightingTypes.keySet();
  210. final String typesRx = "(?:" + StringUtil.join(markers, ")|(?:") + ")";
  211. final String openingTagRx = "<(" + typesRx + ")" +
  212. "(?:\\s+descr=\"((?:[^\"]|\\\\\"|\\\\\\\\\"|\\\\\\[|\\\\\\])*)\")?" +
  213. "(?:\\s+type=\"([0-9A-Z_]+)\")?" +
  214. "(?:\\s+foreground=\"([0-9xa-f]+)\")?" +
  215. "(?:\\s+background=\"([0-9xa-f]+)\")?" +
  216. "(?:\\s+effectcolor=\"([0-9xa-f]+)\")?" +
  217. "(?:\\s+effecttype=\"([A-Z]+)\")?" +
  218. "(?:\\s+fonttype=\"([0-9]+)\")?" +
  219. "(?:\\s+textAttributesKey=\"((?:[^\"]|\\\\\"|\\\\\\\\\"|\\\\\\[|\\\\\\])*)\")?" +
  220. "(/)?>";
  221. final Matcher matcher = Pattern.compile(openingTagRx).matcher(text);
  222. int pos = 0;
  223. final Ref<Integer> textOffset = Ref.create(0);
  224. while (matcher.find(pos)) {
  225. textOffset.set(textOffset.get() + matcher.start() - pos);
  226. pos = extractExpectedHighlight(matcher, text, document, textOffset);
  227. }
  228. }
  229. private int extractExpectedHighlight(final Matcher matcher, final String text, final Document document, final Ref<Integer> textOffset) {
  230. document.deleteString(textOffset.get(), textOffset.get() + matcher.end() - matcher.start());
  231. int groupIdx = 1;
  232. final String marker = matcher.group(groupIdx++);
  233. @NonNls String descr = matcher.group(groupIdx++);
  234. final String typeString = matcher.group(groupIdx++);
  235. final String foregroundColor = matcher.group(groupIdx++);
  236. final String backgroundColor = matcher.group(groupIdx++);
  237. final String effectColor = matcher.group(groupIdx++);
  238. final String effectType = matcher.group(groupIdx++);
  239. final String fontType = matcher.group(groupIdx++);
  240. final String attrKey = matcher.group(groupIdx++);
  241. final boolean closed = matcher.group(groupIdx) != null;
  242. if (descr == null) {
  243. descr = ANY_TEXT; // no descr means any string by default
  244. }
  245. else if (descr.equals("null")) {
  246. descr = null; // explicit "null" descr
  247. }
  248. if (descr != null) {
  249. descr = descr.replaceAll("\\\\\\\\\"", "\""); // replace: \\" to ", doesn't check symbol before sequence \\"
  250. }
  251. HighlightInfoType type = WHATEVER;
  252. if (typeString != null) {
  253. try {
  254. Field field = HighlightInfoType.class.getField(typeString);
  255. type = (HighlightInfoType)field.get(null);
  256. }
  257. catch (Exception e) {
  258. LOG.error(e);
  259. }
  260. LOG.assertTrue(type != null, "Wrong highlight type: " + typeString);
  261. }
  262. TextAttributes forcedAttributes = null;
  263. if (foregroundColor != null) {
  264. //noinspection MagicConstant
  265. forcedAttributes = new TextAttributes(Color.decode(foregroundColor), Color.decode(backgroundColor), Color.decode(effectColor),
  266. EffectType.valueOf(effectType), Integer.parseInt(fontType));
  267. }
  268. final int rangeStart = textOffset.get();
  269. final int toContinueFrom;
  270. if (closed) {
  271. toContinueFrom = matcher.end();
  272. }
  273. else {
  274. int pos = matcher.end();
  275. final Matcher closingTagMatcher = Pattern.compile("</" + marker + ">").matcher(text);
  276. while (true) {
  277. if (!closingTagMatcher.find(pos)) {
  278. LOG.error("Cannot find closing </" + marker + "> in position " + pos);
  279. }
  280. final int nextTagStart = matcher.find(pos) ? matcher.start() : text.length();
  281. if (closingTagMatcher.start() < nextTagStart) {
  282. textOffset.set(textOffset.get() + closingTagMatcher.start() - pos);
  283. document.deleteString(textOffset.get(), textOffset.get() + closingTagMatcher.end() - closingTagMatcher.start());
  284. toContinueFrom = closingTagMatcher.end();
  285. break;
  286. }
  287. textOffset.set(textOffset.get() + nextTagStart - pos);
  288. pos = extractExpectedHighlight(matcher, text, document, textOffset);
  289. }
  290. }
  291. final ExpectedHighlightingSet expectedHighlightingSet = highlightingTypes.get(marker);
  292. if (expectedHighlightingSet.enabled) {
  293. TextAttributesKey forcedTextAttributesKey = attrKey == null ? null : TextAttributesKey.createTextAttributesKey(attrKey);
  294. final HighlightInfo highlightInfo = new HighlightInfo(forcedAttributes, forcedTextAttributesKey, type, rangeStart, textOffset.get(), descr, descr,
  295. expectedHighlightingSet.severity, expectedHighlightingSet.endOfLine, null,
  296. false);
  297. expectedHighlightingSet.infos.add(highlightInfo);
  298. }
  299. return toContinueFrom;
  300. }
  301. private static final HighlightInfoType WHATEVER = new HighlightInfoType.HighlightInfoTypeImpl();
  302. public void checkLineMarkers(Collection<LineMarkerInfo> markerInfos, String text) {
  303. String fileName = myFile == null ? "" : myFile.getName() + ": ";
  304. String failMessage = "";
  305. if (markerInfos != null) {
  306. for (LineMarkerInfo info : markerInfos) {
  307. if (!containsLineMarker(info, lineMarkerInfos.values())) {
  308. final int startOffset = info.startOffset;
  309. final int endOffset = info.endOffset;
  310. int y1 = StringUtil.offsetToLineNumber(text, startOffset);
  311. int y2 = StringUtil.offsetToLineNumber(text, endOffset);
  312. int x1 = startOffset - StringUtil.lineColToOffset(text, y1, 0);
  313. int x2 = endOffset - StringUtil.lineColToOffset(text, y2, 0);
  314. if (!failMessage.isEmpty()) failMessage += '\n';
  315. failMessage += fileName + "Extra line marker highlighted " +
  316. "(" + (x1 + 1) + ", " + (y1 + 1) + ")" + "-" +
  317. "(" + (x2 + 1) + ", " + (y2 + 1) + ")"
  318. + ": '"+info.getLineMarkerTooltip()+"'"
  319. ;
  320. }
  321. }
  322. }
  323. for (LineMarkerInfo expectedLineMarker : lineMarkerInfos.values()) {
  324. if (markerInfos != null && !containsLineMarker(expectedLineMarker, markerInfos)) {
  325. final int startOffset = expectedLineMarker.startOffset;
  326. final int endOffset = expectedLineMarker.endOffset;
  327. int y1 = StringUtil.offsetToLineNumber(text, startOffset);
  328. int y2 = StringUtil.offsetToLineNumber(text, endOffset);
  329. int x1 = startOffset - StringUtil.lineColToOffset(text, y1, 0);
  330. int x2 = endOffset - StringUtil.lineColToOffset(text, y2, 0);
  331. if (!failMessage.isEmpty()) failMessage += '\n';
  332. failMessage += fileName + "Line marker was not highlighted " +
  333. "(" + (x1 + 1) + ", " + (y1 + 1) + ")" + "-" +
  334. "(" + (x2 + 1) + ", " + (y2 + 1) + ")"
  335. + ": '"+expectedLineMarker.getLineMarkerTooltip()+"'"
  336. ;
  337. }
  338. }
  339. if (!failMessage.isEmpty()) Assert.fail(failMessage);
  340. }
  341. private static boolean containsLineMarker(LineMarkerInfo info, Collection<LineMarkerInfo> where) {
  342. final String infoTooltip = info.getLineMarkerTooltip();
  343. for (LineMarkerInfo markerInfo : where) {
  344. String markerInfoTooltip;
  345. if (markerInfo.startOffset == info.startOffset &&
  346. markerInfo.endOffset == info.endOffset &&
  347. ( Comparing.equal(infoTooltip, markerInfoTooltip = markerInfo.getLineMarkerTooltip()) ||
  348. ANY_TEXT.equals(markerInfoTooltip) ||
  349. ANY_TEXT.equals(infoTooltip)
  350. )
  351. ) {
  352. return true;
  353. }
  354. }
  355. return false;
  356. }
  357. public void checkResult(Collection<HighlightInfo> infos, String text) {
  358. checkResult(infos, text, null);
  359. }
  360. public void checkResult(Collection<HighlightInfo> infos, String text, @Nullable String filePath) {
  361. String fileName = myFile == null ? "" : myFile.getName() + ": ";
  362. String failMessage = "";
  363. for (HighlightInfo info : infos) {
  364. if (!expectedInfosContainsInfo(info)) {
  365. final int startOffset = info.startOffset;
  366. final int endOffset = info.endOffset;
  367. String s = text.substring(startOffset, endOffset);
  368. String desc = info.description;
  369. int y1 = StringUtil.offsetToLineNumber(text, startOffset);
  370. int y2 = StringUtil.offsetToLineNumber(text, endOffset);
  371. int x1 = startOffset - StringUtil.lineColToOffset(text, y1, 0);
  372. int x2 = endOffset - StringUtil.lineColToOffset(text, y2, 0);
  373. if (!failMessage.isEmpty()) failMessage += '\n';
  374. failMessage += fileName + "Extra text fragment highlighted " +
  375. "(" + (x1 + 1) + ", " + (y1 + 1) + ")" + "-" +
  376. "(" + (x2 + 1) + ", " + (y2 + 1) + ")" +
  377. " :'" +
  378. s +
  379. "'" + (desc == null ? "" : " (" + desc + ")")
  380. + " [" + info.type + "]";
  381. }
  382. }
  383. final Collection<ExpectedHighlightingSet> expectedHighlights = highlightingTypes.values();
  384. for (ExpectedHighlightingSet highlightingSet : expectedHighlights) {
  385. final Set<HighlightInfo> expInfos = highlightingSet.infos;
  386. for (HighlightInfo expectedInfo : expInfos) {
  387. if (!infosContainsExpectedInfo(infos, expectedInfo) && highlightingSet.enabled) {
  388. final int startOffset = expectedInfo.startOffset;
  389. final int endOffset = expectedInfo.endOffset;
  390. String s = text.substring(startOffset, endOffset);
  391. String desc = expectedInfo.description;
  392. int y1 = StringUtil.offsetToLineNumber(text, startOffset);
  393. int y2 = StringUtil.offsetToLineNumber(text, endOffset);
  394. int x1 = startOffset - StringUtil.lineColToOffset(text, y1, 0);
  395. int x2 = endOffset - StringUtil.lineColToOffset(text, y2, 0);
  396. if (!failMessage.isEmpty()) failMessage += '\n';
  397. failMessage += fileName + "Text fragment was not highlighted " +
  398. "(" + (x1 + 1) + ", " + (y1 + 1) + ")" + "-" +
  399. "(" + (x2 + 1) + ", " + (y2 + 1) + ")" +
  400. " :'" +
  401. s +
  402. "'" + (desc == null ? "" : " (" + desc + ")");
  403. }
  404. }
  405. }
  406. if (!failMessage.isEmpty()) {
  407. compareTexts(infos, text, failMessage, filePath);
  408. }
  409. }
  410. private void compareTexts(Collection<HighlightInfo> infos, String text, String failMessage, @Nullable String filePath) {
  411. String actual = composeText(highlightingTypes, infos, text);
  412. if (filePath != null && !myText.equals(actual)) {
  413. throw new FileComparisonFailure(failMessage, myText, actual, filePath);
  414. }
  415. Assert.assertEquals(failMessage + "\n", myText, actual);
  416. Assert.fail(failMessage);
  417. }
  418. public static String composeText(final Map<String, ExpectedHighlightingSet> types, Collection<HighlightInfo> infos, String text) {
  419. // filter highlighting data and map each highlighting to a tag name
  420. List<Pair<String, HighlightInfo>> list = ContainerUtil.mapNotNull(infos, new NullableFunction<HighlightInfo, Pair<String,HighlightInfo>>() {
  421. @Override
  422. public Pair<String, HighlightInfo> fun(HighlightInfo info) {
  423. for (Map.Entry<String, ExpectedHighlightingSet> entry : types.entrySet()) {
  424. final ExpectedHighlightingSet set = entry.getValue();
  425. if (set.enabled && set.severity == info.getSeverity() && set.endOfLine == info.isAfterEndOfLine) {
  426. return Pair.create(entry.getKey(), info);
  427. }
  428. }
  429. return null;
  430. }
  431. });
  432. // sort filtered highlighting data by end offset in descending order
  433. Collections.sort(list, new Comparator<Pair<String, HighlightInfo>>() {
  434. @Override
  435. public int compare(Pair<String, HighlightInfo> o1, Pair<String, HighlightInfo> o2) {
  436. HighlightInfo i1 = o1.second;
  437. HighlightInfo i2 = o2.second;
  438. int byEnds = i2.endOffset - i1.endOffset;
  439. if (byEnds != 0) return byEnds;
  440. int byStarts = i1.startOffset - i2.startOffset;
  441. if (byStarts != 0) return byStarts;
  442. int bySeverity = i2.severity.compareTo(i1.severity);
  443. if (bySeverity != 0) return bySeverity;
  444. return Comparing.compare(i1.description, i2.description);
  445. }
  446. });
  447. // combine highlighting data with original text
  448. StringBuilder sb = new StringBuilder();
  449. Pair<Integer, Integer> result = composeText(sb, list, 0, text, text.length(), 0);
  450. sb.insert(0, text.substring(0, result.second));
  451. return sb.toString();
  452. }
  453. private static Pair<Integer, Integer> composeText(StringBuilder sb,
  454. List<Pair<String, HighlightInfo>> list, int index,
  455. String text, int endPos, int startPos) {
  456. int i = index;
  457. while (i < list.size()) {
  458. Pair<String, HighlightInfo> pair = list.get(i);
  459. HighlightInfo info = pair.second;
  460. if (info.endOffset < startPos) break;
  461. String severity = pair.first;
  462. HighlightInfo prev = i < list.size() - 1 ? list.get(i + 1).second : null;
  463. sb.insert(0, text.substring(info.endOffset, endPos));
  464. sb.insert(0, "</" + severity + ">");
  465. endPos = info.endOffset;
  466. if (prev != null && prev.endOffset > info.startOffset) {
  467. Pair<Integer, Integer> result = composeText(sb, list, i + 1, text, endPos, info.startOffset);
  468. i = result.first - 1;
  469. endPos = result.second;
  470. }
  471. sb.insert(0, text.substring(info.startOffset, endPos));
  472. sb.insert(0, "<" + severity + " descr=\"" + info.description + "\">");
  473. endPos = info.startOffset;
  474. i++;
  475. }
  476. return Pair.create(i, endPos);
  477. }
  478. private static boolean infosContainsExpectedInfo(Collection<HighlightInfo> infos, HighlightInfo expectedInfo) {
  479. for (HighlightInfo info : infos) {
  480. if (infoEquals(expectedInfo, info)) {
  481. return true;
  482. }
  483. }
  484. return false;
  485. }
  486. private boolean expectedInfosContainsInfo(HighlightInfo info) {
  487. if (info.getTextAttributes(null, null) == TextAttributes.ERASE_MARKER) return true;
  488. final Collection<ExpectedHighlightingSet> expectedHighlights = highlightingTypes.values();
  489. for (ExpectedHighlightingSet highlightingSet : expectedHighlights) {
  490. if (highlightingSet.severity != info.getSeverity()) continue;
  491. if (!highlightingSet.enabled) return true;
  492. final Set<HighlightInfo> infos = highlightingSet.infos;
  493. for (HighlightInfo expectedInfo : infos) {
  494. if (infoEquals(expectedInfo, info)) {
  495. return true;
  496. }
  497. }
  498. }
  499. return false;
  500. }
  501. private static boolean infoEquals(HighlightInfo expectedInfo, HighlightInfo info) {
  502. if (expectedInfo == info) return true;
  503. return
  504. info.getSeverity() == expectedInfo.getSeverity() &&
  505. info.startOffset == expectedInfo.startOffset &&
  506. info.endOffset == expectedInfo.endOffset &&
  507. info.isAfterEndOfLine == expectedInfo.isAfterEndOfLine &&
  508. (expectedInfo.type == WHATEVER || expectedInfo.type.equals(info.type)) &&
  509. (Comparing.strEqual(ANY_TEXT, expectedInfo.description) || Comparing.strEqual(info.description, expectedInfo.description)) &&
  510. (expectedInfo.forcedTextAttributes == null || Comparing.equal(expectedInfo.getTextAttributes(null, null),
  511. info.getTextAttributes(null, null))) &&
  512. (expectedInfo.forcedTextAttributesKey == null || expectedInfo.forcedTextAttributesKey.equals(info.forcedTextAttributesKey));
  513. }
  514. }