From 84c061c3a5d904fd38a953ef74fbfc98ca51df28 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Vilain Date: Wed, 10 Apr 2013 16:07:27 +0200 Subject: [PATCH] (SONAR-3893) Improve the highlighter API to not depend on sonar-channel and allow to work on multi-line tokens - Updated highlighted html to return a list of html lines --- .../sonar/core/source/HtmlTextWrapper.java | 53 +++++++----- .../sonar/core/source/SyntaxHighlighter.java | 4 +- .../core/source/HtmlTextWrapperTest.java | 82 ++++++++++--------- .../core/source/SyntaxHighlighterTest.java | 20 ++--- .../org/sonar/server/platform/Platform.java | 2 + .../java/org/sonar/server/ui/JRubyFacade.java | 5 ++ 6 files changed, 95 insertions(+), 71 deletions(-) diff --git a/sonar-core/src/main/java/org/sonar/core/source/HtmlTextWrapper.java b/sonar-core/src/main/java/org/sonar/core/source/HtmlTextWrapper.java index ce2a9ad4880..d99aff3201a 100644 --- a/sonar-core/src/main/java/org/sonar/core/source/HtmlTextWrapper.java +++ b/sonar-core/src/main/java/org/sonar/core/source/HtmlTextWrapper.java @@ -20,6 +20,7 @@ package org.sonar.core.source; +import com.google.common.collect.Lists; import com.google.common.io.Closeables; import org.slf4j.LoggerFactory; @@ -28,6 +29,7 @@ import java.io.IOException; import java.io.StringReader; import java.util.Collection; import java.util.Collections; +import java.util.List; import static org.sonar.core.source.CharactersReader.END_OF_STREAM; @@ -42,9 +44,10 @@ public class HtmlTextWrapper { public static final char CR_END_OF_LINE = '\r'; public static final char LF_END_OF_LINE = '\n'; - public String wrapTextWithHtml(String text, HighlightingContext context) { + public List wrapTextWithHtml(String text, HighlightingContext context) { - StringBuilder decoratedText = new StringBuilder(); + StringBuilder currentHtmlLine = new StringBuilder(); + List decoratedHtmlLines = Lists.newArrayList(); BufferedReader stringBuffer = null; @@ -56,29 +59,33 @@ public class HtmlTextWrapper { while (charsReader.readNextChar()) { if (shouldStartNewLine(charsReader)) { - decoratedText.append(OPEN_TABLE_LINE); + decoratedHtmlLines.add(currentHtmlLine.toString()); + currentHtmlLine = new StringBuilder(); +// currentHtmlLine.append(OPEN_TABLE_LINE); if (shouldReopenPendingTags(charsReader)) { - reopenCurrentSyntaxTags(charsReader, decoratedText); + reopenCurrentSyntaxTags(charsReader, currentHtmlLine); } } int numberOfTagsToClose = getNumberOfTagsToClose(charsReader.getCurrentIndex(), context); - closeCompletedTags(charsReader, numberOfTagsToClose, decoratedText); + closeCompletedTags(charsReader, numberOfTagsToClose, currentHtmlLine); if (shouldClosePendingTags(charsReader)) { - closeCurrentSyntaxTags(charsReader, decoratedText); - decoratedText.append(CLOSE_TABLE_LINE); + closeCurrentSyntaxTags(charsReader, currentHtmlLine); +// currentHtmlLine.append(CLOSE_TABLE_LINE); } Collection tagsToOpen = getTagsToOpen(charsReader.getCurrentIndex(), context); - openNewTags(charsReader, tagsToOpen, decoratedText); + openNewTags(charsReader, tagsToOpen, currentHtmlLine); - decoratedText.append((char) charsReader.getCurrentValue()); + if (shouldAppendCharToHtmlOutput(charsReader)) { + currentHtmlLine.append((char) charsReader.getCurrentValue()); + } } - if(shouldClosePendingTags(charsReader)) { - closeCurrentSyntaxTags(charsReader, decoratedText); - decoratedText.append(CLOSE_TABLE_LINE); + closeCurrentSyntaxTags(charsReader, currentHtmlLine); + if (currentHtmlLine.length() > 0) { + decoratedHtmlLines.add(currentHtmlLine.toString()); } } catch (IOException exception) { @@ -89,7 +96,11 @@ public class HtmlTextWrapper { Closeables.closeQuietly(stringBuffer); } - return decoratedText.toString(); + return decoratedHtmlLines; + } + + private boolean shouldAppendCharToHtmlOutput(CharactersReader charsReader) { + return charsReader.getCurrentValue() != CR_END_OF_LINE && charsReader.getCurrentValue() != LF_END_OF_LINE; } private int getNumberOfTagsToClose(int currentIndex, HighlightingContext context) { @@ -100,18 +111,18 @@ public class HtmlTextWrapper { return context.getLowerBoundsDefinitions().get(currentIndex); } - private boolean shouldClosePendingTags(CharactersReader context) { - return context.getCurrentValue() == CR_END_OF_LINE - || (context.getCurrentValue() == LF_END_OF_LINE && context.getPreviousValue() != CR_END_OF_LINE) - || (context.getCurrentValue() == END_OF_STREAM && context.getPreviousValue() != LF_END_OF_LINE); + private boolean shouldClosePendingTags(CharactersReader charactersReader) { + return charactersReader.getCurrentValue() == CR_END_OF_LINE + || (charactersReader.getCurrentValue() == LF_END_OF_LINE && charactersReader.getPreviousValue() != CR_END_OF_LINE) + || (charactersReader.getCurrentValue() == END_OF_STREAM && charactersReader.getPreviousValue() != LF_END_OF_LINE); } - private boolean shouldReopenPendingTags(CharactersReader context) { - return context.getPreviousValue() == LF_END_OF_LINE && context.getCurrentValue() != LF_END_OF_LINE; + private boolean shouldReopenPendingTags(CharactersReader charactersReader) { + return charactersReader.getPreviousValue() == LF_END_OF_LINE && charactersReader.getCurrentValue() != LF_END_OF_LINE; } - private boolean shouldStartNewLine(CharactersReader context) { - return context.getPreviousValue() == LF_END_OF_LINE || context.getCurrentIndex() == 0; + private boolean shouldStartNewLine(CharactersReader charactersReader) { + return charactersReader.getPreviousValue() == LF_END_OF_LINE; } private void closeCompletedTags(CharactersReader charactersReader, int numberOfTagsToClose, diff --git a/sonar-core/src/main/java/org/sonar/core/source/SyntaxHighlighter.java b/sonar-core/src/main/java/org/sonar/core/source/SyntaxHighlighter.java index 4913de6810c..c33a9fd4765 100644 --- a/sonar-core/src/main/java/org/sonar/core/source/SyntaxHighlighter.java +++ b/sonar-core/src/main/java/org/sonar/core/source/SyntaxHighlighter.java @@ -24,6 +24,8 @@ import org.sonar.core.persistence.MyBatis; import org.sonar.core.source.jdbc.SnapshotDataDao; import org.sonar.core.source.jdbc.SnapshotSourceDao; +import java.util.Collection; + /** * @since 3.6 */ @@ -37,7 +39,7 @@ public class SyntaxHighlighter { snapshotDataDao = new SnapshotDataDao(myBatis); } - public String getHighlightedSourceAsHtml(long snapshotId) { + public Collection getHighlightedSourceAsHtml(long snapshotId) { String snapshotSource = snapshotSourceDao.selectSnapshotSource(snapshotId); String highlightingRules = snapshotDataDao.selectSnapshotData(snapshotId); diff --git a/sonar-core/src/test/java/org/sonar/core/source/HtmlTextWrapperTest.java b/sonar-core/src/test/java/org/sonar/core/source/HtmlTextWrapperTest.java index 56f926dda90..38d1d5ccb04 100644 --- a/sonar-core/src/test/java/org/sonar/core/source/HtmlTextWrapperTest.java +++ b/sonar-core/src/test/java/org/sonar/core/source/HtmlTextWrapperTest.java @@ -22,6 +22,8 @@ package org.sonar.core.source; import org.junit.Test; +import java.util.List; + 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; @@ -36,9 +38,9 @@ public class HtmlTextWrapperTest { HighlightingContext context = HighlightingContext.buildFrom("0,7,k;"); HtmlTextWrapper htmlTextWrapper = new HtmlTextWrapper(); - String htmlOutput = htmlTextWrapper.wrapTextWithHtml(packageDeclaration, context); + List htmlOutput = htmlTextWrapper.wrapTextWithHtml(packageDeclaration, context); - assertThat(htmlOutput).isEqualTo("package org.sonar.core.source;"); + assertThat(htmlOutput).containsOnly("package org.sonar.core.source;"); } @Test @@ -55,12 +57,12 @@ public class HtmlTextWrapperTest { HighlightingContext context = HighlightingContext.buildFrom("0,14,cppd;"); HtmlTextWrapper htmlTextWrapper = new HtmlTextWrapper(); - String htmlOutput = htmlTextWrapper.wrapTextWithHtml(blockComment, context); + List htmlOutput = htmlTextWrapper.wrapTextWithHtml(blockComment, context); - assertThat(htmlOutput).isEqualTo( - "" + firstCommentLine + "" + LF_END_OF_LINE + - "" + secondCommentLine + "" + LF_END_OF_LINE + - "" + thirdCommentLine + "" + LF_END_OF_LINE + assertThat(htmlOutput).containsExactly( + "" + firstCommentLine + "", + "" + secondCommentLine + "", + "" + thirdCommentLine + "" ); } @@ -72,10 +74,12 @@ public class HtmlTextWrapperTest { HighlightingContext context = HighlightingContext.buildFrom("0,6,k;7,12,k;21,31,k;"); HtmlTextWrapper htmlTextWrapper = new HtmlTextWrapper(); - String htmlOutput = htmlTextWrapper.wrapTextWithHtml(classDeclaration, context); + List htmlOutput = htmlTextWrapper.wrapTextWithHtml(classDeclaration, context); - assertThat(htmlOutput).isEqualTo( - "public class MyClass implements MyInterface {\n"); + assertThat(htmlOutput).containsOnly( + "public " + + "class MyClass " + + "implements MyInterface {"); } @Test @@ -93,16 +97,16 @@ public class HtmlTextWrapperTest { HighlightingContext context = HighlightingContext.buildFrom("0,184,cppd;47,53,k;"); HtmlTextWrapper htmlTextWrapper = new HtmlTextWrapper(); - String htmlOutput = htmlTextWrapper.wrapTextWithHtml(javaDocSample, context); - - assertThat(htmlOutput).isEqualTo( - "/**" + 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 + List htmlOutput = htmlTextWrapper.wrapTextWithHtml(javaDocSample, context); + + assertThat(htmlOutput).containsExactly( + "/**", + " * Creates a FormulaDecorator", + " *", + " * @param metric the metric should have an associated formula", + " * ", + " * @throws IllegalArgumentException if no formula is associated to the metric", + " */" ); } @@ -121,16 +125,16 @@ public class HtmlTextWrapperTest { HighlightingContext context = HighlightingContext.buildFrom("0,52,cppd;54,67,a;69,75,k;106,112,k;"); HtmlTextWrapper htmlTextWrapper = new HtmlTextWrapper(); - String htmlOutput = htmlTextWrapper.wrapTextWithHtml(crlfCodeSample, context); - - assertThat(htmlOutput).isEqualTo( - "/**" + 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 + List htmlOutput = htmlTextWrapper.wrapTextWithHtml(crlfCodeSample, context); + + assertThat(htmlOutput).containsExactly( + "/**", + "* @return metric generated by the decorator", + "*/", + "@DependedUpon", + "public Metric generatesMetric() {", + " return metric;", + "}" ); } @@ -148,15 +152,15 @@ public class HtmlTextWrapperTest { HighlightingContext context = HighlightingContext.buildFrom("0,16,cppd;18,25,k;25,31,k;"); HtmlTextWrapper htmlTextWrapper = new HtmlTextWrapper(); - String htmlOutput = htmlTextWrapper.wrapTextWithHtml(classDeclarationSample, context); - - assertThat(htmlOutput).isEqualTo( - "/*" + LF_END_OF_LINE + - " * Header" + LF_END_OF_LINE + - " */" + LF_END_OF_LINE + - "" + LF_END_OF_LINE + - "public class HelloWorld {" + LF_END_OF_LINE + - "}" + List htmlOutput = htmlTextWrapper.wrapTextWithHtml(classDeclarationSample, context); + + assertThat(htmlOutput).containsExactly( + "/*", + " * Header", + " */", + "", + "public class HelloWorld {", + "}" ); } } diff --git a/sonar-core/src/test/java/org/sonar/core/source/SyntaxHighlighterTest.java b/sonar-core/src/test/java/org/sonar/core/source/SyntaxHighlighterTest.java index d1fb7111c81..0b6fe434d40 100644 --- a/sonar-core/src/test/java/org/sonar/core/source/SyntaxHighlighterTest.java +++ b/sonar-core/src/test/java/org/sonar/core/source/SyntaxHighlighterTest.java @@ -24,8 +24,9 @@ import org.junit.Before; import org.junit.Test; import org.sonar.core.persistence.AbstractDaoTestCase; +import java.util.List; + import static org.fest.assertions.Assertions.assertThat; -import static org.sonar.core.source.HtmlTextWrapper.LF_END_OF_LINE; public class SyntaxHighlighterTest extends AbstractDaoTestCase { @@ -39,16 +40,15 @@ public class SyntaxHighlighterTest extends AbstractDaoTestCase { SyntaxHighlighter highlighter = new SyntaxHighlighter(getMyBatis()); - String highlightedSource = highlighter.getHighlightedSourceAsHtml(11L); + List highlightedSource = (List)highlighter.getHighlightedSourceAsHtml(11L); - assertThat(highlightedSource).isEqualTo( - "/*" + LF_END_OF_LINE + - " * Header" + LF_END_OF_LINE + - " */" + LF_END_OF_LINE + - "" + LF_END_OF_LINE + - "public class HelloWorld {" + LF_END_OF_LINE + - "}" + assertThat(highlightedSource).containsExactly( + "/*", + " * Header", + " */", + "", + "public class HelloWorld {", + "}" ); } - } diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java index 158abd83c86..d47ffc3c948 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java @@ -50,6 +50,7 @@ import org.sonar.core.persistence.*; import org.sonar.core.qualitymodel.DefaultModelFinder; import org.sonar.core.resource.DefaultResourcePermissions; import org.sonar.core.rule.DefaultRuleFinder; +import org.sonar.core.source.SyntaxHighlighter; import org.sonar.core.test.TestPlanPerspectiveLoader; import org.sonar.core.test.TestablePerspectiveLoader; import org.sonar.core.timemachine.Periods; @@ -243,6 +244,7 @@ public final class Platform { servicesContainer.addSingleton(TestablePerspectiveLoader.class); servicesContainer.addSingleton(TestPlanPerspectiveLoader.class); servicesContainer.addSingleton(SnapshotPerspectives.class); + servicesContainer.addSingleton(SyntaxHighlighter.class); ServerExtensionInstaller extensionRegistrar = servicesContainer.getComponentByType(ServerExtensionInstaller.class); extensionRegistrar.registerExtensions(servicesContainer); diff --git a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java index cf13e8f8563..5ba1a4c81f7 100644 --- a/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java +++ b/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java @@ -60,6 +60,7 @@ import org.sonar.core.persistence.DryRunDatabaseFactory; import org.sonar.core.purge.PurgeDao; import org.sonar.core.resource.ResourceIndexerDao; import org.sonar.core.resource.ResourceKeyUpdaterDao; +import org.sonar.core.source.SyntaxHighlighter; import org.sonar.core.timemachine.Periods; import org.sonar.core.workflow.WorkflowEngine; import org.sonar.markdown.Markdown; @@ -574,4 +575,8 @@ public final class JRubyFacade { public MacroInterpreter getMacroInterpreter(){ return get(MacroInterpreter.class); } + + public Collection getHighlightedSourceLines(long snapshotId) { + return get(SyntaxHighlighter.class).getHighlightedSourceAsHtml(snapshotId); + } } -- 2.39.5