/*
* 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 ) {
if ( LOG.isDebugEnabled() ) LOG.debug( "Highlightning source files for rule failures" );
Map<File, List<RuleFailure>> failuresPerFile = orderFailuresPerFile(module.getRuleFailures());
if ( LOG.isDebugEnabled() ) LOG.debug( "Rule failures ordered per file" );
List<File> files = module.getFiles();
for (File file : files) {
List<RuleFailure> failuresForFile = failuresPerFile.get(file);
if ( LOG.isDebugEnabled() ) LOG.debug( "Highlightning source file " + 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;
}
}