Created
August 11, 2018 04:32
-
-
Save h3xstream/912f96d3cc65cdb1aac07596cc4d9d61 to your computer and use it in GitHub Desktop.
SourceDetail with prism.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package hudson.plugins.analysis.views; | |
import java.io.*; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.NoSuchElementException; | |
import org.apache.commons.io.FilenameUtils; | |
import org.apache.commons.io.IOUtils; | |
import org.apache.commons.io.LineIterator; | |
import org.apache.commons.lang.StringEscapeUtils; | |
import org.apache.commons.lang.StringUtils; | |
import com.infradna.tool.bridge_method_injector.WithBridgeMethods; | |
import de.java2html.converter.JavaSource2HTMLConverter; | |
import de.java2html.javasource.JavaSource; | |
import de.java2html.javasource.JavaSourceParser; | |
import de.java2html.options.JavaSourceConversionOptions; | |
import hudson.model.AbstractBuild; | |
import hudson.model.Run; | |
import hudson.model.ModelObject; | |
import hudson.plugins.analysis.util.EncodingValidator; | |
import hudson.plugins.analysis.util.model.FileAnnotation; | |
import hudson.plugins.analysis.util.model.LineRange; | |
/** | |
* Renders a source file containing an annotation for the whole file or a | |
* specific line number. | |
* | |
* @author Ulli Hafner | |
*/ | |
@SuppressWarnings("PMD.CyclomaticComplexity") | |
public class SourceDetail implements ModelObject { | |
/** Offset of the source code generator. After this line the actual source file lines start. */ | |
protected static final int SOURCE_GENERATOR_OFFSET = 13; | |
/** Color for the first (primary) annotation range. */ | |
private static final String FIRST_COLOR = "#FCAF3E"; | |
/** Color for all other annotation ranges. */ | |
private static final String OTHER_COLOR = "#FCE94F"; | |
/** The current build as owner of this object. */ | |
private final Run<?, ?> owner; | |
/** Stripped file name of this annotation without the path prefix. */ | |
private final String fileName; | |
/** The annotation to be shown. */ | |
private final FileAnnotation annotation; | |
/** The rendered source file. */ | |
private String sourceCode = StringUtils.EMPTY; | |
/** The default encoding to be used when reading and parsing files. */ | |
private final String defaultEncoding; | |
/** | |
* Creates a new instance of this source code object. | |
* | |
* @param owner | |
* the current build as owner of this object | |
* @param annotation | |
* the warning to display in the source file | |
* @param defaultEncoding | |
* the default encoding to be used when reading and parsing files | |
*/ | |
public SourceDetail(final Run<?, ?> owner, final FileAnnotation annotation, final String defaultEncoding) { | |
this.owner = owner; | |
this.annotation = annotation; | |
this.defaultEncoding = defaultEncoding; | |
fileName = StringUtils.substringAfterLast(annotation.getFileName(), "/"); | |
initializeContent(); | |
} | |
/** | |
* Initializes the content of the source file: reads the file, colors it, and | |
* splits it into three parts. | |
*/ | |
private void initializeContent() { | |
InputStream file = null; | |
try { | |
File tempFile = new File(annotation.getTempName(owner)); | |
if (tempFile.exists()) { | |
file = new FileInputStream(tempFile); | |
} | |
else { | |
file = new FileInputStream(new File(annotation.getFileName())); | |
} | |
sourceCode = buildCodeBlock(file,annotation.getFileName()); | |
//splitSourceFile(highlightSource(file)); | |
} | |
catch (IOException exception) { | |
sourceCode = "Can't read file: " + exception.getLocalizedMessage(); | |
} | |
finally { | |
IOUtils.closeQuietly(file); | |
} | |
} | |
private String prismLangClassFromExtension(String extension) { | |
switch(extension.toLowerCase()) { | |
case "jav": case "java": | |
return "language-java"; | |
case "htm": case "html": | |
return "language-markup"; | |
case "erb": case "jsp": case "tag": | |
return "language-erb"; | |
case "rb": | |
return "language-ruby"; | |
case "kt": | |
return "language-kotlin"; | |
case "js": | |
return "language-javascript"; | |
case "c": | |
return "language-c"; | |
case "cs": | |
return "language-csharp"; | |
case "vb": | |
return "language-vbnet"; | |
case "cpp": | |
return "language-cpp"; | |
case "groovy": | |
return "language-groovy"; | |
case "pl": | |
return "language-perl"; | |
case "php": | |
return "language-php"; | |
case "py": | |
return "language-python"; | |
case "scala": case "sc": | |
return "language-scala"; | |
} | |
return "language-clike"; //Best effort for unknown extensions | |
} | |
private String buildCodeBlock(InputStream inputStream, String fileName) throws IOException { | |
StringWriter writer = new StringWriter(); | |
String prismCssClass = prismLangClassFromExtension(FilenameUtils.getExtension(fileName)); | |
writer.append("<pre><code class=\""+prismCssClass+" line-numbers\">"); | |
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); | |
List<LineRange> ranges = new ArrayList<>(annotation.getLineRanges()); | |
LineRange activeRange = null; | |
boolean activeRegularBlock = false; | |
int lineNumber = 0; | |
String line = null; | |
while((line = reader.readLine()) != null) { | |
lineNumber++; | |
/** Start highlight block **/ | |
if(activeRange == null) { | |
boolean newBlock = false; | |
ranges: for(LineRange r :ranges) { | |
if(r.getStart() == lineNumber) { | |
newBlock = true; | |
activeRange = r; | |
break ranges; | |
} | |
} | |
if(newBlock) { | |
writer.append("</code><code class=\"highlight\">"); | |
} | |
} | |
/** Code **/ | |
writer.append(line); | |
writer.append("\n"); | |
/** Clode highlight block with bug description **/ | |
if(activeRange != null) { | |
if (lineNumber == activeRange.getEnd()) { //Closing active block | |
ranges.remove(activeRange); | |
activeRange = null; | |
writer.append("</code>"); | |
writer.append("<div class=\"analysis-warning\"><i class=\"fas fa-exclamation-triangle\"></i> " + | |
"<span class=\"analysis-warning-title\">"+annotation.getMessage() + "</span>\n"); | |
writer.append("<div class=\"analysis-detail\">"); | |
writer.append(annotation.getToolTip()); | |
writer.append("</div></div>\n"); | |
writer.append("<code>"); | |
} | |
} | |
} | |
IOUtils.copy(inputStream, writer, "UTF-8"); | |
writer.append("</code></pre>"); | |
return writer.toString(); | |
} | |
@Override | |
public String getDisplayName() { | |
return fileName; | |
} | |
/** | |
* Highlights the specified source and returns the result as an HTML string. | |
* | |
* @param file | |
* the source file to highlight | |
* @return the source as an HTML string | |
* @throws IOException | |
* if the source code could not be read | |
*/ | |
public final String highlightSource(final InputStream file) throws IOException { | |
JavaSource source = new JavaSourceParser().parse( | |
new InputStreamReader(file, EncodingValidator.defaultCharset(defaultEncoding))); | |
JavaSource2HTMLConverter converter = new JavaSource2HTMLConverter(); | |
StringWriter writer = new StringWriter(); | |
JavaSourceConversionOptions options = JavaSourceConversionOptions.getDefault(); | |
options.setShowLineNumbers(true); | |
options.setAddLineAnchors(true); | |
converter.convert(source, options, writer); | |
return writer.toString(); | |
} | |
/** | |
* Splits the source code into three blocks: the line to highlight and the | |
* source code before and after this line. | |
* | |
* @param sourceFile | |
* the source code of the whole file as rendered HTML string | |
*/ | |
// CHECKSTYLE:CONSTANTS-OFF | |
public final void splitSourceFile(final String sourceFile) { | |
StringBuilder output = new StringBuilder(sourceFile.length()); | |
LineIterator lineIterator = IOUtils.lineIterator(new StringReader(sourceFile)); | |
int lineNumber = 1; | |
try { | |
while (lineNumber < SOURCE_GENERATOR_OFFSET) { | |
copyLine(output, lineIterator); | |
lineNumber++; | |
} | |
lineNumber = 1; | |
boolean isFirstRange = true; | |
for (LineRange range : annotation.getLineRanges()) { | |
while (lineNumber < range.getStart()) { | |
copyLine(output, lineIterator); | |
lineNumber++; | |
} | |
output.append("</code>\n"); | |
output.append("</td></tr>\n"); | |
output.append("<tr><td style=\"background-color:"); | |
appendRangeColor(output, isFirstRange); | |
output.append("\">\n"); | |
output.append("<div id=\"line"); | |
output.append(range.getStart()); | |
output.append("\" tooltip=\""); | |
if (range.getStart() > 0) { | |
outputEscaped(output, annotation.getMessage()); | |
} | |
outputEscaped(output, annotation.getToolTip()); | |
output.append("\" nodismiss=\"\">\n"); | |
output.append("<code><b>\n"); | |
if (range.getStart() <= 0) { | |
output.append(annotation.getMessage()); | |
if (StringUtils.isBlank(annotation.getMessage())) { | |
output.append(annotation.getToolTip()); | |
} | |
} | |
else { | |
while (lineNumber <= range.getEnd()) { | |
copyLine(output, lineIterator); | |
lineNumber++; | |
} | |
} | |
output.append("</b></code>\n"); | |
output.append("</div>\n"); | |
output.append("</td></tr>\n"); | |
output.append("<tr><td>\n"); | |
output.append("<code>\n"); | |
isFirstRange = false; | |
} | |
while (lineIterator.hasNext()) { | |
copyLine(output, lineIterator); | |
} | |
} | |
catch (NoSuchElementException exception) { | |
// ignore an illegal range | |
} | |
sourceCode = output.toString(); | |
} | |
// CHECKSTYLE:CONSTANTS-ON | |
/** | |
* Writes the message to the output stream (with escaped HTML). | |
* @param output the output to write to | |
* @param message | |
* the message to write | |
*/ | |
private void outputEscaped(final StringBuilder output, final String message) { | |
output.append(StringEscapeUtils.escapeHtml(message)); | |
} | |
/** | |
* Appends the right range color. | |
* | |
* @param output the output to append the color | |
* @param isFirstRange determines whether the range is the first one | |
*/ | |
private void appendRangeColor(final StringBuilder output, final boolean isFirstRange) { | |
if (isFirstRange) { | |
output.append(FIRST_COLOR); | |
} | |
else { | |
output.append(OTHER_COLOR); | |
} | |
} | |
/** | |
* Copies the next line of the input to the output. | |
* | |
* @param output output | |
* @param lineIterator input | |
*/ | |
private void copyLine(final StringBuilder output, final LineIterator lineIterator) { | |
output.append(lineIterator.nextLine()); | |
output.append("\n"); | |
} | |
/** | |
* Gets the file name of this source file. | |
* | |
* @return the file name | |
*/ | |
public String getFileName() { | |
return fileName; | |
} | |
/** | |
* Returns the build as owner of this object. | |
* | |
* @return the build | |
*/ | |
@WithBridgeMethods(value=AbstractBuild.class, adapterMethod="getAbstractBuild") | |
public Run<?, ?> getOwner() { | |
return owner; | |
} | |
/** | |
* Added for backward compatibility. It generates <pre>AbstractBuild getOwner()</pre> bytecode during the build | |
* process, so old implementations can use that signature. | |
* | |
* @see {@link WithBridgeMethods} | |
*/ | |
@Deprecated | |
private final Object getAbstractBuild(Run owner, Class targetClass) { | |
return owner instanceof AbstractBuild ? (AbstractBuild) owner : null; | |
} | |
/** | |
* Returns the line that should be highlighted. | |
* | |
* @return the line to highlight | |
*/ | |
public String getSourceCode() { | |
return sourceCode; | |
} | |
/** | |
* Creates a new instance of this source code object. | |
* | |
* @param owner | |
* the current build as owner of this object | |
* @param annotation | |
* the warning to display in the source file | |
* @param defaultEncoding | |
* the default encoding to be used when reading and parsing files | |
* @deprecated use {@link #SourceDetail(Run, FileAnnotation, String)} instead | |
*/ | |
@Deprecated | |
public SourceDetail(final AbstractBuild<?, ?> owner, final FileAnnotation annotation, final String defaultEncoding) { | |
this((Run<?, ?>) owner, annotation, defaultEncoding); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment