]> source.dussan.org Git - sonarqube.git/commitdiff
(SONAR-3893) Improve the highlighter API to not depend on sonar-channel and allow...
authorJean-Baptiste Vilain <jean-baptiste.vilain@sonarsource.com>
Mon, 8 Apr 2013 14:26:30 +0000 (16:26 +0200)
committerJean-Baptiste Vilain <jean-baptiste.vilain@sonarsource.com>
Mon, 8 Apr 2013 14:26:30 +0000 (16:26 +0200)
sonar-core/src/main/java/org/sonar/core/source/CharactersReader.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/source/HtmlTextWrapper.java
sonar-core/src/test/java/org/sonar/core/source/HtmlTextWrapperTest.java

diff --git a/sonar-core/src/main/java/org/sonar/core/source/CharactersReader.java b/sonar-core/src/main/java/org/sonar/core/source/CharactersReader.java
new file mode 100644 (file)
index 0000000..7cede8c
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2012 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02
+ */
+
+package org.sonar.core.source;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+public class CharactersReader {
+
+  private static final int END_OF_STREAM = -1;
+
+  private final BufferedReader stringBuffer;
+  private final Deque<String> openTags;
+
+  private int currentValue;
+  private int previousValue;
+  private int currentIndex = -1;
+
+  public CharactersReader(BufferedReader stringBuffer) {
+    this.stringBuffer = stringBuffer;
+    this.openTags = new ArrayDeque<String>();
+  }
+
+  public boolean readNextChar() throws IOException {
+    previousValue = currentValue;
+    currentValue = stringBuffer.read();
+    currentIndex++;
+    return currentValue != END_OF_STREAM;
+  }
+
+  public int getCurrentValue() {
+    return currentValue;
+  }
+
+  public int getPreviousValue() {
+    return previousValue;
+  }
+
+  public int getCurrentIndex() {
+    return currentIndex;
+  }
+
+  public void registerOpenTag(String textType) {
+    openTags.push(textType);
+  }
+
+  public void removeLastOpenTag() {
+    openTags.remove();
+  }
+
+  public Deque<String> getOpenTags() {
+    return openTags;
+  }
+}
index e5d33aae4595aa6202721b813603603c1da21a2e..a3f8a30ad7f94ed5ecf5d840deeec90e72e7f85d 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.core.source;
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.Collections2;
+import org.slf4j.LoggerFactory;
 import org.sonar.api.scan.source.SyntaxHighlightingRule;
 import org.sonar.api.scan.source.SyntaxHighlightingRuleSet;
 
@@ -30,22 +31,15 @@ import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.StringReader;
 import java.util.Collection;
-import java.util.LinkedList;
 import java.util.List;
-import java.util.Queue;
 
 public class HtmlTextWrapper {
 
-  private static final int END_OF_STREAM = -1;
-  private static final char END_OF_LINE = '\n';
   private static final String OPEN_TABLE_LINE = "<tr><td>";
   private static final String CLOSE_TABLE_LINE = "</td></tr>";
 
-  private Queue<String> currentOpenTags;
-
-  public HtmlTextWrapper() {
-    currentOpenTags = new LinkedList<String>();
-  }
+  public static final char CR_END_OF_LINE = '\r';
+  public static final char LF_END_OF_LINE = '\n';
 
   public String wrapTextWithHtml(String text, SyntaxHighlightingRuleSet syntaxHighlighting) throws IOException {
 
@@ -57,36 +51,34 @@ public class HtmlTextWrapper {
     try {
       stringBuffer = new BufferedReader(new StringReader(text));
 
-      int currentCharValue = stringBuffer.read();
-      int currentCharIndex = 0;
-      boolean isNewLine = true;
+      CharactersReader context = new CharactersReader(stringBuffer);
 
-      while(currentCharValue != END_OF_STREAM) {
+      while(context.readNextChar()) {
 
-        if(isNewLine) {
+        if(shouldStartNewLine(context)) {
           decoratedText.append(OPEN_TABLE_LINE);
-          reopenCurrentSyntaxTags(decoratedText);
-          isNewLine = false;
+          if(shouldReopenPendingTags(context)) {
+            reopenCurrentSyntaxTags(context, decoratedText);
+          }
         }
 
-        if(currentCharValue == END_OF_LINE) {
-          closeCurrentSyntaxTags(decoratedText);
+        Collection<SyntaxHighlightingRule> tagsToClose =
+                Collections2.filter(highlightingRules, new IndexRuleFilter(context.getCurrentIndex(), false));
+        closeCompletedTags(context, tagsToClose, decoratedText);
+
+        if(shouldClosePendingTags(context)) {
+          closeCurrentSyntaxTags(context, decoratedText);
           decoratedText.append(CLOSE_TABLE_LINE);
-          isNewLine = true;
-        } else {
-          Collection<SyntaxHighlightingRule> rulesMatchingCurrentIndex =
-                  Collections2.filter(highlightingRules, new IndexRuleFilter(currentCharIndex));
-          if(rulesMatchingCurrentIndex.size() > 0) {
-            injectHtml(currentCharIndex, rulesMatchingCurrentIndex, decoratedText);
-          }
         }
 
-        decoratedText.append((char)currentCharValue);
-        currentCharValue = stringBuffer.read();
-        currentCharIndex++;
+        Collection<SyntaxHighlightingRule> tagsToOpen =
+                Collections2.filter(highlightingRules, new IndexRuleFilter(context.getCurrentIndex(), true));
+        openNewTags(context, tagsToOpen, decoratedText);
+
+        decoratedText.append((char)context.getCurrentValue());
       }
-    } catch (Exception Ex) {
-      //
+    } catch (IOException exception) {
+      LoggerFactory.getLogger(HtmlTextWrapper.class).error("");
     } finally {
       closeReaderSilently(stringBuffer);
     }
@@ -94,31 +86,47 @@ public class HtmlTextWrapper {
     return decoratedText.toString();
   }
 
-  private void injectHtml(int currentIndex, Collection<SyntaxHighlightingRule> rulesMatchingCurrentIndex,
-                          StringBuilder decoratedText) {
+  public boolean shouldClosePendingTags(CharactersReader context) {
+    return context.getCurrentValue() == CR_END_OF_LINE
+            || (context.getCurrentValue() == LF_END_OF_LINE && context.getPreviousValue() != CR_END_OF_LINE);
+  }
+
+  public boolean shouldReopenPendingTags(CharactersReader context) {
+    return context.getPreviousValue() == LF_END_OF_LINE && context.getCurrentValue() != LF_END_OF_LINE;
+  }
+
+  public boolean shouldStartNewLine(CharactersReader context) {
+    return context.getPreviousValue() == LF_END_OF_LINE || context.getCurrentIndex() == 0;
+  }
+
+  private void closeCompletedTags(CharactersReader context, Collection<SyntaxHighlightingRule> rulesMatchingCurrentIndex,
+                                  StringBuilder decoratedText) {
     for (SyntaxHighlightingRule syntaxHighlightingRule : rulesMatchingCurrentIndex) {
-      if(currentIndex == syntaxHighlightingRule.getEndPosition()) {
+      if(context.getCurrentIndex() == syntaxHighlightingRule.getEndPosition()) {
         injectClosingHtml(decoratedText);
-        currentOpenTags.remove();
+        context.removeLastOpenTag();
       }
     }
+  }
 
+  private void openNewTags(CharactersReader context, Collection<SyntaxHighlightingRule> rulesMatchingCurrentIndex,
+                           StringBuilder decoratedText) {
     for (SyntaxHighlightingRule syntaxHighlightingRule : rulesMatchingCurrentIndex) {
-      if(currentIndex == syntaxHighlightingRule.getStartPosition()) {
+      if(context.getCurrentIndex() == syntaxHighlightingRule.getStartPosition()) {
         injectOpeningHtmlForRule(syntaxHighlightingRule.getTextType(), decoratedText);
-        currentOpenTags.add(syntaxHighlightingRule.getTextType());
+        context.registerOpenTag(syntaxHighlightingRule.getTextType());
       }
     }
   }
 
-  private void closeCurrentSyntaxTags(StringBuilder decoratedText) {
-    for (int i = 0; i < currentOpenTags.size(); i++) {
+  private void closeCurrentSyntaxTags(CharactersReader context, StringBuilder decoratedText) {
+    for (int i = 0; i < context.getOpenTags().size(); i++) {
       injectClosingHtml(decoratedText);
     }
   }
 
-  private void reopenCurrentSyntaxTags(StringBuilder decoratedText) {
-    for (String tags : currentOpenTags) {
+  private void reopenCurrentSyntaxTags(CharactersReader context, StringBuilder decoratedText) {
+    for (String tags : context.getOpenTags()) {
       injectOpeningHtmlForRule(tags, decoratedText);
     }
   }
@@ -137,22 +145,25 @@ public class HtmlTextWrapper {
         reader.close();
       }
     } catch (IOException e) {
-      //
+      LoggerFactory.getLogger(HtmlTextWrapper.class).error("Could not close ");
     }
   }
 
   private class IndexRuleFilter implements Predicate<SyntaxHighlightingRule> {
 
     private final int characterIndex;
+    private final boolean isNewCharRange;
 
-    public IndexRuleFilter(int charIndex) {
+    public IndexRuleFilter(int charIndex, boolean isNewCharRange) {
       this.characterIndex = charIndex;
+      this.isNewCharRange = isNewCharRange;
     }
 
     @Override
     public boolean apply(@Nullable SyntaxHighlightingRule syntaxHighlightingRule) {
       if(syntaxHighlightingRule != null) {
-        return characterIndex == syntaxHighlightingRule.getStartPosition() || characterIndex == syntaxHighlightingRule.getEndPosition();
+        return (characterIndex == syntaxHighlightingRule.getStartPosition() && isNewCharRange)
+                || (characterIndex == syntaxHighlightingRule.getEndPosition() && !isNewCharRange);
       }
       return false;
     }
index 51c1bffe9c487f93a1bd25937a2c2677d54985ab..6424583f9ba31d437a3b676be9c351a42400a4a2 100644 (file)
@@ -25,11 +25,11 @@ import org.sonar.api.scan.source.HighlightableTextType;
 import org.sonar.api.scan.source.SyntaxHighlightingRuleSet;
 
 import static org.fest.assertions.Assertions.assertThat;
+import static org.sonar.core.source.HtmlTextWrapper.CR_END_OF_LINE;
+import static org.sonar.core.source.HtmlTextWrapper.LF_END_OF_LINE;
 
 public class HtmlTextWrapperTest {
 
-  private static final String NEW_LINE = "\n";
-
   @Test
   public void should_decorate_simple_character_range() throws Exception {
 
@@ -51,18 +51,20 @@ public class HtmlTextWrapperTest {
     String secondCommentLine = " * Test";
     String thirdCommentLine = " */";
 
-    String blockComment = firstCommentLine + NEW_LINE + secondCommentLine + NEW_LINE + thirdCommentLine + NEW_LINE;
+    String blockComment = firstCommentLine + LF_END_OF_LINE
+            + secondCommentLine + LF_END_OF_LINE
+            + thirdCommentLine + LF_END_OF_LINE;
 
-    SyntaxHighlightingRuleSet syntaxHighlighting = new SyntaxHighlightingRuleSet.Builder()
+    SyntaxHighlightingRuleSet syntaxHighlighting = SyntaxHighlightingRuleSet.builder()
             .registerHighlightingRule(0, 14, HighlightableTextType.BLOCK_COMMENT).build();
 
     HtmlTextWrapper htmlTextWrapper = new HtmlTextWrapper();
     String htmlOutput = htmlTextWrapper.wrapTextWithHtml(blockComment, syntaxHighlighting);
 
     assertThat(htmlOutput).isEqualTo(
-            "<tr><td><span class=\"cppd\">" + firstCommentLine + "</span></td></tr>" + NEW_LINE +
-            "<tr><td><span class=\"cppd\">" + secondCommentLine + "</span></td></tr>" + NEW_LINE +
-            "<tr><td><span class=\"cppd\">" + thirdCommentLine + "</span></td></tr>" + NEW_LINE
+            "<tr><td><span class=\"cppd\">" + firstCommentLine + "</span></td></tr>" + LF_END_OF_LINE +
+            "<tr><td><span class=\"cppd\">" + secondCommentLine + "</span></td></tr>" + LF_END_OF_LINE +
+            "<tr><td><span class=\"cppd\">" + thirdCommentLine + "</span></td></tr>" + LF_END_OF_LINE
     );
   }
 
@@ -83,4 +85,68 @@ public class HtmlTextWrapperTest {
     assertThat(htmlOutput).isEqualTo(
             "<tr><td><span class=\"k\">public</span> <span class=\"k\">class</span> MyClass <span class=\"k\">implements</span> MyInterface {</td></tr>\n");
   }
+
+  @Test
+  public void should_allow_multiple_levels_highlighting() throws Exception {
+
+    String javaDocSample =
+            "/**" + LF_END_OF_LINE +
+            " * Creates a FormulaDecorator" + LF_END_OF_LINE +
+            " *" + LF_END_OF_LINE +
+            " * @param metric the metric should have an associated formula" + LF_END_OF_LINE +
+            " * " + LF_END_OF_LINE +
+            " * @throws IllegalArgumentException if no formula is associated to the metric" + LF_END_OF_LINE +
+            " */" + LF_END_OF_LINE;
+
+    SyntaxHighlightingRuleSet syntaxHighlighting = SyntaxHighlightingRuleSet.builder()
+            .registerHighlightingRule(0, 184, HighlightableTextType.BLOCK_COMMENT)
+            .registerHighlightingRule(47, 53, HighlightableTextType.KEYWORD)
+            .build();
+
+    HtmlTextWrapper htmlTextWrapper = new HtmlTextWrapper();
+    String htmlOutput = htmlTextWrapper.wrapTextWithHtml(javaDocSample, syntaxHighlighting);
+
+    assertThat(htmlOutput).isEqualTo(
+            "<tr><td><span class=\"cppd\">/**</span></td></tr>" + LF_END_OF_LINE +
+            "<tr><td><span class=\"cppd\"> * Creates a FormulaDecorator</span></td></tr>" + LF_END_OF_LINE +
+            "<tr><td><span class=\"cppd\"> *</span></td></tr>" + LF_END_OF_LINE +
+            "<tr><td><span class=\"cppd\"> * @param <span class=\"k\">metric</span> the metric should have an associated formula</span></td></tr>" + LF_END_OF_LINE +
+            "<tr><td><span class=\"cppd\"> * </span></td></tr>" + LF_END_OF_LINE +
+            "<tr><td><span class=\"cppd\"> * @throws IllegalArgumentException if no formula is associated to the metric</span></td></tr>" + LF_END_OF_LINE +
+            "<tr><td><span class=\"cppd\"> */</span></td></tr>" + LF_END_OF_LINE
+          );
+  }
+
+  @Test
+  public void should_support_crlf_line_breaks() throws Exception {
+
+    String crlfCodeSample =
+            "/**" + CR_END_OF_LINE + LF_END_OF_LINE +
+            "* @return metric generated by the decorator" + CR_END_OF_LINE + LF_END_OF_LINE +
+            "*/" + CR_END_OF_LINE + LF_END_OF_LINE +
+            "@DependedUpon" + CR_END_OF_LINE + LF_END_OF_LINE +
+            "public Metric generatesMetric() {" + CR_END_OF_LINE + LF_END_OF_LINE +
+            "  return metric;" + CR_END_OF_LINE + LF_END_OF_LINE +
+            "}" + CR_END_OF_LINE + LF_END_OF_LINE;
+
+    SyntaxHighlightingRuleSet syntaxHighlighting = SyntaxHighlightingRuleSet.builder()
+            .registerHighlightingRule(0, 52, HighlightableTextType.BLOCK_COMMENT)
+            .registerHighlightingRule(54, 67, HighlightableTextType.ANNOTATION)
+            .registerHighlightingRule(69, 75, HighlightableTextType.KEYWORD)
+            .registerHighlightingRule(106, 112, HighlightableTextType.KEYWORD)
+            .build();
+
+    HtmlTextWrapper htmlTextWrapper = new HtmlTextWrapper();
+    String htmlOutput = htmlTextWrapper.wrapTextWithHtml(crlfCodeSample, syntaxHighlighting);
+
+    assertThat(htmlOutput).isEqualTo(
+            "<tr><td><span class=\"cppd\">/**</span></td></tr>" + CR_END_OF_LINE + LF_END_OF_LINE +
+            "<tr><td><span class=\"cppd\">* @return metric generated by the decorator</span></td></tr>" + CR_END_OF_LINE + LF_END_OF_LINE +
+            "<tr><td><span class=\"cppd\">*/</span></td></tr>" + CR_END_OF_LINE + LF_END_OF_LINE +
+            "<tr><td><span class=\"a\">@DependedUpon</span></td></tr>" + CR_END_OF_LINE + LF_END_OF_LINE +
+            "<tr><td><span class=\"k\">public</span> Metric generatesMetric() {</td></tr>" + CR_END_OF_LINE + LF_END_OF_LINE +
+            "<tr><td>  <span class=\"k\">return</span> metric;</td></tr>" + CR_END_OF_LINE + LF_END_OF_LINE +
+            "<tr><td>}</td></tr>" + CR_END_OF_LINE + LF_END_OF_LINE
+          );
+  }
 }