package hudson.plugins.violations.render; import java.util.Map; import java.util.Set; import hudson.model.AbstractBuild; import java.util.logging.Level; import java.util.logging.Logger; import java.io.File; import hudson.plugins.violations.parse.ParseXML; import hudson.plugins.violations.parse.FileModelParser; import hudson.plugins.violations.model.Severity; import hudson.plugins.violations.model.FileModel; import hudson.plugins.violations.model.Violation; import hudson.plugins.violations.generate.XMLUtil; /** * A proxy class for FileModel used to allow * lazy loading of FileModel. This class * is also used to render the FileModel. */ public class FileModelProxy { private static final Logger LOG = Logger.getLogger(FileModelProxy.class.getName()); private final File xmlFile; private FileModel fileModel; private String contextPath; private AbstractBuild<?, ?> build; /** * Construct this proxy. * @param xmlFile the xmlfile to create the FileModel from. */ public FileModelProxy(File xmlFile) { this.xmlFile = xmlFile; } /** * Fluid setting of the build attribute. * @param build the owner build. * @return this object. */ public FileModelProxy build(AbstractBuild<?, ?> build) { this.build = build; return this; } /** * Fluid setting of context path. * This is used for getting icons. * @param contextPath the current WEB context path. * @return this object. */ public FileModelProxy contextPath(String contextPath) { this.contextPath = contextPath; return this; } /** * get the current build. * @return the current build. */ public AbstractBuild<?, ?> getBuild() { return build; } /** * Get the type line. * @param type the violation type. * @return a rendered line containing the number of violations * and the number of suppressed violations. */ public String typeLine(String type) { FileModel.LimitType l = fileModel.getLimitTypeMap().get(type); StringBuilder b = new StringBuilder(); if (l == null) { return type + " ?number?"; } b.append(type); b.append("   "); b.append(l.getNumber()); b.append(" violation"); if (l.getNumber() > 1) { b.append("s"); } if (l.getSuppressed() != 0) { b.append(" ("); b.append(l.getSuppressed()); b.append(" not shown)"); } return b.toString(); } /** * Wheter to show lines. * @return true if the file model contains lines. */ public boolean getShowLines() { return getFileModel().getLines().size() != 0; } private void addBlock( StringBuilder ret, StringBuilder b, int startLine, int previousLine) { if (b.length() == 0) { return; } ret.append("<tr><td colspan='3' class='source heading'>"); ret.append("File: " + new File(fileModel.getDisplayName()).getName()); ret.append(" Lines "); ret.append(startLine + " to " + previousLine); ret.append("</td></tr>\n"); ret.append(b.toString()); ret.append("<tr><td class='source empty' colspan='3'> "); ret.append("</td></tr>\n"); } /** * This gets called from the index.jelly script to * render the marked up contents of the file. * @return a table of lines and associated violations in html. */ public String getFileContent() { StringBuilder ret = new StringBuilder(); StringBuilder b = new StringBuilder(); ret.append("<table class='source'>\n"); int previousLine = -1; int startLine = 0; int currentLine = -1; for (Map.Entry<Integer, String> e : fileModel.getLines().entrySet()) { currentLine = e.getKey(); String line = e.getValue(); // Check if at start of block if (currentLine != (previousLine + 1)) { // Start of block // Check if need to write previous block addBlock(ret, b, startLine, previousLine); b = new StringBuilder(); startLine = currentLine; } previousLine = currentLine; Set<Violation> v = fileModel.getLineViolationMap().get(currentLine); // TR b.append("<tr " + (v != null ? "class='violation'" : "") + ">"); // Icon if (v != null) { showIcon(b, v); } else { b.append("<td class='source icon'/>\n"); } // Line number b.append("<td class='source line' id='line" + currentLine + "'>"); if (v != null) { addVDiv(b); } b.append(currentLine); if (v != null) { showDiv(b, v); b.append("</div>"); } b.append("</td>"); b.append("<td class='source message'>"); b.append(XMLUtil.escapeHTMLContent( contextPath + "/plugin/violations/images/tab.png", line)); b.append("</td>\n"); b.append("</tr>\n"); } addBlock(ret, b, startLine, previousLine); ret.append("</table>"); return ret.toString(); } /** * Get the file model. * If the file model is present, return it, otherwise * parse the xml file. * @return the file model or null if unable to parse. */ public FileModel getFileModel() { if (fileModel != null) { return fileModel; } if (!xmlFile.exists()) { LOG.log(Level.WARNING, "The file " + xmlFile + " does not exist"); return null; } try { FileModel t = new FileModel(); ParseXML.parse(xmlFile, new FileModelParser().fileModel(t)); fileModel = t; return fileModel; } catch (Exception ex) { LOG.log(Level.WARNING, "Unable to parse " + xmlFile, ex); return null; } } private String getSeverityIcon(int level) { String color = null; switch (level) { case Severity.HIGH_VALUE: color = "red"; break; case Severity.LOW_VALUE: color = "yellow"; break; default: color = "violet"; // medium (low,-,high) } return "/plugin/violations/images/16x16/" + color + "-warning.png"; } /** * Get the severity column for a violation. * @param v the violation. * @return a string to place in the severity column of the violation table. */ public String severityColumn(Violation v) { StringBuilder b = new StringBuilder(); addVDiv(b); b.append("<a class='healthReport'>"); b.append( "<img src='" + contextPath + getSeverityIcon(v.getSeverityLevel()) + "' alt='" + v.getSeverity() + "'/>"); b.append("</a>"); b.append("<div class='healthReportDetails'>\n"); b.append("<table class='violationPopup'>\n"); b.append("<tr>\n"); b.append("<th>Severity</th>\n"); b.append("<td>"); b.append(v.getSeverity()); b.append("</td>\n"); b.append("</tr>\n"); b.append("<tr>\n"); b.append("<th>Class</th>\n"); b.append("<td>"); b.append(v.getSource()); b.append("</td>\n"); b.append("</tr>\n"); b.append("<tr>\n"); b.append("<th>Detail</th>\n"); b.append("<td class='message'>"); b.append(v.getSourceDetail()); b.append("</td>\n"); b.append("</tr>\n"); b.append("</table>\n"); b.append("</div>\n"); b.append("</div>\n"); return b.toString(); } private void addVDiv(StringBuilder b) { b.append("<div class='healthReport'"); b.append( "onmouseover=\"this.className='healthReport hover';return true;\""); b.append("onmouseout=\"this.className='healthReport';return true;\">"); } private void showIcon( StringBuilder b, Set<Violation> violations) { // Get the worst icon in the set int level = Severity.LOW_VALUE; for (Violation v: violations) { if (v.getSeverityLevel() < level) { level = v.getSeverityLevel(); } } b.append("<td class='source icon'>"); addVDiv(b); b.append("<a class='healthReport'>"); b.append( "<img src='" + contextPath + getSeverityIcon(level) + "'/>"); b.append("</a>"); showDiv(b, violations); b.append("</div>"); b.append("</td>"); } private void showDiv( StringBuilder b, Set<Violation> violations) { b.append("<div class='healthReportDetails'>\n"); b.append(" <table class='violationPopup'>\n"); b.append(" <thead>\n"); b.append(" <tr>\n"); b.append(" <th> Type</th>\n"); b.append(" <th> Class</th>\n"); b.append(" <th> Description</th>\n"); b.append(" </tr>\n"); b.append(" </thead>\n"); b.append(" <tbody>\n"); for (Violation v: violations) { b.append(" <tr>\n"); b.append(" <td>"); b.append(v.getType()); b.append("</td>\n"); b.append(" <td>"); b.append(v.getSource()); b.append("</td>\n"); b.append(" <td width='100%' class='message'>"); b.append(XMLUtil.escapeContent(v.getPopupMessage())); b.append("</td>\n"); b.append(" </tr>\n"); } b.append(" </tbody>\n"); b.append(" </table>\n"); b.append("</div>\n"); } }