/*
* This program is copyright (c) 2007 Hortis-GRC SA.
*
* This file is part of Sonar.
* Sonar is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Sonar is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Sonar; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package ch.hortis.sonar.core.service;
import ch.hortis.sonar.model.File;
import ch.hortis.sonar.model.FileSource;
import ch.hortis.sonar.model.Parameter;
import ch.hortis.sonar.model.RuleFailure;
import ch.hortis.sonar.model.RuleFailureLevel;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SourcesHighlighterService implements Service {
protected static final Logger LOG = LoggerFactory.getLogger(SourcesHighlighterService.class);
private Pattern cssPattern = Pattern.compile("((?:\\.\\.\\/)*)stylesheet\\.css");
private Pattern footerPattern = Pattern.compile("<hr/><div id=\"footer\">.+</div>");
private Pattern hrefPattern = Pattern.compile("<a href=\"((?:\\.\\./)*((?:\\w+/)*)((\\w+\\.html)))\">");
private Pattern sourceCodePattern = Pattern.compile("^<a name=\"(?:\\d+)\" href=\"#(?:\\d+)\">(\\d+)</a>\\s*");
public void execute(Module module, List<Module> directSubmodules) {
Map<File, List<RuleFailure>> failuresPerFile = orderFailuresPerFile(module.getRuleFailures());
List<File> files = module.getFiles();
for (File file : files) {
if (LOG.isDebugEnabled()) {
LOG.debug("Highlighting file " + file);
}
List<RuleFailure> failuresForFile = failuresPerFile.get(file);
highlight(file, failuresForFile, files);
}
}
private void highlight(File toHighlight, List<RuleFailure> failuresForFile, List<File> snapshotFiles) {
FileSource source = toHighlight.getFileSource();
if (source != null) {
processCssAndFooter(source);
highlightLines(toHighlight, failuresForFile, snapshotFiles);
}
}
protected void processCssAndFooter(FileSource source) {
String fileSource = source.getSource();
fileSource = cssPattern.matcher(fileSource).replaceFirst("../../../stylesheets/highlighted-source.css");
fileSource = footerPattern.matcher(fileSource).replaceFirst("");
source.setSource(fileSource);
}
private void addTooltipActivators(FileSource source, Set<Integer> highlightedLines) {
StringBuffer tootipJavaScript = new StringBuffer(4096);
tootipJavaScript.append("<script src=\"../../../javascripts/prototype.js\" type=\"text/javascript\"></script>\n");
tootipJavaScript.append("<script src=\"../../../javascripts/effects.js\" type=\"text/javascript\"></script>\n");
tootipJavaScript.append("<script src=\"../../../javascripts/tooltip.js\" type=\"text/javascript\"></script>\n");
tootipJavaScript.append("<script type=\"text/javascript\">\n");
for (Integer lineNumber : highlightedLines) {
tootipJavaScript.append("Tooltip.add('activator_")
.append(lineNumber).append("', 'target_").append(lineNumber).append("');\n");
}
tootipJavaScript.append("</script>\n");
source.setSource(source.getSource().replace("</body>", tootipJavaScript.toString() + "</body>"));
}
private void highlightLines(File toHighlight, List<RuleFailure> failuresForFile, List<File> snapshotFiles) {
StringBuffer highlightedSource = new StringBuffer(32768);
Set<Integer> highlightedLines = new HashSet<Integer>();
FileSource fileSource = toHighlight.getFileSource();
String[] lines = fileSource.getSource().split("\\n");
for (String line1 : lines) {
String line = line1.trim();
if (line.length() == 0) {
continue;
}
Matcher sourceLineMatcher = getSourceLineMatcher(line);
if (sourceLineMatcher.find()) {
int lineNumber = Integer.parseInt(sourceLineMatcher.group(1));
int sourceCodeStart = sourceLineMatcher.end(0);
List<RuleFailure> failuresForGivenLine = getFailuresForGivenLine(toHighlight, failuresForFile, lineNumber);
if (failuresForGivenLine.size() > 0) {
line = highlightLine(toHighlight, failuresForGivenLine, line, sourceCodeStart, lineNumber);
highlightedLines.add(lineNumber);
}
line = replaceLineHrefs(snapshotFiles, line);
}
highlightedSource.append(line).append("\n");
}
fileSource.setSource(highlightedSource.toString());
if (highlightedLines.size() > 0) {
addTooltipActivators(fileSource, highlightedLines);
}
}
protected Matcher getSourceLineMatcher(String line) {
return sourceCodePattern.matcher(line);
}
protected String replaceLineHrefs(List<File> snapshotFiles, String line) {
Matcher hrefMatcher = hrefPattern.matcher(line);
while (hrefMatcher.find()) {
String originalHref = hrefMatcher.group(1);
String nameSpace = StringUtils.chop(hrefMatcher.group(2).replace('/', '.'));
String fileName = hrefMatcher.group(3).replace(".html", ".java");
File hrefFile = null;
for (File file : snapshotFiles) {
if (file.getFilename().equals(fileName) &&
file.getNamespace().equals(nameSpace)) {
hrefFile = file;
break;
}
}
if (hrefFile != null) {
line = line.replace(originalHref, hrefFile.getId().toString());
}
}
return line;
}
protected String highlightLine(File toHighlight, List<RuleFailure> failuresForGivenLine, String lineSource, int sourceCodeStart, int lineNumber) {
StringBuffer tooltip = new StringBuffer(4096);
tooltip.append("<span id=\"target_").append(lineNumber).append("\" class=\"tooltip\" style=\"visibility: hidden;\">");
StringBuffer errorsMessages = new StringBuffer(512);
StringBuffer warningsMessages = new StringBuffer(512);
boolean hasErrors = false;
boolean hasWarnings = false;
for (RuleFailure failure : failuresForGivenLine) {
Parameter column = failure.getParameter("column");
String columnVal = "";
if (column != null) {
columnVal = "(col " + Integer.toString(column.getValue().intValue()) + ") ";
}
StringBuffer toAppend = null;
if (failure.getLevel() == RuleFailureLevel.ERROR) {
toAppend = errorsMessages;
hasErrors = true;
} else if (failure.getLevel() == RuleFailureLevel.WARNING) {
toAppend = warningsMessages;
hasWarnings = true;
}
if (toAppend != null) {
toAppend.append("<p><span style=\"font-weight: bold;\">").append(failure.getRule().getRulesCategory().getName())
.append(" - ").append(failure.getRule().getName()).append("</span> : ")
.append(columnVal).append(failure.getMessage().trim()).append("</p>");
}
}
if (errorsMessages.length() > 0) {
if (warningsMessages.length() > 0) {
tooltip.append("<div class=\"title\">Errors</div>");
}
tooltip.append("<div class=\"content\">").append(errorsMessages).append("</div>");
}
if (warningsMessages.length() > 0) {
if (errorsMessages.length() > 0) {
tooltip.append("<div class=\"title\">Warnings</div>");
}
tooltip.append("<div class=\"content\">").append(warningsMessages).append("</div>");
}
tooltip.append("</span>");
String cssClass = "error";
if (hasWarnings && !hasErrors) {
cssClass = "warning";
}
StringBuffer highlightedLine = new StringBuffer(8192);
highlightedLine.append(lineSource.substring(0, sourceCodeStart));
highlightedLine.append("<span id=\"activator_").append(lineNumber).append("\"");
highlightedLine.append(" class=\"").append(cssClass).append("\">");
highlightedLine.append(lineSource.substring(sourceCodeStart));
highlightedLine.append("</span>");
highlightedLine.append(tooltip);
lineSource = highlightedLine.toString();
return lineSource;
}
private List<RuleFailure> getFailuresForGivenLine(File toHighlight, List<RuleFailure> failuresForFile, int lineNumber) {
List<RuleFailure> failuresForGivenLine = new ArrayList<RuleFailure>();
if (failuresForFile != null) {
for (Iterator<RuleFailure> i = failuresForFile.iterator(); i.hasNext();) {
RuleFailure failure = i.next();
Parameter lineParam = failure.getParameter("line");
if (lineParam.getValue().intValue() == lineNumber) {
failuresForGivenLine.add(failure);
i.remove();
}
}
}
return failuresForGivenLine;
}
private Map<File, List<RuleFailure>> orderFailuresPerFile(List<RuleFailure> failures) {
Map<File, List<RuleFailure>> failuresPerFile = new HashMap<File, List<RuleFailure>>();
for (RuleFailure failure : failures) {
File failureFile = failure.getFile();
List<RuleFailure> failuresForFile = failuresPerFile.get(failureFile);
if (failuresForFile == null) {
failuresForFile = new ArrayList<RuleFailure>();
failuresPerFile.put(failureFile, failuresForFile);
}
failuresForFile.add(failure);
}
return failuresPerFile;
}
}