]> source.dussan.org Git - sonarqube.git/commitdiff
Move Syntax highlighting classes from core to sever module
authorJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 29 Jan 2014 14:04:21 +0000 (15:04 +0100)
committerJulien Lancelot <julien.lancelot@sonarsource.com>
Wed, 29 Jan 2014 15:19:32 +0000 (16:19 +0100)
27 files changed:
sonar-core/src/main/java/org/sonar/core/source/CharactersReader.java [deleted file]
sonar-core/src/main/java/org/sonar/core/source/DecorationDataHolder.java [deleted file]
sonar-core/src/main/java/org/sonar/core/source/HtmlSourceDecorator.java [deleted file]
sonar-core/src/main/java/org/sonar/core/source/HtmlTextDecorator.java [deleted file]
sonar-core/src/main/java/org/sonar/core/source/OpeningHtmlTag.java [deleted file]
sonar-core/src/test/java/org/sonar/core/source/DecorationDataHolderTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/source/HtmlSourceDecoratorTest.java [deleted file]
sonar-core/src/test/java/org/sonar/core/source/HtmlTextDecoratorTest.java [deleted file]
sonar-core/src/test/resources/org/sonar/core/source/HtmlSourceDecoratorTest/shared.xml [deleted file]
sonar-server/src/main/java/org/sonar/server/platform/Platform.java
sonar-server/src/main/java/org/sonar/server/source/CharactersReader.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/source/DecorationDataHolder.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/source/HtmlSourceDecorator.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/source/HtmlTextDecorator.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/source/OpeningHtmlTag.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/source/SourceService.java
sonar-server/src/main/java/org/sonar/server/source/ws/SourcesShowWsHandler.java
sonar-server/src/main/java/org/sonar/server/text/RubyTextService.java
sonar-server/src/main/webapp/WEB-INF/app/helpers/source_helper.rb
sonar-server/src/main/webapp/WEB-INF/app/models/snapshot.rb
sonar-server/src/test/java/org/sonar/server/source/DecorationDataHolderTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/source/HtmlSourceDecoratorTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/source/HtmlTextDecoratorTest.java [new file with mode: 0644]
sonar-server/src/test/java/org/sonar/server/source/SourceServiceTest.java
sonar-server/src/test/java/org/sonar/server/source/ws/SourcesShowWsHandlerTest.java
sonar-server/src/test/java/org/sonar/server/text/RubyTextServiceTest.java
sonar-server/src/test/resources/org/sonar/server/source/HtmlSourceDecoratorTest/shared.xml [new file with mode: 0644]

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
deleted file mode 100644 (file)
index e100bb4..0000000
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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 this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.core.source;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.util.ArrayDeque;
-import java.util.Deque;
-
-/**
- * @since 3.6
- */
-class CharactersReader {
-
-  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>();
-  }
-
-  boolean readNextChar() throws IOException {
-    previousValue = currentValue;
-    currentValue = stringBuffer.read();
-    currentIndex++;
-    return currentValue != END_OF_STREAM;
-  }
-
-  int getCurrentValue() {
-    return currentValue;
-  }
-
-  int getPreviousValue() {
-    return previousValue;
-  }
-
-  int getCurrentIndex() {
-    return currentIndex;
-  }
-
-  void registerOpenTag(String textType) {
-    openTags.push(textType);
-  }
-
-  void removeLastOpenTag() {
-    openTags.remove();
-  }
-
-  Deque<String> getOpenTags() {
-    return openTags;
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/source/DecorationDataHolder.java b/sonar-core/src/main/java/org/sonar/core/source/DecorationDataHolder.java
deleted file mode 100644 (file)
index 91246d3..0000000
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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 this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.core.source;
-
-import com.google.common.collect.Lists;
-
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.List;
-
-class DecorationDataHolder {
-
-  private static final String ENTITY_SEPARATOR = ";";
-  private static final String FIELD_SEPARATOR = ",";
-  private static final String SYMBOL_PREFIX = "sym-";
-  private static final String HIGHLIGHTABLE = "sym";
-
-  private List<OpeningHtmlTag> openingTagsEntries;
-  private int openingTagsIndex;
-  private List<Integer> closingTagsOffsets;
-  private int closingTagsIndex;
-
-  DecorationDataHolder() {
-    openingTagsEntries = Lists.newArrayList();
-    closingTagsOffsets = Lists.newArrayList();
-  }
-
-  void loadSymbolReferences(String symbolsReferences) {
-    String[] symbols = symbolsReferences.split(ENTITY_SEPARATOR);
-    for (String symbol : symbols) {
-      String[] symbolFields = symbol.split(FIELD_SEPARATOR);
-      int declarationStartOffset = Integer.parseInt(symbolFields[0]);
-      int declarationEndOffset = Integer.parseInt(symbolFields[1]);
-      int symbolLength = declarationEndOffset - declarationStartOffset;
-      String[] symbolOccurrences = Arrays.copyOfRange(symbolFields, 2, symbolFields.length);
-      loadSymbolOccurrences(declarationStartOffset, symbolLength, symbolOccurrences);
-    }
-  }
-
-  void loadSyntaxHighlightingData(String syntaxHighlightingRules) {
-    String[] rules = syntaxHighlightingRules.split(ENTITY_SEPARATOR);
-    for (String rule : rules) {
-      String[] ruleFields = rule.split(FIELD_SEPARATOR);
-      insertAndPreserveOrder(new OpeningHtmlTag(Integer.parseInt(ruleFields[0]), ruleFields[2]), openingTagsEntries);
-      insertAndPreserveOrder(Integer.parseInt(ruleFields[1]), closingTagsOffsets);
-    }
-  }
-
-  List<OpeningHtmlTag> getOpeningTagsEntries() {
-    return openingTagsEntries;
-  }
-
-  OpeningHtmlTag getCurrentOpeningTagEntry() {
-    return openingTagsIndex < openingTagsEntries.size() ? openingTagsEntries.get(openingTagsIndex) : null;
-  }
-
-  void nextOpeningTagEntry() {
-    openingTagsIndex++;
-  }
-
-  List<Integer> getClosingTagsOffsets() {
-    return closingTagsOffsets;
-  }
-
-  int getCurrentClosingTagOffset() {
-    return closingTagsIndex < closingTagsOffsets.size() ? closingTagsOffsets.get(closingTagsIndex) : -1;
-  }
-
-  void nextClosingTagOffset() {
-    closingTagsIndex++;
-  }
-
-  private void loadSymbolOccurrences(int declarationStartOffset, int symbolLength, String[] symbolOccurrences) {
-    for (String symbolOccurrence : symbolOccurrences) {
-      int occurrenceStartOffset = Integer.parseInt(symbolOccurrence);
-      int occurrenceEndOffset = occurrenceStartOffset + symbolLength;
-      insertAndPreserveOrder(new OpeningHtmlTag(occurrenceStartOffset, SYMBOL_PREFIX + declarationStartOffset + " " + HIGHLIGHTABLE), openingTagsEntries);
-      insertAndPreserveOrder(occurrenceEndOffset, closingTagsOffsets);
-    }
-  }
-
-  private void insertAndPreserveOrder(OpeningHtmlTag newEntry, List<OpeningHtmlTag> openingHtmlTags) {
-    int insertionIndex = 0;
-    Iterator<OpeningHtmlTag> tagIterator = openingHtmlTags.iterator();
-    while (tagIterator.hasNext() && tagIterator.next().getStartOffset() <= newEntry.getStartOffset()) {
-      insertionIndex++;
-    }
-    openingHtmlTags.add(insertionIndex, newEntry);
-  }
-
-  private void insertAndPreserveOrder(int newOffset, List<Integer> orderedOffsets) {
-    int insertionIndex = 0;
-    Iterator<Integer> entriesIterator = orderedOffsets.iterator();
-    while (entriesIterator.hasNext() && entriesIterator.next() <= newOffset) {
-      insertionIndex++;
-    }
-    orderedOffsets.add(insertionIndex, newOffset);
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/source/HtmlSourceDecorator.java b/sonar-core/src/main/java/org/sonar/core/source/HtmlSourceDecorator.java
deleted file mode 100644 (file)
index 4ac1654..0000000
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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 this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.core.source;
-
-import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import org.apache.ibatis.session.SqlSession;
-import org.sonar.api.ServerComponent;
-import org.sonar.core.persistence.MyBatis;
-import org.sonar.core.source.db.SnapshotDataDao;
-import org.sonar.core.source.db.SnapshotDataDto;
-import org.sonar.core.source.db.SnapshotSourceDao;
-
-import javax.annotation.CheckForNull;
-import javax.annotation.Nullable;
-
-import java.util.Collection;
-import java.util.List;
-
-public class HtmlSourceDecorator implements ServerComponent {
-
-  private final MyBatis mybatis;
-
-  private final SnapshotSourceDao snapshotSourceDao;
-  private final SnapshotDataDao snapshotDataDao;
-
-  public HtmlSourceDecorator(MyBatis mybatis, SnapshotSourceDao snapshotSourceDao, SnapshotDataDao snapshotDataDao) {
-    this.mybatis = mybatis;
-    this.snapshotSourceDao = snapshotSourceDao;
-    this.snapshotDataDao = snapshotDataDao;
-  }
-
-  @CheckForNull
-  public List<String> getDecoratedSourceAsHtml(String componentKey, @Nullable Integer from, @Nullable Integer to) {
-    SqlSession session = mybatis.openSession();
-    try {
-      Collection<SnapshotDataDto> snapshotDataEntries = snapshotDataDao.selectSnapshotDataByComponentKey(componentKey, highlightingDataTypes(), session);
-      if (!snapshotDataEntries.isEmpty()) {
-        String snapshotSource = snapshotSourceDao.selectSnapshotSourceByComponentKey(componentKey, session);
-        return decorate(snapshotSource, snapshotDataEntries, from, to);
-      }
-      return null;
-    } finally {
-      MyBatis.closeQuietly(session);
-    }
-  }
-
-  @CheckForNull
-  public List<String> getDecoratedSourceAsHtml(long snapshotId) {
-    Collection<SnapshotDataDto> snapshotDataEntries = snapshotDataDao.selectSnapshotData(snapshotId, highlightingDataTypes());
-    if (!snapshotDataEntries.isEmpty()) {
-      String snapshotSource = snapshotSourceDao.selectSnapshotSource(snapshotId);
-      if (snapshotSource != null) {
-        return decorate(snapshotSource, snapshotDataEntries, null, null);
-      }
-    }
-    return null;
-  }
-
-  @CheckForNull
-  private List<String> decorate(@Nullable String snapshotSource, Collection<SnapshotDataDto> snapshotDataEntries, @Nullable Integer from, @Nullable Integer to) {
-    if (snapshotSource != null) {
-      DecorationDataHolder decorationDataHolder = new DecorationDataHolder();
-      for (SnapshotDataDto snapshotDataEntry : snapshotDataEntries) {
-        loadSnapshotData(decorationDataHolder, snapshotDataEntry);
-      }
-
-      HtmlTextDecorator textDecorator = new HtmlTextDecorator();
-      return textDecorator.decorateTextWithHtml(snapshotSource, decorationDataHolder, from, to);
-    }
-    return null;
-  }
-
-  private List<String> highlightingDataTypes() {
-    return Lists.newArrayList(SnapshotDataTypes.SYNTAX_HIGHLIGHTING,
-      SnapshotDataTypes.SYMBOL_HIGHLIGHTING);
-  }
-
-  private void loadSnapshotData(DecorationDataHolder dataHolder, SnapshotDataDto entry) {
-    if (!Strings.isNullOrEmpty(entry.getData())) {
-      if (SnapshotDataTypes.SYNTAX_HIGHLIGHTING.equals(entry.getDataType())) {
-        dataHolder.loadSyntaxHighlightingData(entry.getData());
-      } else if (SnapshotDataTypes.SYMBOL_HIGHLIGHTING.equals(entry.getDataType())) {
-        dataHolder.loadSymbolReferences(entry.getData());
-      }
-    }
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/source/HtmlTextDecorator.java b/sonar-core/src/main/java/org/sonar/core/source/HtmlTextDecorator.java
deleted file mode 100644 (file)
index d186f1c..0000000
+++ /dev/null
@@ -1,212 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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 this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.core.source;
-
-import com.google.common.collect.Lists;
-import com.google.common.io.Closeables;
-import org.slf4j.LoggerFactory;
-
-import javax.annotation.Nullable;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.StringReader;
-import java.util.Collection;
-import java.util.List;
-
-import static org.sonar.core.source.CharactersReader.END_OF_STREAM;
-
-/**
- * @since 3.6
- */
-class HtmlTextDecorator {
-
-  static final char CR_END_OF_LINE = '\r';
-  static final char LF_END_OF_LINE = '\n';
-  static final char HTML_OPENING = '<';
-  static final char HTML_CLOSING = '>';
-  static final char AMPERSAND = '&';
-  static final String ENCODED_HTML_OPENING = "&lt;";
-  static final String ENCODED_HTML_CLOSING = "&gt;";
-  static final String ENCODED_AMPERSAND = "&amp;";
-
-  List<String> decorateTextWithHtml(String text, DecorationDataHolder decorationDataHolder) {
-    return decorateTextWithHtml(text, decorationDataHolder, null, null);
-  }
-
-  List<String> decorateTextWithHtml(String text, DecorationDataHolder decorationDataHolder, @Nullable Integer from, @Nullable Integer to) {
-
-    StringBuilder currentHtmlLine = new StringBuilder();
-    List<String> decoratedHtmlLines = Lists.newArrayList();
-    int currentLine = 1;
-
-    BufferedReader stringBuffer = null;
-    try {
-      stringBuffer = new BufferedReader(new StringReader(text));
-
-      CharactersReader charsReader = new CharactersReader(stringBuffer);
-
-      while (charsReader.readNextChar()) {
-
-        if (shouldStartNewLine(charsReader)) {
-          addLine(decoratedHtmlLines, currentHtmlLine.toString(), currentLine, from, to);
-          currentLine++;
-          currentHtmlLine = new StringBuilder();
-          if (shouldReopenPendingTags(charsReader)) {
-            reopenCurrentSyntaxTags(charsReader, currentHtmlLine);
-          }
-        }
-
-        int numberOfTagsToClose = getNumberOfTagsToClose(charsReader.getCurrentIndex(), decorationDataHolder);
-        closeCompletedTags(charsReader, numberOfTagsToClose, currentHtmlLine);
-
-        if (shouldClosePendingTags(charsReader)) {
-          closeCurrentSyntaxTags(charsReader, currentHtmlLine);
-        }
-
-        Collection<String> tagsToOpen = getTagsToOpen(charsReader.getCurrentIndex(), decorationDataHolder);
-        openNewTags(charsReader, tagsToOpen, currentHtmlLine);
-
-        if (shouldAppendCharToHtmlOutput(charsReader)) {
-          char currentChar = (char) charsReader.getCurrentValue();
-          currentHtmlLine.append(normalize(currentChar));
-        }
-      }
-
-      closeCurrentSyntaxTags(charsReader, currentHtmlLine);
-
-      if (shouldStartNewLine(charsReader)) {
-        addLine(decoratedHtmlLines, currentHtmlLine.toString(), currentLine, from, to);
-        currentLine++;
-        addLine(decoratedHtmlLines, "", currentLine, from, to);
-        currentLine++;
-      } else if (currentHtmlLine.length() > 0) {
-        addLine(decoratedHtmlLines, currentHtmlLine.toString(), currentLine, from, to);
-        currentLine++;
-      }
-
-    } catch (IOException exception) {
-      String errorMsg = "An exception occurred while highlighting the syntax of one of the project's files";
-      LoggerFactory.getLogger(HtmlTextDecorator.class).error(errorMsg);
-      throw new IllegalStateException(errorMsg, exception);
-    } finally {
-      Closeables.closeQuietly(stringBuffer);
-    }
-
-    return decoratedHtmlLines;
-  }
-
-  private void addLine(List<String> decoratedHtmlLines, String line, int currentLine, @Nullable Integer from, @Nullable Integer to) {
-    if ((from == null || currentLine >= from)
-      && (to == null || to >= currentLine)) {
-      decoratedHtmlLines.add(line);
-    }
-  }
-
-  private char[] normalize(char currentChar) {
-    char[] normalizedChars;
-    if (currentChar == HTML_OPENING) {
-      normalizedChars = ENCODED_HTML_OPENING.toCharArray();
-    } else if (currentChar == HTML_CLOSING) {
-      normalizedChars = ENCODED_HTML_CLOSING.toCharArray();
-    } else if (currentChar == AMPERSAND) {
-      normalizedChars = ENCODED_AMPERSAND.toCharArray();
-    } else {
-      normalizedChars = new char[]{currentChar};
-    }
-    return normalizedChars;
-  }
-
-  private boolean shouldAppendCharToHtmlOutput(CharactersReader charsReader) {
-    return charsReader.getCurrentValue() != CR_END_OF_LINE && charsReader.getCurrentValue() != LF_END_OF_LINE;
-  }
-
-  private int getNumberOfTagsToClose(int currentIndex, DecorationDataHolder dataHolder) {
-    int numberOfTagsToClose = 0;
-
-    while (currentIndex == dataHolder.getCurrentClosingTagOffset()) {
-      numberOfTagsToClose++;
-      dataHolder.nextClosingTagOffset();
-    }
-    return numberOfTagsToClose;
-  }
-
-  private Collection<String> getTagsToOpen(int currentIndex, DecorationDataHolder dataHolder) {
-    Collection<String> tagsToOpen = Lists.newArrayList();
-    while (dataHolder.getCurrentOpeningTagEntry() != null && currentIndex == dataHolder.getCurrentOpeningTagEntry().getStartOffset()) {
-      tagsToOpen.add(dataHolder.getCurrentOpeningTagEntry().getCssClass());
-      dataHolder.nextOpeningTagEntry();
-    }
-    return tagsToOpen;
-  }
-
-  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 charactersReader) {
-    return (charactersReader.getPreviousValue() == LF_END_OF_LINE && charactersReader.getCurrentValue() != LF_END_OF_LINE)
-      || (charactersReader.getPreviousValue() == CR_END_OF_LINE && charactersReader.getCurrentValue() != CR_END_OF_LINE
-      && charactersReader.getCurrentValue() != LF_END_OF_LINE);
-  }
-
-  private boolean shouldStartNewLine(CharactersReader charactersReader) {
-    return charactersReader.getPreviousValue() == LF_END_OF_LINE
-      || (charactersReader.getPreviousValue() == CR_END_OF_LINE && charactersReader.getCurrentValue() != LF_END_OF_LINE);
-  }
-
-  private void closeCompletedTags(CharactersReader charactersReader, int numberOfTagsToClose,
-                                  StringBuilder decoratedText) {
-    for (int i = 0; i < numberOfTagsToClose; i++) {
-      injectClosingHtml(decoratedText);
-      charactersReader.removeLastOpenTag();
-    }
-  }
-
-  private void openNewTags(CharactersReader charactersReader, Collection<String> tagsToOpen,
-                           StringBuilder decoratedText) {
-    for (String tagToOpen : tagsToOpen) {
-      injectOpeningHtmlForRule(tagToOpen, decoratedText);
-      charactersReader.registerOpenTag(tagToOpen);
-    }
-  }
-
-  private void closeCurrentSyntaxTags(CharactersReader charactersReader, StringBuilder decoratedText) {
-    for (int i = 0; i < charactersReader.getOpenTags().size(); i++) {
-      injectClosingHtml(decoratedText);
-    }
-  }
-
-  private void reopenCurrentSyntaxTags(CharactersReader charactersReader, StringBuilder decoratedText) {
-    for (String tags : charactersReader.getOpenTags()) {
-      injectOpeningHtmlForRule(tags, decoratedText);
-    }
-  }
-
-  private void injectOpeningHtmlForRule(String textType, StringBuilder decoratedText) {
-    decoratedText.append("<span class=\"").append(textType).append("\">");
-  }
-
-  private void injectClosingHtml(StringBuilder decoratedText) {
-    decoratedText.append("</span>");
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/source/OpeningHtmlTag.java b/sonar-core/src/main/java/org/sonar/core/source/OpeningHtmlTag.java
deleted file mode 100644 (file)
index 54b95e7..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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 this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-package org.sonar.core.source;
-
-class OpeningHtmlTag {
-
-  private final int startOffset;
-  private final String cssClass;
-
-  OpeningHtmlTag(int startOffset, String cssClass) {
-    this.startOffset = startOffset;
-    this.cssClass = cssClass;
-  }
-
-  int getStartOffset() {
-    return startOffset;
-  }
-
-  String getCssClass() {
-    return cssClass;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (o == null || getClass() != o.getClass()) {
-      return false;
-    }
-    return compareTo((OpeningHtmlTag) o);
-  }
-
-  @Override
-  public int hashCode() {
-    int result = startOffset;
-    result = 31 * result + (cssClass != null ? cssClass.hashCode() : 0);
-    return result;
-  }
-
-  private boolean compareTo(OpeningHtmlTag otherTag) {
-    if (startOffset != otherTag.startOffset) {
-      return false;
-    }
-    if (cssClass != null ? !cssClass.equals(otherTag.cssClass) : otherTag.cssClass != null) {
-      return false;
-    }
-    return true;
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/source/DecorationDataHolderTest.java b/sonar-core/src/test/java/org/sonar/core/source/DecorationDataHolderTest.java
deleted file mode 100644 (file)
index 62730ff..0000000
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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 this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.core.source;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import java.util.List;
-
-import static org.fest.assertions.Assertions.assertThat;
-
-public class DecorationDataHolderTest {
-
-  private static final String SAMPLE_SYNTAX_HIGHLIGHTING_RULES = "0,8,k;0,52,cppd;54,67,a;69,75,k;106,130,cppd;114,130,k;";
-  private static final String SAMPLE_SYMBOLS_REFERENCES = "80,85,80,90,140;";
-
-  private DecorationDataHolder decorationDataHolder;
-
-  @Before
-  public void setUpHighlightingContext() {
-    decorationDataHolder = new DecorationDataHolder();
-    decorationDataHolder.loadSyntaxHighlightingData(SAMPLE_SYNTAX_HIGHLIGHTING_RULES);
-    decorationDataHolder.loadSymbolReferences(SAMPLE_SYMBOLS_REFERENCES);
-  }
-
-  @Test
-  public void should_extract_lower_bounds_from_serialized_rules() throws Exception {
-
-    List<OpeningHtmlTag> openingTagsEntries = decorationDataHolder.getOpeningTagsEntries();
-
-    assertThat(openingTagsEntries.get(0)).isEqualTo(new OpeningHtmlTag(0, "k"));
-    assertThat(openingTagsEntries.get(1)).isEqualTo(new OpeningHtmlTag(0, "cppd"));
-    assertThat(openingTagsEntries.get(2)).isEqualTo(new OpeningHtmlTag(54, "a"));
-    assertThat(openingTagsEntries.get(3)).isEqualTo(new OpeningHtmlTag(69, "k"));
-    assertThat(openingTagsEntries.get(4)).isEqualTo(new OpeningHtmlTag(80, "sym-80 sym"));
-    assertThat(openingTagsEntries.get(5)).isEqualTo(new OpeningHtmlTag(90, "sym-80 sym"));
-    assertThat(openingTagsEntries.get(6)).isEqualTo(new OpeningHtmlTag(106, "cppd"));
-    assertThat(openingTagsEntries.get(7)).isEqualTo(new OpeningHtmlTag(114, "k"));
-    assertThat(openingTagsEntries.get(8)).isEqualTo(new OpeningHtmlTag(140, "sym-80 sym"));
-  }
-
-  @Test
-  public void should_extract_upper_bounds_from_serialized_rules() throws Exception {
-
-    List<Integer> offsets = decorationDataHolder.getClosingTagsOffsets();
-
-    assertThat(offsets.get(0)).isEqualTo(8);
-    assertThat(offsets.get(1)).isEqualTo(52);
-    assertThat(offsets.get(2)).isEqualTo(67);
-    assertThat(offsets.get(3)).isEqualTo(75);
-    assertThat(offsets.get(4)).isEqualTo(85);
-    assertThat(offsets.get(5)).isEqualTo(95);
-    assertThat(offsets.get(6)).isEqualTo(130);
-    assertThat(offsets.get(7)).isEqualTo(130);
-    assertThat(offsets.get(8)).isEqualTo(145);
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/source/HtmlSourceDecoratorTest.java b/sonar-core/src/test/java/org/sonar/core/source/HtmlSourceDecoratorTest.java
deleted file mode 100644 (file)
index cd623f7..0000000
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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 this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-package org.sonar.core.source;
-
-import com.google.common.collect.Lists;
-import org.apache.ibatis.session.SqlSession;
-import org.junit.Before;
-import org.junit.Test;
-import org.sonar.core.persistence.AbstractDaoTestCase;
-import org.sonar.core.persistence.MyBatis;
-import org.sonar.core.source.db.SnapshotDataDao;
-import org.sonar.core.source.db.SnapshotSourceDao;
-
-import java.util.List;
-
-import static org.fest.assertions.Assertions.assertThat;
-import static org.mockito.Mockito.*;
-
-public class HtmlSourceDecoratorTest extends AbstractDaoTestCase {
-
-
-  HtmlSourceDecorator sourceDecorator;
-
-  @Before
-  public void setUpDatasets() {
-    setupData("shared");
-
-    SnapshotSourceDao snapshotSourceDao = new SnapshotSourceDao(getMyBatis());
-    SnapshotDataDao snapshotDataDao = new SnapshotDataDao(getMyBatis());
-    sourceDecorator = new HtmlSourceDecorator(getMyBatis(), snapshotSourceDao, snapshotDataDao);
-  }
-
-  @Test
-  public void highlight_syntax_with_html() throws Exception {
-    List<String> decoratedSource = sourceDecorator.getDecoratedSourceAsHtml(11L);
-
-    assertThat(decoratedSource).containsExactly(
-      "<span class=\"cppd\">/*</span>",
-      "<span class=\"cppd\"> * Header</span>",
-      "<span class=\"cppd\"> */</span>",
-      "",
-      "<span class=\"k\">public </span><span class=\"k\">class </span>HelloWorld {",
-      "}"
-    );
-  }
-
-  @Test
-  public void highlight_syntax_with_html_from_component() throws Exception {
-    List<String> decoratedSource = sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:Dispatcher", null, null);
-
-    assertThat(decoratedSource).containsExactly(
-      "<span class=\"cppd\">/*</span>",
-      "<span class=\"cppd\"> * Header</span>",
-      "<span class=\"cppd\"> */</span>",
-      "",
-      "<span class=\"k\">public </span><span class=\"k\">class </span>HelloWorld {",
-      "}"
-    );
-  }
-
-  @Test
-  public void highlight_syntax_with_html_from_component_on_given_lines() throws Exception {
-    assertThat(sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:Dispatcher", null, 2)).hasSize(2);
-    assertThat(sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:Dispatcher", 2, null)).hasSize(5);
-    assertThat(sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:Dispatcher", 1, 2)).hasSize(2);
-  }
-
-  @Test
-  public void mark_symbols_with_html() throws Exception {
-    List<String> decoratedSource = sourceDecorator.getDecoratedSourceAsHtml(12L);
-
-    assertThat(decoratedSource).containsExactly(
-      "/*",
-      " * Header",
-      " */",
-      "",
-      "public class <span class=\"sym-31 sym\">HelloWorld</span> {",
-      "}"
-    );
-  }
-
-  @Test
-  public void mark_symbols_with_html_from_component() throws Exception {
-    List<String> decoratedSource = sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:VelocityManager", null, null);
-
-    assertThat(decoratedSource).containsExactly(
-      "/*",
-      " * Header",
-      " */",
-      "",
-      "public class <span class=\"sym-31 sym\">HelloWorld</span> {",
-      "}"
-    );
-  }
-
-  @Test
-  public void decorate_source_with_multiple_decoration_strategies() throws Exception {
-    List<String> decoratedSource = sourceDecorator.getDecoratedSourceAsHtml(13L);
-
-    assertThat(decoratedSource).containsExactly(
-      "<span class=\"cppd\">/*</span>",
-      "<span class=\"cppd\"> * Header</span>",
-      "<span class=\"cppd\"> */</span>",
-      "",
-      "<span class=\"k\">public </span><span class=\"k\">class </span><span class=\"sym-31 sym\">HelloWorld</span> {",
-      "  <span class=\"k\">public</span> <span class=\"k\">void</span> <span class=\"sym-58 sym\">foo</span>() {",
-      "  }",
-      "  <span class=\"k\">public</span> <span class=\"k\">void</span> <span class=\"sym-84 sym\">bar</span>() {",
-      "    <span class=\"sym-58 sym\">foo</span>();",
-      "  }",
-      "}"
-    );
-  }
-
-  @Test
-  public void decorate_source_with_multiple_decoration_strategies_from_component() throws Exception {
-    List<String> decoratedSource = sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:DebuggingInterceptor", null, null);
-
-    assertThat(decoratedSource).containsExactly(
-      "<span class=\"cppd\">/*</span>",
-      "<span class=\"cppd\"> * Header</span>",
-      "<span class=\"cppd\"> */</span>",
-      "",
-      "<span class=\"k\">public </span><span class=\"k\">class </span><span class=\"sym-31 sym\">HelloWorld</span> {",
-      "  <span class=\"k\">public</span> <span class=\"k\">void</span> <span class=\"sym-58 sym\">foo</span>() {",
-      "  }",
-      "  <span class=\"k\">public</span> <span class=\"k\">void</span> <span class=\"sym-84 sym\">bar</span>() {",
-      "    <span class=\"sym-58 sym\">foo</span>();",
-      "  }",
-      "}"
-    );
-  }
-
-  @Test
-  public void should_not_query_sources_if_no_snapshot_data() throws Exception {
-    SnapshotSourceDao snapshotSourceDao = mock(SnapshotSourceDao.class);
-    SnapshotDataDao snapshotDataDao = mock(SnapshotDataDao.class);
-
-    HtmlSourceDecorator sourceDecorator = new HtmlSourceDecorator(mock(MyBatis.class), snapshotSourceDao, snapshotDataDao);
-
-    sourceDecorator.getDecoratedSourceAsHtml(14L);
-
-    verify(snapshotDataDao, times(1)).selectSnapshotData(14L, Lists.newArrayList("highlight_syntax", "symbol"));
-    verify(snapshotSourceDao, times(0)).selectSnapshotSource(14L);
-  }
-
-  @Test
-  public void should_not_query_sources_if_no_snapshot_data_from_component() throws Exception {
-    SnapshotSourceDao snapshotSourceDao = mock(SnapshotSourceDao.class);
-    SnapshotDataDao snapshotDataDao = mock(SnapshotDataDao.class);
-
-    HtmlSourceDecorator sourceDecorator = new HtmlSourceDecorator(mock(MyBatis.class), snapshotSourceDao, snapshotDataDao);
-
-    sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:DebuggingInterceptor", null, null);
-
-    verify(snapshotDataDao, times(1)).selectSnapshotDataByComponentKey(eq("org.apache.struts:struts:DebuggingInterceptor"), eq(Lists.newArrayList("highlight_syntax", "symbol")),
-      any(SqlSession.class));
-    verify(snapshotSourceDao, times(0)).selectSnapshotSourceByComponentKey(eq("org.apache.struts:struts:DebuggingInterceptor"),
-      any(SqlSession.class));
-  }
-}
diff --git a/sonar-core/src/test/java/org/sonar/core/source/HtmlTextDecoratorTest.java b/sonar-core/src/test/java/org/sonar/core/source/HtmlTextDecoratorTest.java
deleted file mode 100644 (file)
index 68c0bdc..0000000
+++ /dev/null
@@ -1,404 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2013 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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 this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
- */
-
-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.HtmlTextDecorator.CR_END_OF_LINE;
-import static org.sonar.core.source.HtmlTextDecorator.LF_END_OF_LINE;
-
-public class HtmlTextDecoratorTest {
-
-  @Test
-  public void should_decorate_simple_character_range() throws Exception {
-
-    String packageDeclaration = "package org.sonar.core.source;";
-
-    DecorationDataHolder decorationData = new DecorationDataHolder();
-    decorationData.loadSyntaxHighlightingData("0,7,k;");
-
-    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
-    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(packageDeclaration, decorationData);
-
-    assertThat(htmlOutput).containsOnly("<span class=\"k\">package</span> org.sonar.core.source;");
-  }
-
-  @Test
-  public void should_decorate_multiple_lines_characters_range() throws Exception {
-
-    String firstCommentLine = "/*";
-    String secondCommentLine = " * Test";
-    String thirdCommentLine = " */";
-
-    String blockComment = firstCommentLine + LF_END_OF_LINE
-      + secondCommentLine + LF_END_OF_LINE
-      + thirdCommentLine + LF_END_OF_LINE;
-
-    DecorationDataHolder decorationData = new DecorationDataHolder();
-    decorationData.loadSyntaxHighlightingData("0,14,cppd;");
-
-    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
-    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(blockComment, decorationData);
-
-    assertThat(htmlOutput).containsExactly(
-        "<span class=\"cppd\">" + firstCommentLine + "</span>",
-        "<span class=\"cppd\">" + secondCommentLine + "</span>",
-        "<span class=\"cppd\">" + thirdCommentLine + "</span>",
-        ""
-        );
-  }
-
-  @Test
-  public void should_highlight_multiple_words_in_one_line() throws Exception {
-
-    String classDeclaration = "public class MyClass implements MyInterface {";
-
-    DecorationDataHolder decorationData = new DecorationDataHolder();
-    decorationData.loadSyntaxHighlightingData("0,6,k;7,12,k;21,31,k;");
-
-    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
-    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(classDeclaration, decorationData);
-
-    assertThat(htmlOutput).containsOnly(
-        "<span class=\"k\">public</span> " +
-          "<span class=\"k\">class</span> MyClass " +
-          "<span class=\"k\">implements</span> MyInterface {");
-  }
-
-  @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;
-
-    DecorationDataHolder decorationData = new DecorationDataHolder();
-    decorationData.loadSyntaxHighlightingData("0,184,cppd;47,53,k;");
-
-    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
-    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(javaDocSample, decorationData);
-
-    assertThat(htmlOutput).containsExactly(
-        "<span class=\"cppd\">/**</span>",
-        "<span class=\"cppd\"> * Creates a FormulaDecorator</span>",
-        "<span class=\"cppd\"> *</span>",
-        "<span class=\"cppd\"> * @param <span class=\"k\">metric</span> the metric should have an associated formula</span>",
-        "<span class=\"cppd\"> * </span>",
-        "<span class=\"cppd\"> * @throws IllegalArgumentException if no formula is associated to the metric</span>",
-        "<span class=\"cppd\"> */</span>",
-        ""
-        );
-  }
-
-  @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;
-
-    DecorationDataHolder decorationData = new DecorationDataHolder();
-    decorationData.loadSyntaxHighlightingData("0,52,cppd;54,67,a;69,75,k;106,112,k;");
-
-    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
-    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(crlfCodeSample, decorationData);
-
-    assertThat(htmlOutput).containsExactly(
-        "<span class=\"cppd\">/**</span>",
-        "<span class=\"cppd\">* @return metric generated by the decorator</span>",
-        "<span class=\"cppd\">*/</span>",
-        "<span class=\"a\">@DependedUpon</span>",
-        "<span class=\"k\">public</span> Metric generatesMetric() {",
-        "  <span class=\"k\">return</span> metric;",
-        "}",
-        ""
-        );
-  }
-
-  @Test
-  public void should_close_tags_at_end_of_file() throws Exception {
-
-    String classDeclarationSample =
-        "/*" + LF_END_OF_LINE +
-          " * Header" + LF_END_OF_LINE +
-          " */" + LF_END_OF_LINE +
-          LF_END_OF_LINE +
-          "public class HelloWorld {" + LF_END_OF_LINE +
-          "}";
-
-    DecorationDataHolder decorationData = new DecorationDataHolder();
-    decorationData.loadSyntaxHighlightingData("0,16,cppd;18,25,k;25,31,k;");
-
-    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
-    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(classDeclarationSample, decorationData);
-
-    assertThat(htmlOutput).containsExactly(
-        "<span class=\"cppd\">/*</span>",
-        "<span class=\"cppd\"> * Header</span>",
-        "<span class=\"cppd\"> */</span>",
-        "",
-        "<span class=\"k\">public </span><span class=\"k\">class </span>HelloWorld {",
-        "}"
-        );
-  }
-
-  @Test
-  public void should_escape_markup_chars() throws Exception {
-
-    String javadocWithHtml =
-        "/**\n" +
-          " * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.\n" +
-          " * \n" +
-          " * This framework can used for instance in order to :\n" +
-          " * <ul>\n" +
-          " *   <li>Create a lexer in charge to generate a list of tokens from a character stream</li>\n" +
-          " *   <li>Create a source code syntax highligther in charge to decorate a source code with HTML tags</li>\n" +
-          " *   <li>Create a javadoc generator</li>\n" +
-          " *   <li>...</li>\n" +
-          " * </ul>\n" +
-          " */\n";
-
-    DecorationDataHolder decorationData = new DecorationDataHolder();
-    decorationData.loadSyntaxHighlightingData("0,453,cppd;");
-
-    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
-    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData);
-
-    assertThat(htmlOutput).containsExactly(
-        "<span class=\"cppd\">/**</span>",
-        "<span class=\"cppd\"> * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.</span>",
-        "<span class=\"cppd\"> * </span>",
-        "<span class=\"cppd\"> * This framework can used for instance in order to :</span>",
-        "<span class=\"cppd\"> * &lt;ul&gt;</span>",
-        "<span class=\"cppd\"> *   &lt;li&gt;Create a lexer in charge to generate a list of tokens from a character stream&lt;/li&gt;</span>",
-        "<span class=\"cppd\"> *   &lt;li&gt;Create a source code syntax highligther in charge to decorate a source code with HTML tags&lt;/li&gt;</span>",
-        "<span class=\"cppd\"> *   &lt;li&gt;Create a javadoc generator&lt;/li&gt;</span>",
-        "<span class=\"cppd\"> *   &lt;li&gt;...&lt;/li&gt;</span>",
-        "<span class=\"cppd\"> * &lt;/ul&gt;</span>",
-        "<span class=\"cppd\"> */</span>",
-        "");
-  }
-
-  @Test
-  public void should_escape_ampersand_char() throws Exception {
-
-    String javadocWithAmpersandChar =
-        "/**\n" +
-          " * Definition of a dashboard.\n" +
-          " * <p/>\n" +
-          " * Its name and description can be retrieved using the i18n mechanism, using the keys \"dashboard.&lt;id&gt;.name\" and\n" +
-          " * \"dashboard.&lt;id&gt;.description\".\n" +
-          " *\n" +
-          " * @since 2.13\n" +
-          " */\n";
-
-    DecorationDataHolder decorationData = new DecorationDataHolder();
-    decorationData.loadSyntaxHighlightingData("0,220,cppd;");
-
-    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
-    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithAmpersandChar, decorationData);
-
-    assertThat(htmlOutput).containsExactly(
-        "<span class=\"cppd\">/**</span>",
-        "<span class=\"cppd\"> * Definition of a dashboard.</span>",
-        "<span class=\"cppd\"> * &lt;p/&gt;</span>",
-        "<span class=\"cppd\"> * Its name and description can be retrieved using the i18n mechanism, using the keys \"dashboard.&amp;lt;id&amp;gt;.name\" and</span>",
-        "<span class=\"cppd\"> * \"dashboard.&amp;lt;id&amp;gt;.description\".</span>",
-        "<span class=\"cppd\"> *</span>",
-        "<span class=\"cppd\"> * @since 2.13</span>",
-        "<span class=\"cppd\"> */</span>",
-        "");
-  }
-
-  @Test
-  public void should_support_cr_line_breaks() throws Exception {
-
-    String crCodeSample =
-        "/**" + CR_END_OF_LINE +
-          "* @return metric generated by the decorator" + CR_END_OF_LINE +
-          "*/" + CR_END_OF_LINE +
-          "@DependedUpon" + CR_END_OF_LINE +
-          "public Metric generatesMetric() {" + CR_END_OF_LINE +
-          "  return metric;" + CR_END_OF_LINE +
-          "}" + CR_END_OF_LINE;
-
-    DecorationDataHolder decorationData = new DecorationDataHolder();
-    decorationData.loadSyntaxHighlightingData("0,50,cppd;51,64,a;65,71,k;101,107,k;");
-
-    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
-    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(crCodeSample, decorationData);
-
-    assertThat(htmlOutput).containsExactly(
-        "<span class=\"cppd\">/**</span>",
-        "<span class=\"cppd\">* @return metric generated by the decorator</span>",
-        "<span class=\"cppd\">*/</span>",
-        "<span class=\"a\">@DependedUpon</span>",
-        "<span class=\"k\">public</span> Metric generatesMetric() {",
-        "  <span class=\"k\">return</span> metric;",
-        "}",
-        ""
-        );
-
-  }
-
-  @Test
-  public void should_support_multiple_empty_lines_at_end_of_file() throws Exception {
-
-    String classDeclarationSample =
-        "/*" + LF_END_OF_LINE +
-          " * Header" + LF_END_OF_LINE +
-          " */" + LF_END_OF_LINE +
-          LF_END_OF_LINE +
-          "public class HelloWorld {" + LF_END_OF_LINE +
-          "}" + LF_END_OF_LINE + LF_END_OF_LINE + LF_END_OF_LINE;
-
-    DecorationDataHolder decorationData = new DecorationDataHolder();
-    decorationData.loadSyntaxHighlightingData("0,16,cppd;18,25,k;25,31,k;");
-
-    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
-    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(classDeclarationSample, decorationData);
-
-    assertThat(htmlOutput).containsExactly(
-        "<span class=\"cppd\">/*</span>",
-        "<span class=\"cppd\"> * Header</span>",
-        "<span class=\"cppd\"> */</span>",
-        "",
-        "<span class=\"k\">public </span><span class=\"k\">class </span>HelloWorld {",
-        "}",
-        "",
-        "",
-        ""
-        );
-  }
-
-  @Test
-  public void begin_from_given_line() throws Exception {
-
-    String javadocWithHtml =
-      "/**\n" +
-        " * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.\n" +
-        " * \n" +
-        " * This framework can used for instance in order to :\n" +
-        " * <ul>\n" +
-        " *   <li>Create a lexer in charge to generate a list of tokens from a character stream</li>\n" +
-        " *   <li>Create a source code syntax highligther in charge to decorate a source code with HTML tags</li>\n" +
-        " *   <li>Create a javadoc generator</li>\n" +
-        " *   <li>...</li>\n" +
-        " * </ul>\n" +
-        " */\n";
-
-    DecorationDataHolder decorationData = new DecorationDataHolder();
-    decorationData.loadSyntaxHighlightingData("0,453,cppd;");
-
-    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
-    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData, 4, null);
-    assertThat(htmlOutput).hasSize(9);
-
-    // Begin from line 4
-    assertThat(htmlOutput).containsExactly(
-      "<span class=\"cppd\"> * This framework can used for instance in order to :</span>",
-      "<span class=\"cppd\"> * &lt;ul&gt;</span>",
-      "<span class=\"cppd\"> *   &lt;li&gt;Create a lexer in charge to generate a list of tokens from a character stream&lt;/li&gt;</span>",
-      "<span class=\"cppd\"> *   &lt;li&gt;Create a source code syntax highligther in charge to decorate a source code with HTML tags&lt;/li&gt;</span>",
-      "<span class=\"cppd\"> *   &lt;li&gt;Create a javadoc generator&lt;/li&gt;</span>",
-      "<span class=\"cppd\"> *   &lt;li&gt;...&lt;/li&gt;</span>",
-      "<span class=\"cppd\"> * &lt;/ul&gt;</span>",
-      "<span class=\"cppd\"> */</span>",
-      "");
-  }
-
-  @Test
-  public void end_to_given_line() throws Exception {
-
-    String javadocWithHtml =
-      "/**\n" +
-        " * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.\n" +
-        " * \n" +
-        " * This framework can used for instance in order to :\n" +
-        " * <ul>\n" +
-        " *   <li>Create a lexer in charge to generate a list of tokens from a character stream</li>\n" +
-        " *   <li>Create a source code syntax highligther in charge to decorate a source code with HTML tags</li>\n" +
-        " *   <li>Create a javadoc generator</li>\n" +
-        " *   <li>...</li>\n" +
-        " * </ul>\n" +
-        " */\n";
-
-    DecorationDataHolder decorationData = new DecorationDataHolder();
-    decorationData.loadSyntaxHighlightingData("0,453,cppd;");
-
-    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
-    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData, null, 4);
-    assertThat(htmlOutput).hasSize(4);
-
-    // End at line 4
-    assertThat(htmlOutput).containsExactly(
-      "<span class=\"cppd\">/**</span>",
-      "<span class=\"cppd\"> * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.</span>",
-      "<span class=\"cppd\"> * </span>",
-      "<span class=\"cppd\"> * This framework can used for instance in order to :</span>");
-  }
-
-  @Test
-  public void return_code_from_given_lint_given_end_line() throws Exception {
-
-    String javadocWithHtml =
-      "/**\n" +
-        " * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.\n" +
-        " * \n" +
-        " * This framework can used for instance in order to :\n" +
-        " * <ul>\n" +
-        " *   <li>Create a lexer in charge to generate a list of tokens from a character stream</li>\n" +
-        " *   <li>Create a source code syntax highligther in charge to decorate a source code with HTML tags</li>\n" +
-        " *   <li>Create a javadoc generator</li>\n" +
-        " *   <li>...</li>\n" +
-        " * </ul>\n" +
-        " */\n";
-
-    DecorationDataHolder decorationData = new DecorationDataHolder();
-    decorationData.loadSyntaxHighlightingData("0,453,cppd;");
-
-    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
-    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData, 4, 8);
-    assertThat(htmlOutput).hasSize(5);
-
-    // Begin from line 4 and finish at line 8
-    assertThat(htmlOutput).containsExactly(
-      "<span class=\"cppd\"> * This framework can used for instance in order to :</span>",
-      "<span class=\"cppd\"> * &lt;ul&gt;</span>",
-      "<span class=\"cppd\"> *   &lt;li&gt;Create a lexer in charge to generate a list of tokens from a character stream&lt;/li&gt;</span>",
-      "<span class=\"cppd\"> *   &lt;li&gt;Create a source code syntax highligther in charge to decorate a source code with HTML tags&lt;/li&gt;</span>",
-      "<span class=\"cppd\"> *   &lt;li&gt;Create a javadoc generator&lt;/li&gt;</span>"
-    );
-  }
-}
diff --git a/sonar-core/src/test/resources/org/sonar/core/source/HtmlSourceDecoratorTest/shared.xml b/sonar-core/src/test/resources/org/sonar/core/source/HtmlSourceDecoratorTest/shared.xml
deleted file mode 100644 (file)
index 5f71e15..0000000
+++ /dev/null
@@ -1,20 +0,0 @@
-<dataset>
-
-    <projects id="1" kee="org.apache.struts:struts:Dispatcher" enabled="[true]"/>
-    <projects id="2" kee="org.apache.struts:struts:VelocityManager" enabled="[true]"/>
-    <projects id="3" kee="org.apache.struts:struts:DebuggingInterceptor" enabled="[true]"/>
-
-    <snapshots id="11" project_id="1" islast="[true]" />
-    <snapshots id="12" project_id="2" islast="[true]" />
-    <snapshots id="13" project_id="3" islast="[true]" />
-    <snapshots id="14" project_id="3" islast="[true]" />
-
-    <snapshot_data id="101" resource_id="1" snapshot_id="11" snapshot_data="0,16,cppd;18,25,k;25,31,k;" data_type="highlight_syntax" />
-    <snapshot_data id="102" resource_id="2" snapshot_id="12" snapshot_data="31,41,31;" data_type="symbol" />
-    <snapshot_data id="103" resource_id="3" snapshot_id="13" snapshot_data="0,16,cppd;18,25,k;25,31,k;46,52,k;53,57,k;72,78,k;79,83,k;" data_type="highlight_syntax" />
-    <snapshot_data id="104" resource_id="3" snapshot_id="13" snapshot_data="31,41,31;58,61,58,96;84,87,84;" data_type="symbol" />
-
-    <snapshot_sources id="101" snapshot_id="11" data="/*&#10; * Header&#10; */&#10;&#10;public class HelloWorld {&#10;}" />
-    <snapshot_sources id="102" snapshot_id="12" data="/*&#10; * Header&#10; */&#10;&#10;public class HelloWorld {&#10;}" />
-    <snapshot_sources id="103" snapshot_id="13" data="/*&#10; * Header&#10; */&#10;&#10;public class HelloWorld {&#10;  public void foo() {&#10;  }&#10;  public void bar() {&#10;    foo();&#10;  }&#10;}" />
-</dataset>
index 0bb70fb04ea1f5799e6e521d06de362954a05f0b..a5f559743b6c3a0b2dde3f947ccaf69e9ec121f6 100644 (file)
@@ -58,7 +58,6 @@ import org.sonar.core.profiling.Profiling;
 import org.sonar.core.purge.PurgeProfiler;
 import org.sonar.core.resource.DefaultResourcePermissions;
 import org.sonar.core.rule.DefaultRuleFinder;
-import org.sonar.core.source.HtmlSourceDecorator;
 import org.sonar.core.technicaldebt.*;
 import org.sonar.core.test.TestPlanPerspectiveLoader;
 import org.sonar.core.test.TestablePerspectiveLoader;
@@ -98,6 +97,7 @@ import org.sonar.server.plugins.*;
 import org.sonar.server.qualityprofile.*;
 import org.sonar.server.rule.*;
 import org.sonar.server.rule.ws.*;
+import org.sonar.server.source.HtmlSourceDecorator;
 import org.sonar.server.source.SourceService;
 import org.sonar.server.source.ws.SourcesShowWsHandler;
 import org.sonar.server.source.ws.SourcesWs;
diff --git a/sonar-server/src/main/java/org/sonar/server/source/CharactersReader.java b/sonar-server/src/main/java/org/sonar/server/source/CharactersReader.java
new file mode 100644 (file)
index 0000000..c80a67c
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.source;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.ArrayDeque;
+import java.util.Deque;
+
+/**
+ * @since 3.6
+ */
+class CharactersReader {
+
+  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>();
+  }
+
+  boolean readNextChar() throws IOException {
+    previousValue = currentValue;
+    currentValue = stringBuffer.read();
+    currentIndex++;
+    return currentValue != END_OF_STREAM;
+  }
+
+  int getCurrentValue() {
+    return currentValue;
+  }
+
+  int getPreviousValue() {
+    return previousValue;
+  }
+
+  int getCurrentIndex() {
+    return currentIndex;
+  }
+
+  void registerOpenTag(String textType) {
+    openTags.push(textType);
+  }
+
+  void removeLastOpenTag() {
+    openTags.remove();
+  }
+
+  Deque<String> getOpenTags() {
+    return openTags;
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/source/DecorationDataHolder.java b/sonar-server/src/main/java/org/sonar/server/source/DecorationDataHolder.java
new file mode 100644 (file)
index 0000000..7b840a0
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.source;
+
+import com.google.common.collect.Lists;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.List;
+
+class DecorationDataHolder {
+
+  private static final String ENTITY_SEPARATOR = ";";
+  private static final String FIELD_SEPARATOR = ",";
+  private static final String SYMBOL_PREFIX = "sym-";
+  private static final String HIGHLIGHTABLE = "sym";
+
+  private List<OpeningHtmlTag> openingTagsEntries;
+  private int openingTagsIndex;
+  private List<Integer> closingTagsOffsets;
+  private int closingTagsIndex;
+
+  DecorationDataHolder() {
+    openingTagsEntries = Lists.newArrayList();
+    closingTagsOffsets = Lists.newArrayList();
+  }
+
+  void loadSymbolReferences(String symbolsReferences) {
+    String[] symbols = symbolsReferences.split(ENTITY_SEPARATOR);
+    for (String symbol : symbols) {
+      String[] symbolFields = symbol.split(FIELD_SEPARATOR);
+      int declarationStartOffset = Integer.parseInt(symbolFields[0]);
+      int declarationEndOffset = Integer.parseInt(symbolFields[1]);
+      int symbolLength = declarationEndOffset - declarationStartOffset;
+      String[] symbolOccurrences = Arrays.copyOfRange(symbolFields, 2, symbolFields.length);
+      loadSymbolOccurrences(declarationStartOffset, symbolLength, symbolOccurrences);
+    }
+  }
+
+  void loadSyntaxHighlightingData(String syntaxHighlightingRules) {
+    String[] rules = syntaxHighlightingRules.split(ENTITY_SEPARATOR);
+    for (String rule : rules) {
+      String[] ruleFields = rule.split(FIELD_SEPARATOR);
+      insertAndPreserveOrder(new OpeningHtmlTag(Integer.parseInt(ruleFields[0]), ruleFields[2]), openingTagsEntries);
+      insertAndPreserveOrder(Integer.parseInt(ruleFields[1]), closingTagsOffsets);
+    }
+  }
+
+  List<OpeningHtmlTag> getOpeningTagsEntries() {
+    return openingTagsEntries;
+  }
+
+  OpeningHtmlTag getCurrentOpeningTagEntry() {
+    return openingTagsIndex < openingTagsEntries.size() ? openingTagsEntries.get(openingTagsIndex) : null;
+  }
+
+  void nextOpeningTagEntry() {
+    openingTagsIndex++;
+  }
+
+  List<Integer> getClosingTagsOffsets() {
+    return closingTagsOffsets;
+  }
+
+  int getCurrentClosingTagOffset() {
+    return closingTagsIndex < closingTagsOffsets.size() ? closingTagsOffsets.get(closingTagsIndex) : -1;
+  }
+
+  void nextClosingTagOffset() {
+    closingTagsIndex++;
+  }
+
+  private void loadSymbolOccurrences(int declarationStartOffset, int symbolLength, String[] symbolOccurrences) {
+    for (String symbolOccurrence : symbolOccurrences) {
+      int occurrenceStartOffset = Integer.parseInt(symbolOccurrence);
+      int occurrenceEndOffset = occurrenceStartOffset + symbolLength;
+      insertAndPreserveOrder(new OpeningHtmlTag(occurrenceStartOffset, SYMBOL_PREFIX + declarationStartOffset + " " + HIGHLIGHTABLE), openingTagsEntries);
+      insertAndPreserveOrder(occurrenceEndOffset, closingTagsOffsets);
+    }
+  }
+
+  private void insertAndPreserveOrder(OpeningHtmlTag newEntry, List<OpeningHtmlTag> openingHtmlTags) {
+    int insertionIndex = 0;
+    Iterator<OpeningHtmlTag> tagIterator = openingHtmlTags.iterator();
+    while (tagIterator.hasNext() && tagIterator.next().getStartOffset() <= newEntry.getStartOffset()) {
+      insertionIndex++;
+    }
+    openingHtmlTags.add(insertionIndex, newEntry);
+  }
+
+  private void insertAndPreserveOrder(int newOffset, List<Integer> orderedOffsets) {
+    int insertionIndex = 0;
+    Iterator<Integer> entriesIterator = orderedOffsets.iterator();
+    while (entriesIterator.hasNext() && entriesIterator.next() <= newOffset) {
+      insertionIndex++;
+    }
+    orderedOffsets.add(insertionIndex, newOffset);
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/source/HtmlSourceDecorator.java b/sonar-server/src/main/java/org/sonar/server/source/HtmlSourceDecorator.java
new file mode 100644 (file)
index 0000000..e63bfa7
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.source;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import org.apache.ibatis.session.SqlSession;
+import org.sonar.api.ServerComponent;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.core.source.SnapshotDataTypes;
+import org.sonar.core.source.db.SnapshotDataDao;
+import org.sonar.core.source.db.SnapshotDataDto;
+import org.sonar.core.source.db.SnapshotSourceDao;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+
+import java.util.Collection;
+import java.util.List;
+
+public class HtmlSourceDecorator implements ServerComponent {
+
+  private final MyBatis mybatis;
+
+  private final SnapshotSourceDao snapshotSourceDao;
+  private final SnapshotDataDao snapshotDataDao;
+
+  public HtmlSourceDecorator(MyBatis mybatis, SnapshotSourceDao snapshotSourceDao, SnapshotDataDao snapshotDataDao) {
+    this.mybatis = mybatis;
+    this.snapshotSourceDao = snapshotSourceDao;
+    this.snapshotDataDao = snapshotDataDao;
+  }
+
+  @CheckForNull
+  public List<String> getDecoratedSourceAsHtml(String componentKey, @Nullable Integer from, @Nullable Integer to) {
+    SqlSession session = mybatis.openSession();
+    try {
+      Collection<SnapshotDataDto> snapshotDataEntries = snapshotDataDao.selectSnapshotDataByComponentKey(componentKey, highlightingDataTypes(), session);
+      if (!snapshotDataEntries.isEmpty()) {
+        String snapshotSource = snapshotSourceDao.selectSnapshotSourceByComponentKey(componentKey, session);
+        return decorate(snapshotSource, snapshotDataEntries, from, to);
+      }
+      return null;
+    } finally {
+      MyBatis.closeQuietly(session);
+    }
+  }
+
+  @CheckForNull
+  public List<String> getDecoratedSourceAsHtml(long snapshotId) {
+    Collection<SnapshotDataDto> snapshotDataEntries = snapshotDataDao.selectSnapshotData(snapshotId, highlightingDataTypes());
+    if (!snapshotDataEntries.isEmpty()) {
+      String snapshotSource = snapshotSourceDao.selectSnapshotSource(snapshotId);
+      if (snapshotSource != null) {
+        return decorate(snapshotSource, snapshotDataEntries, null, null);
+      }
+    }
+    return null;
+  }
+
+  @CheckForNull
+  private List<String> decorate(@Nullable String snapshotSource, Collection<SnapshotDataDto> snapshotDataEntries, @Nullable Integer from, @Nullable Integer to) {
+    if (snapshotSource != null) {
+      DecorationDataHolder decorationDataHolder = new DecorationDataHolder();
+      for (SnapshotDataDto snapshotDataEntry : snapshotDataEntries) {
+        loadSnapshotData(decorationDataHolder, snapshotDataEntry);
+      }
+
+      HtmlTextDecorator textDecorator = new HtmlTextDecorator();
+      return textDecorator.decorateTextWithHtml(snapshotSource, decorationDataHolder, from, to);
+    }
+    return null;
+  }
+
+  private List<String> highlightingDataTypes() {
+    return Lists.newArrayList(SnapshotDataTypes.SYNTAX_HIGHLIGHTING,
+      SnapshotDataTypes.SYMBOL_HIGHLIGHTING);
+  }
+
+  private void loadSnapshotData(DecorationDataHolder dataHolder, SnapshotDataDto entry) {
+    if (!Strings.isNullOrEmpty(entry.getData())) {
+      if (SnapshotDataTypes.SYNTAX_HIGHLIGHTING.equals(entry.getDataType())) {
+        dataHolder.loadSyntaxHighlightingData(entry.getData());
+      } else if (SnapshotDataTypes.SYMBOL_HIGHLIGHTING.equals(entry.getDataType())) {
+        dataHolder.loadSymbolReferences(entry.getData());
+      }
+    }
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/source/HtmlTextDecorator.java b/sonar-server/src/main/java/org/sonar/server/source/HtmlTextDecorator.java
new file mode 100644 (file)
index 0000000..41ca1ba
--- /dev/null
@@ -0,0 +1,212 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.source;
+
+import com.google.common.collect.Lists;
+import com.google.common.io.Closeables;
+import org.slf4j.LoggerFactory;
+
+import javax.annotation.Nullable;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.StringReader;
+import java.util.Collection;
+import java.util.List;
+
+
+
+/**
+ * @since 3.6
+ */
+class HtmlTextDecorator {
+
+  static final char CR_END_OF_LINE = '\r';
+  static final char LF_END_OF_LINE = '\n';
+  static final char HTML_OPENING = '<';
+  static final char HTML_CLOSING = '>';
+  static final char AMPERSAND = '&';
+  static final String ENCODED_HTML_OPENING = "&lt;";
+  static final String ENCODED_HTML_CLOSING = "&gt;";
+  static final String ENCODED_AMPERSAND = "&amp;";
+
+  List<String> decorateTextWithHtml(String text, DecorationDataHolder decorationDataHolder) {
+    return decorateTextWithHtml(text, decorationDataHolder, null, null);
+  }
+
+  List<String> decorateTextWithHtml(String text, DecorationDataHolder decorationDataHolder, @Nullable Integer from, @Nullable Integer to) {
+
+    StringBuilder currentHtmlLine = new StringBuilder();
+    List<String> decoratedHtmlLines = Lists.newArrayList();
+    int currentLine = 1;
+
+    BufferedReader stringBuffer = null;
+    try {
+      stringBuffer = new BufferedReader(new StringReader(text));
+
+      CharactersReader charsReader = new CharactersReader(stringBuffer);
+
+      while (charsReader.readNextChar()) {
+
+        if (shouldStartNewLine(charsReader)) {
+          addLine(decoratedHtmlLines, currentHtmlLine.toString(), currentLine, from, to);
+          currentLine++;
+          currentHtmlLine = new StringBuilder();
+          if (shouldReopenPendingTags(charsReader)) {
+            reopenCurrentSyntaxTags(charsReader, currentHtmlLine);
+          }
+        }
+
+        int numberOfTagsToClose = getNumberOfTagsToClose(charsReader.getCurrentIndex(), decorationDataHolder);
+        closeCompletedTags(charsReader, numberOfTagsToClose, currentHtmlLine);
+
+        if (shouldClosePendingTags(charsReader)) {
+          closeCurrentSyntaxTags(charsReader, currentHtmlLine);
+        }
+
+        Collection<String> tagsToOpen = getTagsToOpen(charsReader.getCurrentIndex(), decorationDataHolder);
+        openNewTags(charsReader, tagsToOpen, currentHtmlLine);
+
+        if (shouldAppendCharToHtmlOutput(charsReader)) {
+          char currentChar = (char) charsReader.getCurrentValue();
+          currentHtmlLine.append(normalize(currentChar));
+        }
+      }
+
+      closeCurrentSyntaxTags(charsReader, currentHtmlLine);
+
+      if (shouldStartNewLine(charsReader)) {
+        addLine(decoratedHtmlLines, currentHtmlLine.toString(), currentLine, from, to);
+        currentLine++;
+        addLine(decoratedHtmlLines, "", currentLine, from, to);
+        currentLine++;
+      } else if (currentHtmlLine.length() > 0) {
+        addLine(decoratedHtmlLines, currentHtmlLine.toString(), currentLine, from, to);
+        currentLine++;
+      }
+
+    } catch (IOException exception) {
+      String errorMsg = "An exception occurred while highlighting the syntax of one of the project's files";
+      LoggerFactory.getLogger(HtmlTextDecorator.class).error(errorMsg);
+      throw new IllegalStateException(errorMsg, exception);
+    } finally {
+      Closeables.closeQuietly(stringBuffer);
+    }
+
+    return decoratedHtmlLines;
+  }
+
+  private void addLine(List<String> decoratedHtmlLines, String line, int currentLine, @Nullable Integer from, @Nullable Integer to) {
+    if ((from == null || currentLine >= from)
+      && (to == null || to >= currentLine)) {
+      decoratedHtmlLines.add(line);
+    }
+  }
+
+  private char[] normalize(char currentChar) {
+    char[] normalizedChars;
+    if (currentChar == HTML_OPENING) {
+      normalizedChars = ENCODED_HTML_OPENING.toCharArray();
+    } else if (currentChar == HTML_CLOSING) {
+      normalizedChars = ENCODED_HTML_CLOSING.toCharArray();
+    } else if (currentChar == AMPERSAND) {
+      normalizedChars = ENCODED_AMPERSAND.toCharArray();
+    } else {
+      normalizedChars = new char[]{currentChar};
+    }
+    return normalizedChars;
+  }
+
+  private boolean shouldAppendCharToHtmlOutput(CharactersReader charsReader) {
+    return charsReader.getCurrentValue() != CR_END_OF_LINE && charsReader.getCurrentValue() != LF_END_OF_LINE;
+  }
+
+  private int getNumberOfTagsToClose(int currentIndex, DecorationDataHolder dataHolder) {
+    int numberOfTagsToClose = 0;
+
+    while (currentIndex == dataHolder.getCurrentClosingTagOffset()) {
+      numberOfTagsToClose++;
+      dataHolder.nextClosingTagOffset();
+    }
+    return numberOfTagsToClose;
+  }
+
+  private Collection<String> getTagsToOpen(int currentIndex, DecorationDataHolder dataHolder) {
+    Collection<String> tagsToOpen = Lists.newArrayList();
+    while (dataHolder.getCurrentOpeningTagEntry() != null && currentIndex == dataHolder.getCurrentOpeningTagEntry().getStartOffset()) {
+      tagsToOpen.add(dataHolder.getCurrentOpeningTagEntry().getCssClass());
+      dataHolder.nextOpeningTagEntry();
+    }
+    return tagsToOpen;
+  }
+
+  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() == CharactersReader.END_OF_STREAM && charactersReader.getPreviousValue() != LF_END_OF_LINE);
+  }
+
+  private boolean shouldReopenPendingTags(CharactersReader charactersReader) {
+    return (charactersReader.getPreviousValue() == LF_END_OF_LINE && charactersReader.getCurrentValue() != LF_END_OF_LINE)
+      || (charactersReader.getPreviousValue() == CR_END_OF_LINE && charactersReader.getCurrentValue() != CR_END_OF_LINE
+      && charactersReader.getCurrentValue() != LF_END_OF_LINE);
+  }
+
+  private boolean shouldStartNewLine(CharactersReader charactersReader) {
+    return charactersReader.getPreviousValue() == LF_END_OF_LINE
+      || (charactersReader.getPreviousValue() == CR_END_OF_LINE && charactersReader.getCurrentValue() != LF_END_OF_LINE);
+  }
+
+  private void closeCompletedTags(CharactersReader charactersReader, int numberOfTagsToClose,
+                                  StringBuilder decoratedText) {
+    for (int i = 0; i < numberOfTagsToClose; i++) {
+      injectClosingHtml(decoratedText);
+      charactersReader.removeLastOpenTag();
+    }
+  }
+
+  private void openNewTags(CharactersReader charactersReader, Collection<String> tagsToOpen,
+                           StringBuilder decoratedText) {
+    for (String tagToOpen : tagsToOpen) {
+      injectOpeningHtmlForRule(tagToOpen, decoratedText);
+      charactersReader.registerOpenTag(tagToOpen);
+    }
+  }
+
+  private void closeCurrentSyntaxTags(CharactersReader charactersReader, StringBuilder decoratedText) {
+    for (int i = 0; i < charactersReader.getOpenTags().size(); i++) {
+      injectClosingHtml(decoratedText);
+    }
+  }
+
+  private void reopenCurrentSyntaxTags(CharactersReader charactersReader, StringBuilder decoratedText) {
+    for (String tags : charactersReader.getOpenTags()) {
+      injectOpeningHtmlForRule(tags, decoratedText);
+    }
+  }
+
+  private void injectOpeningHtmlForRule(String textType, StringBuilder decoratedText) {
+    decoratedText.append("<span class=\"").append(textType).append("\">");
+  }
+
+  private void injectClosingHtml(StringBuilder decoratedText) {
+    decoratedText.append("</span>");
+  }
+}
diff --git a/sonar-server/src/main/java/org/sonar/server/source/OpeningHtmlTag.java b/sonar-server/src/main/java/org/sonar/server/source/OpeningHtmlTag.java
new file mode 100644 (file)
index 0000000..b45b950
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+package org.sonar.server.source;
+
+class OpeningHtmlTag {
+
+  private final int startOffset;
+  private final String cssClass;
+
+  OpeningHtmlTag(int startOffset, String cssClass) {
+    this.startOffset = startOffset;
+    this.cssClass = cssClass;
+  }
+
+  int getStartOffset() {
+    return startOffset;
+  }
+
+  String getCssClass() {
+    return cssClass;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+    return compareTo((OpeningHtmlTag) o);
+  }
+
+  @Override
+  public int hashCode() {
+    int result = startOffset;
+    result = 31 * result + (cssClass != null ? cssClass.hashCode() : 0);
+    return result;
+  }
+
+  private boolean compareTo(OpeningHtmlTag otherTag) {
+    if (startOffset != otherTag.startOffset) {
+      return false;
+    }
+    if (cssClass != null ? !cssClass.equals(otherTag.cssClass) : otherTag.cssClass != null) {
+      return false;
+    }
+    return true;
+  }
+}
index f2472b273df6e023f07cf59e569e424785c0a9ff..ab17c9e4419f2b0e14a794afd8d2ae75cdb63e88 100644 (file)
 package org.sonar.server.source;
 
 import org.sonar.api.ServerComponent;
+import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.measure.db.MeasureDataDao;
 import org.sonar.core.measure.db.MeasureDataDto;
 import org.sonar.core.resource.ResourceDao;
 import org.sonar.core.resource.ResourceDto;
-import org.sonar.core.source.HtmlSourceDecorator;
 import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.ui.CodeColorizers;
 import org.sonar.server.user.UserSession;
 
 import javax.annotation.CheckForNull;
@@ -37,12 +38,20 @@ import java.util.List;
 
 public class SourceService implements ServerComponent {
 
+
   private final HtmlSourceDecorator sourceDecorator;
+
+  /**
+   * Old service to colorize code
+   */
+  private final CodeColorizers codeColorizers;
+
   private final ResourceDao resourceDao;
   private final MeasureDataDao measureDataDao;
 
-  public SourceService(HtmlSourceDecorator sourceDecorator, ResourceDao resourceDao, MeasureDataDao measureDataDao) {
+  public SourceService(HtmlSourceDecorator sourceDecorator, CodeColorizers codeColorizers, ResourceDao resourceDao, MeasureDataDao measureDataDao) {
     this.sourceDecorator = sourceDecorator;
+    this.codeColorizers = codeColorizers;
     this.resourceDao = resourceDao;
     this.measureDataDao = measureDataDao;
   }
@@ -60,9 +69,18 @@ public class SourceService implements ServerComponent {
     return sourceDecorator.getDecoratedSourceAsHtml(componentKey, from, to);
   }
 
-  // TODO move this in another service
   @CheckForNull
-  public String findDataFromComponent(String componentKey, String metricKey){
+  public String getScmAuthorData(String componentKey) {
+    return findDataFromComponent(componentKey, CoreMetrics.SCM_AUTHORS_BY_LINE_KEY);
+  }
+
+  @CheckForNull
+  public String getScmDateData(String componentKey) {
+    return findDataFromComponent(componentKey, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY);
+  }
+
+  @CheckForNull
+  private String findDataFromComponent(String componentKey, String metricKey) {
     MeasureDataDto data = measureDataDao.findByComponentKeyAndMetricKey(componentKey, metricKey);
     if (data != null) {
       return data.getText();
index e087f62bf464c7c7a3523663b60386138533ab58..b4ee35ac3dab30cacc8c1110a07b7c64bd4aebb6 100644 (file)
@@ -21,7 +21,6 @@
 package org.sonar.server.source.ws;
 
 import com.google.common.base.Splitter;
-import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.RequestHandler;
 import org.sonar.api.server.ws.Response;
@@ -52,8 +51,8 @@ public class SourcesShowWsHandler implements RequestHandler {
       throw new NotFoundException("Component : " + componentKey + " has no source.");
     }
 
-    String scmAuthorData = sourceService.findDataFromComponent(componentKey, CoreMetrics.SCM_AUTHORS_BY_LINE_KEY);
-    String scmDataData = sourceService.findDataFromComponent(componentKey, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY);
+    String scmAuthorData = sourceService.getScmAuthorData(componentKey);
+    String scmDataData = sourceService.getScmDateData(componentKey);
 
     int from = fromParam != null ? fromParam : 1;
     int to = toParam != null ? toParam : sourceHtml.size() + from;
index 135a28731ad608767e40a5a873894965c76cf04e..2206e6c4c207fd5bfb62a2e2924797907a5f69a7 100644 (file)
@@ -21,8 +21,8 @@ package org.sonar.server.text;
 
 import org.apache.commons.lang.StringEscapeUtils;
 import org.sonar.api.ServerComponent;
-import org.sonar.core.source.HtmlSourceDecorator;
 import org.sonar.markdown.Markdown;
+import org.sonar.server.source.HtmlSourceDecorator;
 
 import java.util.List;
 
index 914629a5040dd18fb786a8d5a821cdf1b7613268..1d671555819984764b4eb178979bf83a30d2dd3d 100644 (file)
@@ -73,7 +73,7 @@ module SourceHelper
       end
 
       panel.html_lines=[]
-      html_source_lines = snapshot.highlighted_source_lines || snapshot.source.syntax_highlighted_lines()
+      html_source_lines = Internal.text.highlightedSourceLines(snapshot.id) || snapshot.source.syntax_highlighted_lines()
       line_range=sanitize_range(options[:line_range], 1..html_source_lines.length)
 
       html_source_lines.each_with_index do |source, index|
index 583c3ab9884919df211da80572bbab423b11f189..b0d2c4061257b044b6666dc6043665831e4fa715 100644 (file)
@@ -251,10 +251,6 @@ class Snapshot < ActiveRecord::Base
       end
   end
 
-  def highlighted_source_lines
-    Internal.text.highlightedSourceLines(id)
-  end
-
   def has_source
     SnapshotSource.count('id', :conditions => "snapshot_id = #{id}") > 0
   end
diff --git a/sonar-server/src/test/java/org/sonar/server/source/DecorationDataHolderTest.java b/sonar-server/src/test/java/org/sonar/server/source/DecorationDataHolderTest.java
new file mode 100644 (file)
index 0000000..507fc3e
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.source;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+
+public class DecorationDataHolderTest {
+
+  private static final String SAMPLE_SYNTAX_HIGHLIGHTING_RULES = "0,8,k;0,52,cppd;54,67,a;69,75,k;106,130,cppd;114,130,k;";
+  private static final String SAMPLE_SYMBOLS_REFERENCES = "80,85,80,90,140;";
+
+  private DecorationDataHolder decorationDataHolder;
+
+  @Before
+  public void setUpHighlightingContext() {
+    decorationDataHolder = new DecorationDataHolder();
+    decorationDataHolder.loadSyntaxHighlightingData(SAMPLE_SYNTAX_HIGHLIGHTING_RULES);
+    decorationDataHolder.loadSymbolReferences(SAMPLE_SYMBOLS_REFERENCES);
+  }
+
+  @Test
+  public void should_extract_lower_bounds_from_serialized_rules() throws Exception {
+
+    List<OpeningHtmlTag> openingTagsEntries = decorationDataHolder.getOpeningTagsEntries();
+
+    assertThat(openingTagsEntries.get(0)).isEqualTo(new OpeningHtmlTag(0, "k"));
+    assertThat(openingTagsEntries.get(1)).isEqualTo(new OpeningHtmlTag(0, "cppd"));
+    assertThat(openingTagsEntries.get(2)).isEqualTo(new OpeningHtmlTag(54, "a"));
+    assertThat(openingTagsEntries.get(3)).isEqualTo(new OpeningHtmlTag(69, "k"));
+    assertThat(openingTagsEntries.get(4)).isEqualTo(new OpeningHtmlTag(80, "sym-80 sym"));
+    assertThat(openingTagsEntries.get(5)).isEqualTo(new OpeningHtmlTag(90, "sym-80 sym"));
+    assertThat(openingTagsEntries.get(6)).isEqualTo(new OpeningHtmlTag(106, "cppd"));
+    assertThat(openingTagsEntries.get(7)).isEqualTo(new OpeningHtmlTag(114, "k"));
+    assertThat(openingTagsEntries.get(8)).isEqualTo(new OpeningHtmlTag(140, "sym-80 sym"));
+  }
+
+  @Test
+  public void should_extract_upper_bounds_from_serialized_rules() throws Exception {
+
+    List<Integer> offsets = decorationDataHolder.getClosingTagsOffsets();
+
+    assertThat(offsets.get(0)).isEqualTo(8);
+    assertThat(offsets.get(1)).isEqualTo(52);
+    assertThat(offsets.get(2)).isEqualTo(67);
+    assertThat(offsets.get(3)).isEqualTo(75);
+    assertThat(offsets.get(4)).isEqualTo(85);
+    assertThat(offsets.get(5)).isEqualTo(95);
+    assertThat(offsets.get(6)).isEqualTo(130);
+    assertThat(offsets.get(7)).isEqualTo(130);
+    assertThat(offsets.get(8)).isEqualTo(145);
+  }
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/source/HtmlSourceDecoratorTest.java b/sonar-server/src/test/java/org/sonar/server/source/HtmlSourceDecoratorTest.java
new file mode 100644 (file)
index 0000000..113c87f
--- /dev/null
@@ -0,0 +1,179 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.source;
+
+import com.google.common.collect.Lists;
+import org.apache.ibatis.session.SqlSession;
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.core.persistence.AbstractDaoTestCase;
+import org.sonar.core.persistence.MyBatis;
+import org.sonar.core.source.db.SnapshotDataDao;
+import org.sonar.core.source.db.SnapshotSourceDao;
+
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.mockito.Mockito.*;
+
+public class HtmlSourceDecoratorTest extends AbstractDaoTestCase {
+
+
+  HtmlSourceDecorator sourceDecorator;
+
+  @Before
+  public void setUpDatasets() {
+    setupData("shared");
+
+    SnapshotSourceDao snapshotSourceDao = new SnapshotSourceDao(getMyBatis());
+    SnapshotDataDao snapshotDataDao = new SnapshotDataDao(getMyBatis());
+    sourceDecorator = new HtmlSourceDecorator(getMyBatis(), snapshotSourceDao, snapshotDataDao);
+  }
+
+  @Test
+  public void highlight_syntax_with_html() throws Exception {
+    List<String> decoratedSource = sourceDecorator.getDecoratedSourceAsHtml(11L);
+
+    assertThat(decoratedSource).containsExactly(
+      "<span class=\"cppd\">/*</span>",
+      "<span class=\"cppd\"> * Header</span>",
+      "<span class=\"cppd\"> */</span>",
+      "",
+      "<span class=\"k\">public </span><span class=\"k\">class </span>HelloWorld {",
+      "}"
+    );
+  }
+
+  @Test
+  public void highlight_syntax_with_html_from_component() throws Exception {
+    List<String> decoratedSource = sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:Dispatcher", null, null);
+
+    assertThat(decoratedSource).containsExactly(
+      "<span class=\"cppd\">/*</span>",
+      "<span class=\"cppd\"> * Header</span>",
+      "<span class=\"cppd\"> */</span>",
+      "",
+      "<span class=\"k\">public </span><span class=\"k\">class </span>HelloWorld {",
+      "}"
+    );
+  }
+
+  @Test
+  public void highlight_syntax_with_html_from_component_on_given_lines() throws Exception {
+    assertThat(sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:Dispatcher", null, 2)).hasSize(2);
+    assertThat(sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:Dispatcher", 2, null)).hasSize(5);
+    assertThat(sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:Dispatcher", 1, 2)).hasSize(2);
+  }
+
+  @Test
+  public void mark_symbols_with_html() throws Exception {
+    List<String> decoratedSource = sourceDecorator.getDecoratedSourceAsHtml(12L);
+
+    assertThat(decoratedSource).containsExactly(
+      "/*",
+      " * Header",
+      " */",
+      "",
+      "public class <span class=\"sym-31 sym\">HelloWorld</span> {",
+      "}"
+    );
+  }
+
+  @Test
+  public void mark_symbols_with_html_from_component() throws Exception {
+    List<String> decoratedSource = sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:VelocityManager", null, null);
+
+    assertThat(decoratedSource).containsExactly(
+      "/*",
+      " * Header",
+      " */",
+      "",
+      "public class <span class=\"sym-31 sym\">HelloWorld</span> {",
+      "}"
+    );
+  }
+
+  @Test
+  public void decorate_source_with_multiple_decoration_strategies() throws Exception {
+    List<String> decoratedSource = sourceDecorator.getDecoratedSourceAsHtml(13L);
+
+    assertThat(decoratedSource).containsExactly(
+      "<span class=\"cppd\">/*</span>",
+      "<span class=\"cppd\"> * Header</span>",
+      "<span class=\"cppd\"> */</span>",
+      "",
+      "<span class=\"k\">public </span><span class=\"k\">class </span><span class=\"sym-31 sym\">HelloWorld</span> {",
+      "  <span class=\"k\">public</span> <span class=\"k\">void</span> <span class=\"sym-58 sym\">foo</span>() {",
+      "  }",
+      "  <span class=\"k\">public</span> <span class=\"k\">void</span> <span class=\"sym-84 sym\">bar</span>() {",
+      "    <span class=\"sym-58 sym\">foo</span>();",
+      "  }",
+      "}"
+    );
+  }
+
+  @Test
+  public void decorate_source_with_multiple_decoration_strategies_from_component() throws Exception {
+    List<String> decoratedSource = sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:DebuggingInterceptor", null, null);
+
+    assertThat(decoratedSource).containsExactly(
+      "<span class=\"cppd\">/*</span>",
+      "<span class=\"cppd\"> * Header</span>",
+      "<span class=\"cppd\"> */</span>",
+      "",
+      "<span class=\"k\">public </span><span class=\"k\">class </span><span class=\"sym-31 sym\">HelloWorld</span> {",
+      "  <span class=\"k\">public</span> <span class=\"k\">void</span> <span class=\"sym-58 sym\">foo</span>() {",
+      "  }",
+      "  <span class=\"k\">public</span> <span class=\"k\">void</span> <span class=\"sym-84 sym\">bar</span>() {",
+      "    <span class=\"sym-58 sym\">foo</span>();",
+      "  }",
+      "}"
+    );
+  }
+
+  @Test
+  public void should_not_query_sources_if_no_snapshot_data() throws Exception {
+    SnapshotSourceDao snapshotSourceDao = mock(SnapshotSourceDao.class);
+    SnapshotDataDao snapshotDataDao = mock(SnapshotDataDao.class);
+
+    HtmlSourceDecorator sourceDecorator = new HtmlSourceDecorator(mock(MyBatis.class), snapshotSourceDao, snapshotDataDao);
+
+    sourceDecorator.getDecoratedSourceAsHtml(14L);
+
+    verify(snapshotDataDao, times(1)).selectSnapshotData(14L, Lists.newArrayList("highlight_syntax", "symbol"));
+    verify(snapshotSourceDao, times(0)).selectSnapshotSource(14L);
+  }
+
+  @Test
+  public void should_not_query_sources_if_no_snapshot_data_from_component() throws Exception {
+    SnapshotSourceDao snapshotSourceDao = mock(SnapshotSourceDao.class);
+    SnapshotDataDao snapshotDataDao = mock(SnapshotDataDao.class);
+
+    HtmlSourceDecorator sourceDecorator = new HtmlSourceDecorator(mock(MyBatis.class), snapshotSourceDao, snapshotDataDao);
+
+    sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:DebuggingInterceptor", null, null);
+
+    verify(snapshotDataDao, times(1)).selectSnapshotDataByComponentKey(eq("org.apache.struts:struts:DebuggingInterceptor"), eq(Lists.newArrayList("highlight_syntax", "symbol")),
+      any(SqlSession.class));
+    verify(snapshotSourceDao, times(0)).selectSnapshotSourceByComponentKey(eq("org.apache.struts:struts:DebuggingInterceptor"),
+      any(SqlSession.class));
+  }
+}
diff --git a/sonar-server/src/test/java/org/sonar/server/source/HtmlTextDecoratorTest.java b/sonar-server/src/test/java/org/sonar/server/source/HtmlTextDecoratorTest.java
new file mode 100644 (file)
index 0000000..3e5de01
--- /dev/null
@@ -0,0 +1,404 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2013 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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 this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+ */
+
+package org.sonar.server.source;
+
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.sonar.server.source.HtmlTextDecorator.CR_END_OF_LINE;
+import static org.sonar.server.source.HtmlTextDecorator.LF_END_OF_LINE;
+
+public class HtmlTextDecoratorTest {
+
+  @Test
+  public void should_decorate_simple_character_range() throws Exception {
+
+    String packageDeclaration = "package org.sonar.core.source;";
+
+    DecorationDataHolder decorationData = new DecorationDataHolder();
+    decorationData.loadSyntaxHighlightingData("0,7,k;");
+
+    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
+    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(packageDeclaration, decorationData);
+
+    assertThat(htmlOutput).containsOnly("<span class=\"k\">package</span> org.sonar.core.source;");
+  }
+
+  @Test
+  public void should_decorate_multiple_lines_characters_range() throws Exception {
+
+    String firstCommentLine = "/*";
+    String secondCommentLine = " * Test";
+    String thirdCommentLine = " */";
+
+    String blockComment = firstCommentLine + LF_END_OF_LINE
+      + secondCommentLine + LF_END_OF_LINE
+      + thirdCommentLine + LF_END_OF_LINE;
+
+    DecorationDataHolder decorationData = new DecorationDataHolder();
+    decorationData.loadSyntaxHighlightingData("0,14,cppd;");
+
+    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
+    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(blockComment, decorationData);
+
+    assertThat(htmlOutput).containsExactly(
+      "<span class=\"cppd\">" + firstCommentLine + "</span>",
+      "<span class=\"cppd\">" + secondCommentLine + "</span>",
+      "<span class=\"cppd\">" + thirdCommentLine + "</span>",
+      ""
+    );
+  }
+
+  @Test
+  public void should_highlight_multiple_words_in_one_line() throws Exception {
+
+    String classDeclaration = "public class MyClass implements MyInterface {";
+
+    DecorationDataHolder decorationData = new DecorationDataHolder();
+    decorationData.loadSyntaxHighlightingData("0,6,k;7,12,k;21,31,k;");
+
+    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
+    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(classDeclaration, decorationData);
+
+    assertThat(htmlOutput).containsOnly(
+      "<span class=\"k\">public</span> " +
+        "<span class=\"k\">class</span> MyClass " +
+        "<span class=\"k\">implements</span> MyInterface {");
+  }
+
+  @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;
+
+    DecorationDataHolder decorationData = new DecorationDataHolder();
+    decorationData.loadSyntaxHighlightingData("0,184,cppd;47,53,k;");
+
+    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
+    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(javaDocSample, decorationData);
+
+    assertThat(htmlOutput).containsExactly(
+      "<span class=\"cppd\">/**</span>",
+      "<span class=\"cppd\"> * Creates a FormulaDecorator</span>",
+      "<span class=\"cppd\"> *</span>",
+      "<span class=\"cppd\"> * @param <span class=\"k\">metric</span> the metric should have an associated formula</span>",
+      "<span class=\"cppd\"> * </span>",
+      "<span class=\"cppd\"> * @throws IllegalArgumentException if no formula is associated to the metric</span>",
+      "<span class=\"cppd\"> */</span>",
+      ""
+    );
+  }
+
+  @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;
+
+    DecorationDataHolder decorationData = new DecorationDataHolder();
+    decorationData.loadSyntaxHighlightingData("0,52,cppd;54,67,a;69,75,k;106,112,k;");
+
+    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
+    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(crlfCodeSample, decorationData);
+
+    assertThat(htmlOutput).containsExactly(
+      "<span class=\"cppd\">/**</span>",
+      "<span class=\"cppd\">* @return metric generated by the decorator</span>",
+      "<span class=\"cppd\">*/</span>",
+      "<span class=\"a\">@DependedUpon</span>",
+      "<span class=\"k\">public</span> Metric generatesMetric() {",
+      "  <span class=\"k\">return</span> metric;",
+      "}",
+      ""
+    );
+  }
+
+  @Test
+  public void should_close_tags_at_end_of_file() throws Exception {
+
+    String classDeclarationSample =
+      "/*" + LF_END_OF_LINE +
+        " * Header" + LF_END_OF_LINE +
+        " */" + LF_END_OF_LINE +
+        LF_END_OF_LINE +
+        "public class HelloWorld {" + LF_END_OF_LINE +
+        "}";
+
+    DecorationDataHolder decorationData = new DecorationDataHolder();
+    decorationData.loadSyntaxHighlightingData("0,16,cppd;18,25,k;25,31,k;");
+
+    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
+    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(classDeclarationSample, decorationData);
+
+    assertThat(htmlOutput).containsExactly(
+      "<span class=\"cppd\">/*</span>",
+      "<span class=\"cppd\"> * Header</span>",
+      "<span class=\"cppd\"> */</span>",
+      "",
+      "<span class=\"k\">public </span><span class=\"k\">class </span>HelloWorld {",
+      "}"
+    );
+  }
+
+  @Test
+  public void should_escape_markup_chars() throws Exception {
+
+    String javadocWithHtml =
+      "/**\n" +
+        " * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.\n" +
+        " * \n" +
+        " * This framework can used for instance in order to :\n" +
+        " * <ul>\n" +
+        " *   <li>Create a lexer in charge to generate a list of tokens from a character stream</li>\n" +
+        " *   <li>Create a source code syntax highligther in charge to decorate a source code with HTML tags</li>\n" +
+        " *   <li>Create a javadoc generator</li>\n" +
+        " *   <li>...</li>\n" +
+        " * </ul>\n" +
+        " */\n";
+
+    DecorationDataHolder decorationData = new DecorationDataHolder();
+    decorationData.loadSyntaxHighlightingData("0,453,cppd;");
+
+    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
+    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData);
+
+    assertThat(htmlOutput).containsExactly(
+      "<span class=\"cppd\">/**</span>",
+      "<span class=\"cppd\"> * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.</span>",
+      "<span class=\"cppd\"> * </span>",
+      "<span class=\"cppd\"> * This framework can used for instance in order to :</span>",
+      "<span class=\"cppd\"> * &lt;ul&gt;</span>",
+      "<span class=\"cppd\"> *   &lt;li&gt;Create a lexer in charge to generate a list of tokens from a character stream&lt;/li&gt;</span>",
+      "<span class=\"cppd\"> *   &lt;li&gt;Create a source code syntax highligther in charge to decorate a source code with HTML tags&lt;/li&gt;</span>",
+      "<span class=\"cppd\"> *   &lt;li&gt;Create a javadoc generator&lt;/li&gt;</span>",
+      "<span class=\"cppd\"> *   &lt;li&gt;...&lt;/li&gt;</span>",
+      "<span class=\"cppd\"> * &lt;/ul&gt;</span>",
+      "<span class=\"cppd\"> */</span>",
+      "");
+  }
+
+  @Test
+  public void should_escape_ampersand_char() throws Exception {
+
+    String javadocWithAmpersandChar =
+      "/**\n" +
+        " * Definition of a dashboard.\n" +
+        " * <p/>\n" +
+        " * Its name and description can be retrieved using the i18n mechanism, using the keys \"dashboard.&lt;id&gt;.name\" and\n" +
+        " * \"dashboard.&lt;id&gt;.description\".\n" +
+        " *\n" +
+        " * @since 2.13\n" +
+        " */\n";
+
+    DecorationDataHolder decorationData = new DecorationDataHolder();
+    decorationData.loadSyntaxHighlightingData("0,220,cppd;");
+
+    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
+    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithAmpersandChar, decorationData);
+
+    assertThat(htmlOutput).containsExactly(
+      "<span class=\"cppd\">/**</span>",
+      "<span class=\"cppd\"> * Definition of a dashboard.</span>",
+      "<span class=\"cppd\"> * &lt;p/&gt;</span>",
+      "<span class=\"cppd\"> * Its name and description can be retrieved using the i18n mechanism, using the keys \"dashboard.&amp;lt;id&amp;gt;.name\" and</span>",
+      "<span class=\"cppd\"> * \"dashboard.&amp;lt;id&amp;gt;.description\".</span>",
+      "<span class=\"cppd\"> *</span>",
+      "<span class=\"cppd\"> * @since 2.13</span>",
+      "<span class=\"cppd\"> */</span>",
+      "");
+  }
+
+  @Test
+  public void should_support_cr_line_breaks() throws Exception {
+
+    String crCodeSample =
+      "/**" + CR_END_OF_LINE +
+        "* @return metric generated by the decorator" + CR_END_OF_LINE +
+        "*/" + CR_END_OF_LINE +
+        "@DependedUpon" + CR_END_OF_LINE +
+        "public Metric generatesMetric() {" + CR_END_OF_LINE +
+        "  return metric;" + CR_END_OF_LINE +
+        "}" + CR_END_OF_LINE;
+
+    DecorationDataHolder decorationData = new DecorationDataHolder();
+    decorationData.loadSyntaxHighlightingData("0,50,cppd;51,64,a;65,71,k;101,107,k;");
+
+    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
+    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(crCodeSample, decorationData);
+
+    assertThat(htmlOutput).containsExactly(
+      "<span class=\"cppd\">/**</span>",
+      "<span class=\"cppd\">* @return metric generated by the decorator</span>",
+      "<span class=\"cppd\">*/</span>",
+      "<span class=\"a\">@DependedUpon</span>",
+      "<span class=\"k\">public</span> Metric generatesMetric() {",
+      "  <span class=\"k\">return</span> metric;",
+      "}",
+      ""
+    );
+
+  }
+
+  @Test
+  public void should_support_multiple_empty_lines_at_end_of_file() throws Exception {
+
+    String classDeclarationSample =
+      "/*" + LF_END_OF_LINE +
+        " * Header" + LF_END_OF_LINE +
+        " */" + LF_END_OF_LINE +
+        LF_END_OF_LINE +
+        "public class HelloWorld {" + LF_END_OF_LINE +
+        "}" + LF_END_OF_LINE + LF_END_OF_LINE + LF_END_OF_LINE;
+
+    DecorationDataHolder decorationData = new DecorationDataHolder();
+    decorationData.loadSyntaxHighlightingData("0,16,cppd;18,25,k;25,31,k;");
+
+    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
+    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(classDeclarationSample, decorationData);
+
+    assertThat(htmlOutput).containsExactly(
+      "<span class=\"cppd\">/*</span>",
+      "<span class=\"cppd\"> * Header</span>",
+      "<span class=\"cppd\"> */</span>",
+      "",
+      "<span class=\"k\">public </span><span class=\"k\">class </span>HelloWorld {",
+      "}",
+      "",
+      "",
+      ""
+    );
+  }
+
+  @Test
+  public void begin_from_given_line() throws Exception {
+
+    String javadocWithHtml =
+      "/**\n" +
+        " * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.\n" +
+        " * \n" +
+        " * This framework can used for instance in order to :\n" +
+        " * <ul>\n" +
+        " *   <li>Create a lexer in charge to generate a list of tokens from a character stream</li>\n" +
+        " *   <li>Create a source code syntax highligther in charge to decorate a source code with HTML tags</li>\n" +
+        " *   <li>Create a javadoc generator</li>\n" +
+        " *   <li>...</li>\n" +
+        " * </ul>\n" +
+        " */\n";
+
+    DecorationDataHolder decorationData = new DecorationDataHolder();
+    decorationData.loadSyntaxHighlightingData("0,453,cppd;");
+
+    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
+    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData, 4, null);
+    assertThat(htmlOutput).hasSize(9);
+
+    // Begin from line 4
+    assertThat(htmlOutput).containsExactly(
+      "<span class=\"cppd\"> * This framework can used for instance in order to :</span>",
+      "<span class=\"cppd\"> * &lt;ul&gt;</span>",
+      "<span class=\"cppd\"> *   &lt;li&gt;Create a lexer in charge to generate a list of tokens from a character stream&lt;/li&gt;</span>",
+      "<span class=\"cppd\"> *   &lt;li&gt;Create a source code syntax highligther in charge to decorate a source code with HTML tags&lt;/li&gt;</span>",
+      "<span class=\"cppd\"> *   &lt;li&gt;Create a javadoc generator&lt;/li&gt;</span>",
+      "<span class=\"cppd\"> *   &lt;li&gt;...&lt;/li&gt;</span>",
+      "<span class=\"cppd\"> * &lt;/ul&gt;</span>",
+      "<span class=\"cppd\"> */</span>",
+      "");
+  }
+
+  @Test
+  public void end_to_given_line() throws Exception {
+
+    String javadocWithHtml =
+      "/**\n" +
+        " * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.\n" +
+        " * \n" +
+        " * This framework can used for instance in order to :\n" +
+        " * <ul>\n" +
+        " *   <li>Create a lexer in charge to generate a list of tokens from a character stream</li>\n" +
+        " *   <li>Create a source code syntax highligther in charge to decorate a source code with HTML tags</li>\n" +
+        " *   <li>Create a javadoc generator</li>\n" +
+        " *   <li>...</li>\n" +
+        " * </ul>\n" +
+        " */\n";
+
+    DecorationDataHolder decorationData = new DecorationDataHolder();
+    decorationData.loadSyntaxHighlightingData("0,453,cppd;");
+
+    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
+    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData, null, 4);
+    assertThat(htmlOutput).hasSize(4);
+
+    // End at line 4
+    assertThat(htmlOutput).containsExactly(
+      "<span class=\"cppd\">/**</span>",
+      "<span class=\"cppd\"> * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.</span>",
+      "<span class=\"cppd\"> * </span>",
+      "<span class=\"cppd\"> * This framework can used for instance in order to :</span>");
+  }
+
+  @Test
+  public void return_code_from_given_lint_given_end_line() throws Exception {
+
+    String javadocWithHtml =
+      "/**\n" +
+        " * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.\n" +
+        " * \n" +
+        " * This framework can used for instance in order to :\n" +
+        " * <ul>\n" +
+        " *   <li>Create a lexer in charge to generate a list of tokens from a character stream</li>\n" +
+        " *   <li>Create a source code syntax highligther in charge to decorate a source code with HTML tags</li>\n" +
+        " *   <li>Create a javadoc generator</li>\n" +
+        " *   <li>...</li>\n" +
+        " * </ul>\n" +
+        " */\n";
+
+    DecorationDataHolder decorationData = new DecorationDataHolder();
+    decorationData.loadSyntaxHighlightingData("0,453,cppd;");
+
+    HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator();
+    List<String> htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData, 4, 8);
+    assertThat(htmlOutput).hasSize(5);
+
+    // Begin from line 4 and finish at line 8
+    assertThat(htmlOutput).containsExactly(
+      "<span class=\"cppd\"> * This framework can used for instance in order to :</span>",
+      "<span class=\"cppd\"> * &lt;ul&gt;</span>",
+      "<span class=\"cppd\"> *   &lt;li&gt;Create a lexer in charge to generate a list of tokens from a character stream&lt;/li&gt;</span>",
+      "<span class=\"cppd\"> *   &lt;li&gt;Create a source code syntax highligther in charge to decorate a source code with HTML tags&lt;/li&gt;</span>",
+      "<span class=\"cppd\"> *   &lt;li&gt;Create a javadoc generator&lt;/li&gt;</span>"
+    );
+  }
+}
index 91cb480f5d9704e77a96ae1547a80e0bdd391745..69728a01a49c8d30384cb09df082687cc32d7b34 100644 (file)
@@ -25,12 +25,13 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
+import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.web.UserRole;
 import org.sonar.core.measure.db.MeasureDataDao;
 import org.sonar.core.resource.ResourceDao;
 import org.sonar.core.resource.ResourceDto;
-import org.sonar.core.source.HtmlSourceDecorator;
 import org.sonar.server.exceptions.NotFoundException;
+import org.sonar.server.ui.CodeColorizers;
 import org.sonar.server.user.MockUserSession;
 
 import static org.fest.assertions.Assertions.assertThat;
@@ -43,6 +44,9 @@ public class SourceServiceTest {
   @Mock
   HtmlSourceDecorator sourceDecorator;
 
+  @Mock
+  CodeColorizers codeColorizers;
+
   @Mock
   ResourceDao resourceDao;
 
@@ -53,7 +57,7 @@ public class SourceServiceTest {
 
   @Before
   public void setUp() throws Exception {
-    service = new SourceService(sourceDecorator, resourceDao, measureDataDao);
+    service = new SourceService(sourceDecorator, codeColorizers, resourceDao, measureDataDao);
   }
 
   @Test
@@ -98,16 +102,30 @@ public class SourceServiceTest {
   }
 
   @Test
-  public void find_data_from_component() throws Exception {
+  public void get_scm_author_data() throws Exception {
+    String componentKey = "org.sonar.sample:Sample";
+    service.getScmAuthorData(componentKey);
+    verify(measureDataDao).findByComponentKeyAndMetricKey(componentKey, CoreMetrics.SCM_AUTHORS_BY_LINE_KEY);
+  }
+
+  @Test
+  public void not_get_scm_author_data_if_no_data() throws Exception {
+    String componentKey = "org.sonar.sample:Sample";
+    when(measureDataDao.findByComponentKeyAndMetricKey(eq(componentKey), anyString())).thenReturn(null);
+    assertThat(service.getScmAuthorData(componentKey)).isNull();
+  }
+
+  @Test
+  public void get_scm_date_data() throws Exception {
     String componentKey = "org.sonar.sample:Sample";
-    service.findDataFromComponent(componentKey, "metric_key");
-    verify(measureDataDao).findByComponentKeyAndMetricKey(componentKey, "metric_key");
+    service.getScmDateData(componentKey);
+    verify(measureDataDao).findByComponentKeyAndMetricKey(componentKey, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY);
   }
 
   @Test
-  public void not_find_data_from_component_if_no_data() throws Exception {
+  public void not_get_scm_date_data_if_no_data() throws Exception {
     String componentKey = "org.sonar.sample:Sample";
-    when(measureDataDao.findByComponentKeyAndMetricKey(componentKey, "metric_key")).thenReturn(null);
-    assertThat(service.findDataFromComponent(componentKey, "metric_key")).isNull();
+    when(measureDataDao.findByComponentKeyAndMetricKey(eq(componentKey), anyString())).thenReturn(null);
+    assertThat(service.getScmDateData(componentKey)).isNull();
   }
 }
index aec87ed9dd19f60a8f262ca483141186b0c24a11..0410653bb80e5f4f8a21ee6493aa6528dff1bbc2 100644 (file)
@@ -25,7 +25,6 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.runners.MockitoJUnitRunner;
-import org.sonar.api.measures.CoreMetrics;
 import org.sonar.api.server.ws.WsTester;
 import org.sonar.server.exceptions.NotFoundException;
 import org.sonar.server.source.SourceService;
@@ -98,8 +97,8 @@ public class SourcesShowWsHandlerTest {
       "public class <span class=\"sym-31 sym\">HelloWorld</span> {}"
     ));
 
-    when(sourceService.findDataFromComponent(componentKey, CoreMetrics.SCM_AUTHORS_BY_LINE_KEY)).thenReturn("1=julien;");
-    when(sourceService.findDataFromComponent(componentKey, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY)).thenReturn("1=2013-03-13T16:22:31+0100;");
+    when(sourceService.getScmAuthorData(componentKey)).thenReturn("1=julien;");
+    when(sourceService.getScmDateData(componentKey)).thenReturn("1=2013-03-13T16:22:31+0100;");
 
     WsTester.TestRequest request = tester.newRequest("show").setParam("key", componentKey);
     request.execute().assertJson(getClass(), "show_source_with_scm.json");
@@ -113,9 +112,9 @@ public class SourcesShowWsHandlerTest {
       "",
       "public class <span class=\"sym-31 sym\">HelloWorld</span> {"
     ));
-    when(sourceService.findDataFromComponent(componentKey, CoreMetrics.SCM_AUTHORS_BY_LINE_KEY))
+    when(sourceService.getScmAuthorData(componentKey))
       .thenReturn("1=julien;2=simon;3=julien;4=simon;5=jean;6=julien");
-    when(sourceService.findDataFromComponent(componentKey, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY))
+    when(sourceService.getScmDateData(componentKey))
       .thenReturn("1=2013-03-13T16:22:31+0100;2=2013-03-14T16:22:31+0100;3=2013-03-13T16:22:31+0100;4=2013-03-14T16:22:31+0100;5=2013-03-15T16:22:31+0100;6=2013-03-13T16:22:31+0100;");
 
     WsTester.TestRequest request = tester.newRequest("show").setParam("key", componentKey).setParam("from", "3").setParam("to", "5");
@@ -130,9 +129,9 @@ public class SourcesShowWsHandlerTest {
       "",
       "public class <span class=\"sym-31 sym\">HelloWorld</span> {"
     ));
-    when(sourceService.findDataFromComponent(componentKey, CoreMetrics.SCM_AUTHORS_BY_LINE_KEY))
+    when(sourceService.getScmAuthorData(componentKey))
       .thenReturn("1=julien;2=julien;3=simon");
-    when(sourceService.findDataFromComponent(componentKey, CoreMetrics.SCM_LAST_COMMIT_DATETIMES_BY_LINE_KEY))
+    when(sourceService.getScmDateData(componentKey))
       .thenReturn("1=2013-03-13T16:22:31+0100;2=2013-03-13T16:22:31+0100;3=2013-03-14T16:22:31+0100;");
     WsTester.TestRequest request = tester.newRequest("show").setParam("key", componentKey);
     request.execute().assertJson(getClass(), "show_source_with_scm_without_repeating_same_lines.json");
index 1d1783c42f5e34f4b4ea4357c0ab279cf2d808de..add28feac3e51f7a0991c5076c307f2292fb01db 100644 (file)
@@ -20,7 +20,7 @@
 package org.sonar.server.text;
 
 import org.junit.Test;
-import org.sonar.core.source.HtmlSourceDecorator;
+import org.sonar.server.source.HtmlSourceDecorator;
 
 import static org.fest.assertions.Assertions.assertThat;
 import static org.mockito.Mockito.*;
diff --git a/sonar-server/src/test/resources/org/sonar/server/source/HtmlSourceDecoratorTest/shared.xml b/sonar-server/src/test/resources/org/sonar/server/source/HtmlSourceDecoratorTest/shared.xml
new file mode 100644 (file)
index 0000000..5f71e15
--- /dev/null
@@ -0,0 +1,20 @@
+<dataset>
+
+    <projects id="1" kee="org.apache.struts:struts:Dispatcher" enabled="[true]"/>
+    <projects id="2" kee="org.apache.struts:struts:VelocityManager" enabled="[true]"/>
+    <projects id="3" kee="org.apache.struts:struts:DebuggingInterceptor" enabled="[true]"/>
+
+    <snapshots id="11" project_id="1" islast="[true]" />
+    <snapshots id="12" project_id="2" islast="[true]" />
+    <snapshots id="13" project_id="3" islast="[true]" />
+    <snapshots id="14" project_id="3" islast="[true]" />
+
+    <snapshot_data id="101" resource_id="1" snapshot_id="11" snapshot_data="0,16,cppd;18,25,k;25,31,k;" data_type="highlight_syntax" />
+    <snapshot_data id="102" resource_id="2" snapshot_id="12" snapshot_data="31,41,31;" data_type="symbol" />
+    <snapshot_data id="103" resource_id="3" snapshot_id="13" snapshot_data="0,16,cppd;18,25,k;25,31,k;46,52,k;53,57,k;72,78,k;79,83,k;" data_type="highlight_syntax" />
+    <snapshot_data id="104" resource_id="3" snapshot_id="13" snapshot_data="31,41,31;58,61,58,96;84,87,84;" data_type="symbol" />
+
+    <snapshot_sources id="101" snapshot_id="11" data="/*&#10; * Header&#10; */&#10;&#10;public class HelloWorld {&#10;}" />
+    <snapshot_sources id="102" snapshot_id="12" data="/*&#10; * Header&#10; */&#10;&#10;public class HelloWorld {&#10;}" />
+    <snapshot_sources id="103" snapshot_id="13" data="/*&#10; * Header&#10; */&#10;&#10;public class HelloWorld {&#10;  public void foo() {&#10;  }&#10;  public void bar() {&#10;    foo();&#10;  }&#10;}" />
+</dataset>