From: Julien Lancelot Date: Wed, 29 Jan 2014 14:04:21 +0000 (+0100) Subject: Move Syntax highlighting classes from core to sever module X-Git-Tag: 4.2~391 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=d5236ae0363d97f3ef47712005dee74d6704d7a0;p=sonarqube.git Move Syntax highlighting classes from core to sever module --- 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 index e100bb4c7c5..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/source/CharactersReader.java +++ /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 openTags; - - private int currentValue; - private int previousValue; - private int currentIndex = -1; - - public CharactersReader(BufferedReader stringBuffer) { - this.stringBuffer = stringBuffer; - this.openTags = new ArrayDeque(); - } - - 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 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 index 91246d3b034..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/source/DecorationDataHolder.java +++ /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 openingTagsEntries; - private int openingTagsIndex; - private List 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 getOpeningTagsEntries() { - return openingTagsEntries; - } - - OpeningHtmlTag getCurrentOpeningTagEntry() { - return openingTagsIndex < openingTagsEntries.size() ? openingTagsEntries.get(openingTagsIndex) : null; - } - - void nextOpeningTagEntry() { - openingTagsIndex++; - } - - List 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 openingHtmlTags) { - int insertionIndex = 0; - Iterator tagIterator = openingHtmlTags.iterator(); - while (tagIterator.hasNext() && tagIterator.next().getStartOffset() <= newEntry.getStartOffset()) { - insertionIndex++; - } - openingHtmlTags.add(insertionIndex, newEntry); - } - - private void insertAndPreserveOrder(int newOffset, List orderedOffsets) { - int insertionIndex = 0; - Iterator 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 index 4ac16549bf2..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/source/HtmlSourceDecorator.java +++ /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 getDecoratedSourceAsHtml(String componentKey, @Nullable Integer from, @Nullable Integer to) { - SqlSession session = mybatis.openSession(); - try { - Collection 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 getDecoratedSourceAsHtml(long snapshotId) { - Collection 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 decorate(@Nullable String snapshotSource, Collection 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 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 index d186f1c5bc0..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/source/HtmlTextDecorator.java +++ /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 = "<"; - static final String ENCODED_HTML_CLOSING = ">"; - static final String ENCODED_AMPERSAND = "&"; - - List decorateTextWithHtml(String text, DecorationDataHolder decorationDataHolder) { - return decorateTextWithHtml(text, decorationDataHolder, null, null); - } - - List decorateTextWithHtml(String text, DecorationDataHolder decorationDataHolder, @Nullable Integer from, @Nullable Integer to) { - - StringBuilder currentHtmlLine = new StringBuilder(); - List 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 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 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 getTagsToOpen(int currentIndex, DecorationDataHolder dataHolder) { - Collection 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 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(""); - } - - private void injectClosingHtml(StringBuilder decoratedText) { - decoratedText.append(""); - } -} 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 index 54b95e7542d..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/source/OpeningHtmlTag.java +++ /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 index 62730ffc402..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/source/DecorationDataHolderTest.java +++ /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 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 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 index cd623f77ded..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/source/HtmlSourceDecoratorTest.java +++ /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 decoratedSource = sourceDecorator.getDecoratedSourceAsHtml(11L); - - assertThat(decoratedSource).containsExactly( - "/*", - " * Header", - " */", - "", - "public class HelloWorld {", - "}" - ); - } - - @Test - public void highlight_syntax_with_html_from_component() throws Exception { - List decoratedSource = sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:Dispatcher", null, null); - - assertThat(decoratedSource).containsExactly( - "/*", - " * Header", - " */", - "", - "public class 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 decoratedSource = sourceDecorator.getDecoratedSourceAsHtml(12L); - - assertThat(decoratedSource).containsExactly( - "/*", - " * Header", - " */", - "", - "public class HelloWorld {", - "}" - ); - } - - @Test - public void mark_symbols_with_html_from_component() throws Exception { - List decoratedSource = sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:VelocityManager", null, null); - - assertThat(decoratedSource).containsExactly( - "/*", - " * Header", - " */", - "", - "public class HelloWorld {", - "}" - ); - } - - @Test - public void decorate_source_with_multiple_decoration_strategies() throws Exception { - List decoratedSource = sourceDecorator.getDecoratedSourceAsHtml(13L); - - assertThat(decoratedSource).containsExactly( - "/*", - " * Header", - " */", - "", - "public class HelloWorld {", - " public void foo() {", - " }", - " public void bar() {", - " foo();", - " }", - "}" - ); - } - - @Test - public void decorate_source_with_multiple_decoration_strategies_from_component() throws Exception { - List decoratedSource = sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:DebuggingInterceptor", null, null); - - assertThat(decoratedSource).containsExactly( - "/*", - " * Header", - " */", - "", - "public class HelloWorld {", - " public void foo() {", - " }", - " public void bar() {", - " foo();", - " }", - "}" - ); - } - - @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 index 68c0bdc139a..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/source/HtmlTextDecoratorTest.java +++ /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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(packageDeclaration, decorationData); - - assertThat(htmlOutput).containsOnly("package 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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(blockComment, decorationData); - - assertThat(htmlOutput).containsExactly( - "" + firstCommentLine + "", - "" + secondCommentLine + "", - "" + thirdCommentLine + "", - "" - ); - } - - @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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(classDeclaration, decorationData); - - assertThat(htmlOutput).containsOnly( - "public " + - "class MyClass " + - "implements 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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(javaDocSample, decorationData); - - assertThat(htmlOutput).containsExactly( - "/**", - " * Creates a FormulaDecorator", - " *", - " * @param metric the metric should have an associated formula", - " * ", - " * @throws IllegalArgumentException if no formula is associated to the metric", - " */", - "" - ); - } - - @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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(crlfCodeSample, decorationData); - - assertThat(htmlOutput).containsExactly( - "/**", - "* @return metric generated by the decorator", - "*/", - "@DependedUpon", - "public Metric generatesMetric() {", - " return 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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(classDeclarationSample, decorationData); - - assertThat(htmlOutput).containsExactly( - "/*", - " * Header", - " */", - "", - "public class 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" + - " *
    \n" + - " *
  • Create a lexer in charge to generate a list of tokens from a character stream
  • \n" + - " *
  • Create a source code syntax highligther in charge to decorate a source code with HTML tags
  • \n" + - " *
  • Create a javadoc generator
  • \n" + - " *
  • ...
  • \n" + - " *
\n" + - " */\n"; - - DecorationDataHolder decorationData = new DecorationDataHolder(); - decorationData.loadSyntaxHighlightingData("0,453,cppd;"); - - HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator(); - List htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData); - - assertThat(htmlOutput).containsExactly( - "/**", - " * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.", - " * ", - " * This framework can used for instance in order to :", - " * <ul>", - " * <li>Create a lexer in charge to generate a list of tokens from a character stream</li>", - " * <li>Create a source code syntax highligther in charge to decorate a source code with HTML tags</li>", - " * <li>Create a javadoc generator</li>", - " * <li>...</li>", - " * </ul>", - " */", - ""); - } - - @Test - public void should_escape_ampersand_char() throws Exception { - - String javadocWithAmpersandChar = - "/**\n" + - " * Definition of a dashboard.\n" + - " *

\n" + - " * Its name and description can be retrieved using the i18n mechanism, using the keys \"dashboard.<id>.name\" and\n" + - " * \"dashboard.<id>.description\".\n" + - " *\n" + - " * @since 2.13\n" + - " */\n"; - - DecorationDataHolder decorationData = new DecorationDataHolder(); - decorationData.loadSyntaxHighlightingData("0,220,cppd;"); - - HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator(); - List htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithAmpersandChar, decorationData); - - assertThat(htmlOutput).containsExactly( - "/**", - " * Definition of a dashboard.", - " * <p/>", - " * Its name and description can be retrieved using the i18n mechanism, using the keys \"dashboard.&lt;id&gt;.name\" and", - " * \"dashboard.&lt;id&gt;.description\".", - " *", - " * @since 2.13", - " */", - ""); - } - - @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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(crCodeSample, decorationData); - - assertThat(htmlOutput).containsExactly( - "/**", - "* @return metric generated by the decorator", - "*/", - "@DependedUpon", - "public Metric generatesMetric() {", - " return 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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(classDeclarationSample, decorationData); - - assertThat(htmlOutput).containsExactly( - "/*", - " * Header", - " */", - "", - "public class 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" + - " *

    \n" + - " *
  • Create a lexer in charge to generate a list of tokens from a character stream
  • \n" + - " *
  • Create a source code syntax highligther in charge to decorate a source code with HTML tags
  • \n" + - " *
  • Create a javadoc generator
  • \n" + - " *
  • ...
  • \n" + - " *
\n" + - " */\n"; - - DecorationDataHolder decorationData = new DecorationDataHolder(); - decorationData.loadSyntaxHighlightingData("0,453,cppd;"); - - HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator(); - List htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData, 4, null); - assertThat(htmlOutput).hasSize(9); - - // Begin from line 4 - assertThat(htmlOutput).containsExactly( - " * This framework can used for instance in order to :", - " * <ul>", - " * <li>Create a lexer in charge to generate a list of tokens from a character stream</li>", - " * <li>Create a source code syntax highligther in charge to decorate a source code with HTML tags</li>", - " * <li>Create a javadoc generator</li>", - " * <li>...</li>", - " * </ul>", - " */", - ""); - } - - @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" + - " *
    \n" + - " *
  • Create a lexer in charge to generate a list of tokens from a character stream
  • \n" + - " *
  • Create a source code syntax highligther in charge to decorate a source code with HTML tags
  • \n" + - " *
  • Create a javadoc generator
  • \n" + - " *
  • ...
  • \n" + - " *
\n" + - " */\n"; - - DecorationDataHolder decorationData = new DecorationDataHolder(); - decorationData.loadSyntaxHighlightingData("0,453,cppd;"); - - HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator(); - List htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData, null, 4); - assertThat(htmlOutput).hasSize(4); - - // End at line 4 - assertThat(htmlOutput).containsExactly( - "/**", - " * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.", - " * ", - " * This framework can used for instance in order to :"); - } - - @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" + - " *
    \n" + - " *
  • Create a lexer in charge to generate a list of tokens from a character stream
  • \n" + - " *
  • Create a source code syntax highligther in charge to decorate a source code with HTML tags
  • \n" + - " *
  • Create a javadoc generator
  • \n" + - " *
  • ...
  • \n" + - " *
\n" + - " */\n"; - - DecorationDataHolder decorationData = new DecorationDataHolder(); - decorationData.loadSyntaxHighlightingData("0,453,cppd;"); - - HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator(); - List htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData, 4, 8); - assertThat(htmlOutput).hasSize(5); - - // Begin from line 4 and finish at line 8 - assertThat(htmlOutput).containsExactly( - " * This framework can used for instance in order to :", - " * <ul>", - " * <li>Create a lexer in charge to generate a list of tokens from a character stream</li>", - " * <li>Create a source code syntax highligther in charge to decorate a source code with HTML tags</li>", - " * <li>Create a javadoc generator</li>" - ); - } -} 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 index 5f71e1539ac..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/source/HtmlSourceDecoratorTest/shared.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java index 0bb70fb04ea..a5f559743b6 100644 --- a/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ b/sonar-server/src/main/java/org/sonar/server/platform/Platform.java @@ -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 index 00000000000..c80a67c1377 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/source/CharactersReader.java @@ -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 openTags; + + private int currentValue; + private int previousValue; + private int currentIndex = -1; + + public CharactersReader(BufferedReader stringBuffer) { + this.stringBuffer = stringBuffer; + this.openTags = new ArrayDeque(); + } + + 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 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 index 00000000000..7b840a0dfb1 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/source/DecorationDataHolder.java @@ -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 openingTagsEntries; + private int openingTagsIndex; + private List 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 getOpeningTagsEntries() { + return openingTagsEntries; + } + + OpeningHtmlTag getCurrentOpeningTagEntry() { + return openingTagsIndex < openingTagsEntries.size() ? openingTagsEntries.get(openingTagsIndex) : null; + } + + void nextOpeningTagEntry() { + openingTagsIndex++; + } + + List 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 openingHtmlTags) { + int insertionIndex = 0; + Iterator tagIterator = openingHtmlTags.iterator(); + while (tagIterator.hasNext() && tagIterator.next().getStartOffset() <= newEntry.getStartOffset()) { + insertionIndex++; + } + openingHtmlTags.add(insertionIndex, newEntry); + } + + private void insertAndPreserveOrder(int newOffset, List orderedOffsets) { + int insertionIndex = 0; + Iterator 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 index 00000000000..e63bfa72ef6 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/source/HtmlSourceDecorator.java @@ -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 getDecoratedSourceAsHtml(String componentKey, @Nullable Integer from, @Nullable Integer to) { + SqlSession session = mybatis.openSession(); + try { + Collection 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 getDecoratedSourceAsHtml(long snapshotId) { + Collection 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 decorate(@Nullable String snapshotSource, Collection 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 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 index 00000000000..41ca1ba0d37 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/source/HtmlTextDecorator.java @@ -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 = "<"; + static final String ENCODED_HTML_CLOSING = ">"; + static final String ENCODED_AMPERSAND = "&"; + + List decorateTextWithHtml(String text, DecorationDataHolder decorationDataHolder) { + return decorateTextWithHtml(text, decorationDataHolder, null, null); + } + + List decorateTextWithHtml(String text, DecorationDataHolder decorationDataHolder, @Nullable Integer from, @Nullable Integer to) { + + StringBuilder currentHtmlLine = new StringBuilder(); + List 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 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 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 getTagsToOpen(int currentIndex, DecorationDataHolder dataHolder) { + Collection 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 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(""); + } + + private void injectClosingHtml(StringBuilder decoratedText) { + decoratedText.append(""); + } +} 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 index 00000000000..b45b95024b2 --- /dev/null +++ b/sonar-server/src/main/java/org/sonar/server/source/OpeningHtmlTag.java @@ -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; + } +} diff --git a/sonar-server/src/main/java/org/sonar/server/source/SourceService.java b/sonar-server/src/main/java/org/sonar/server/source/SourceService.java index f2472b273df..ab17c9e4419 100644 --- a/sonar-server/src/main/java/org/sonar/server/source/SourceService.java +++ b/sonar-server/src/main/java/org/sonar/server/source/SourceService.java @@ -21,13 +21,14 @@ 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(); diff --git a/sonar-server/src/main/java/org/sonar/server/source/ws/SourcesShowWsHandler.java b/sonar-server/src/main/java/org/sonar/server/source/ws/SourcesShowWsHandler.java index e087f62bf46..b4ee35ac3da 100644 --- a/sonar-server/src/main/java/org/sonar/server/source/ws/SourcesShowWsHandler.java +++ b/sonar-server/src/main/java/org/sonar/server/source/ws/SourcesShowWsHandler.java @@ -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; diff --git a/sonar-server/src/main/java/org/sonar/server/text/RubyTextService.java b/sonar-server/src/main/java/org/sonar/server/text/RubyTextService.java index 135a28731ad..2206e6c4c20 100644 --- a/sonar-server/src/main/java/org/sonar/server/text/RubyTextService.java +++ b/sonar-server/src/main/java/org/sonar/server/text/RubyTextService.java @@ -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; diff --git a/sonar-server/src/main/webapp/WEB-INF/app/helpers/source_helper.rb b/sonar-server/src/main/webapp/WEB-INF/app/helpers/source_helper.rb index 914629a5040..1d671555819 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/helpers/source_helper.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/helpers/source_helper.rb @@ -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| diff --git a/sonar-server/src/main/webapp/WEB-INF/app/models/snapshot.rb b/sonar-server/src/main/webapp/WEB-INF/app/models/snapshot.rb index 583c3ab9884..b0d2c406125 100644 --- a/sonar-server/src/main/webapp/WEB-INF/app/models/snapshot.rb +++ b/sonar-server/src/main/webapp/WEB-INF/app/models/snapshot.rb @@ -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 index 00000000000..507fc3e6300 --- /dev/null +++ b/sonar-server/src/test/java/org/sonar/server/source/DecorationDataHolderTest.java @@ -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 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 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 index 00000000000..113c87fd318 --- /dev/null +++ b/sonar-server/src/test/java/org/sonar/server/source/HtmlSourceDecoratorTest.java @@ -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 decoratedSource = sourceDecorator.getDecoratedSourceAsHtml(11L); + + assertThat(decoratedSource).containsExactly( + "/*", + " * Header", + " */", + "", + "public class HelloWorld {", + "}" + ); + } + + @Test + public void highlight_syntax_with_html_from_component() throws Exception { + List decoratedSource = sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:Dispatcher", null, null); + + assertThat(decoratedSource).containsExactly( + "/*", + " * Header", + " */", + "", + "public class 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 decoratedSource = sourceDecorator.getDecoratedSourceAsHtml(12L); + + assertThat(decoratedSource).containsExactly( + "/*", + " * Header", + " */", + "", + "public class HelloWorld {", + "}" + ); + } + + @Test + public void mark_symbols_with_html_from_component() throws Exception { + List decoratedSource = sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:VelocityManager", null, null); + + assertThat(decoratedSource).containsExactly( + "/*", + " * Header", + " */", + "", + "public class HelloWorld {", + "}" + ); + } + + @Test + public void decorate_source_with_multiple_decoration_strategies() throws Exception { + List decoratedSource = sourceDecorator.getDecoratedSourceAsHtml(13L); + + assertThat(decoratedSource).containsExactly( + "/*", + " * Header", + " */", + "", + "public class HelloWorld {", + " public void foo() {", + " }", + " public void bar() {", + " foo();", + " }", + "}" + ); + } + + @Test + public void decorate_source_with_multiple_decoration_strategies_from_component() throws Exception { + List decoratedSource = sourceDecorator.getDecoratedSourceAsHtml("org.apache.struts:struts:DebuggingInterceptor", null, null); + + assertThat(decoratedSource).containsExactly( + "/*", + " * Header", + " */", + "", + "public class HelloWorld {", + " public void foo() {", + " }", + " public void bar() {", + " foo();", + " }", + "}" + ); + } + + @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 index 00000000000..3e5de01690a --- /dev/null +++ b/sonar-server/src/test/java/org/sonar/server/source/HtmlTextDecoratorTest.java @@ -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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(packageDeclaration, decorationData); + + assertThat(htmlOutput).containsOnly("package 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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(blockComment, decorationData); + + assertThat(htmlOutput).containsExactly( + "" + firstCommentLine + "", + "" + secondCommentLine + "", + "" + thirdCommentLine + "", + "" + ); + } + + @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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(classDeclaration, decorationData); + + assertThat(htmlOutput).containsOnly( + "public " + + "class MyClass " + + "implements 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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(javaDocSample, decorationData); + + assertThat(htmlOutput).containsExactly( + "/**", + " * Creates a FormulaDecorator", + " *", + " * @param metric the metric should have an associated formula", + " * ", + " * @throws IllegalArgumentException if no formula is associated to the metric", + " */", + "" + ); + } + + @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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(crlfCodeSample, decorationData); + + assertThat(htmlOutput).containsExactly( + "/**", + "* @return metric generated by the decorator", + "*/", + "@DependedUpon", + "public Metric generatesMetric() {", + " return 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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(classDeclarationSample, decorationData); + + assertThat(htmlOutput).containsExactly( + "/*", + " * Header", + " */", + "", + "public class 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" + + " *
    \n" + + " *
  • Create a lexer in charge to generate a list of tokens from a character stream
  • \n" + + " *
  • Create a source code syntax highligther in charge to decorate a source code with HTML tags
  • \n" + + " *
  • Create a javadoc generator
  • \n" + + " *
  • ...
  • \n" + + " *
\n" + + " */\n"; + + DecorationDataHolder decorationData = new DecorationDataHolder(); + decorationData.loadSyntaxHighlightingData("0,453,cppd;"); + + HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator(); + List htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData); + + assertThat(htmlOutput).containsExactly( + "/**", + " * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.", + " * ", + " * This framework can used for instance in order to :", + " * <ul>", + " * <li>Create a lexer in charge to generate a list of tokens from a character stream</li>", + " * <li>Create a source code syntax highligther in charge to decorate a source code with HTML tags</li>", + " * <li>Create a javadoc generator</li>", + " * <li>...</li>", + " * </ul>", + " */", + ""); + } + + @Test + public void should_escape_ampersand_char() throws Exception { + + String javadocWithAmpersandChar = + "/**\n" + + " * Definition of a dashboard.\n" + + " *

\n" + + " * Its name and description can be retrieved using the i18n mechanism, using the keys \"dashboard.<id>.name\" and\n" + + " * \"dashboard.<id>.description\".\n" + + " *\n" + + " * @since 2.13\n" + + " */\n"; + + DecorationDataHolder decorationData = new DecorationDataHolder(); + decorationData.loadSyntaxHighlightingData("0,220,cppd;"); + + HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator(); + List htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithAmpersandChar, decorationData); + + assertThat(htmlOutput).containsExactly( + "/**", + " * Definition of a dashboard.", + " * <p/>", + " * Its name and description can be retrieved using the i18n mechanism, using the keys \"dashboard.&lt;id&gt;.name\" and", + " * \"dashboard.&lt;id&gt;.description\".", + " *", + " * @since 2.13", + " */", + ""); + } + + @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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(crCodeSample, decorationData); + + assertThat(htmlOutput).containsExactly( + "/**", + "* @return metric generated by the decorator", + "*/", + "@DependedUpon", + "public Metric generatesMetric() {", + " return 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 htmlOutput = htmlTextDecorator.decorateTextWithHtml(classDeclarationSample, decorationData); + + assertThat(htmlOutput).containsExactly( + "/*", + " * Header", + " */", + "", + "public class 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" + + " *

    \n" + + " *
  • Create a lexer in charge to generate a list of tokens from a character stream
  • \n" + + " *
  • Create a source code syntax highligther in charge to decorate a source code with HTML tags
  • \n" + + " *
  • Create a javadoc generator
  • \n" + + " *
  • ...
  • \n" + + " *
\n" + + " */\n"; + + DecorationDataHolder decorationData = new DecorationDataHolder(); + decorationData.loadSyntaxHighlightingData("0,453,cppd;"); + + HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator(); + List htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData, 4, null); + assertThat(htmlOutput).hasSize(9); + + // Begin from line 4 + assertThat(htmlOutput).containsExactly( + " * This framework can used for instance in order to :", + " * <ul>", + " * <li>Create a lexer in charge to generate a list of tokens from a character stream</li>", + " * <li>Create a source code syntax highligther in charge to decorate a source code with HTML tags</li>", + " * <li>Create a javadoc generator</li>", + " * <li>...</li>", + " * </ul>", + " */", + ""); + } + + @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" + + " *
    \n" + + " *
  • Create a lexer in charge to generate a list of tokens from a character stream
  • \n" + + " *
  • Create a source code syntax highligther in charge to decorate a source code with HTML tags
  • \n" + + " *
  • Create a javadoc generator
  • \n" + + " *
  • ...
  • \n" + + " *
\n" + + " */\n"; + + DecorationDataHolder decorationData = new DecorationDataHolder(); + decorationData.loadSyntaxHighlightingData("0,453,cppd;"); + + HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator(); + List htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData, null, 4); + assertThat(htmlOutput).hasSize(4); + + // End at line 4 + assertThat(htmlOutput).containsExactly( + "/**", + " * Provides a basic framework to sequentially read any kind of character stream in order to feed a generic OUTPUT.", + " * ", + " * This framework can used for instance in order to :"); + } + + @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" + + " *
    \n" + + " *
  • Create a lexer in charge to generate a list of tokens from a character stream
  • \n" + + " *
  • Create a source code syntax highligther in charge to decorate a source code with HTML tags
  • \n" + + " *
  • Create a javadoc generator
  • \n" + + " *
  • ...
  • \n" + + " *
\n" + + " */\n"; + + DecorationDataHolder decorationData = new DecorationDataHolder(); + decorationData.loadSyntaxHighlightingData("0,453,cppd;"); + + HtmlTextDecorator htmlTextDecorator = new HtmlTextDecorator(); + List htmlOutput = htmlTextDecorator.decorateTextWithHtml(javadocWithHtml, decorationData, 4, 8); + assertThat(htmlOutput).hasSize(5); + + // Begin from line 4 and finish at line 8 + assertThat(htmlOutput).containsExactly( + " * This framework can used for instance in order to :", + " * <ul>", + " * <li>Create a lexer in charge to generate a list of tokens from a character stream</li>", + " * <li>Create a source code syntax highligther in charge to decorate a source code with HTML tags</li>", + " * <li>Create a javadoc generator</li>" + ); + } +} diff --git a/sonar-server/src/test/java/org/sonar/server/source/SourceServiceTest.java b/sonar-server/src/test/java/org/sonar/server/source/SourceServiceTest.java index 91cb480f5d9..69728a01a49 100644 --- a/sonar-server/src/test/java/org/sonar/server/source/SourceServiceTest.java +++ b/sonar-server/src/test/java/org/sonar/server/source/SourceServiceTest.java @@ -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(); } } diff --git a/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesShowWsHandlerTest.java b/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesShowWsHandlerTest.java index aec87ed9dd1..0410653bb80 100644 --- a/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesShowWsHandlerTest.java +++ b/sonar-server/src/test/java/org/sonar/server/source/ws/SourcesShowWsHandlerTest.java @@ -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 HelloWorld {}" )); - 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 HelloWorld {" )); - 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 HelloWorld {" )); - 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"); diff --git a/sonar-server/src/test/java/org/sonar/server/text/RubyTextServiceTest.java b/sonar-server/src/test/java/org/sonar/server/text/RubyTextServiceTest.java index 1d1783c42f5..add28feac3e 100644 --- a/sonar-server/src/test/java/org/sonar/server/text/RubyTextServiceTest.java +++ b/sonar-server/src/test/java/org/sonar/server/text/RubyTextServiceTest.java @@ -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 index 00000000000..5f71e1539ac --- /dev/null +++ b/sonar-server/src/test/resources/org/sonar/server/source/HtmlSourceDecoratorTest/shared.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + +