package hudson.plugins.violations.types.simian; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import org.xmlpull.v1.XmlPullParserException; import hudson.plugins.violations.model.FullFileModel; import hudson.plugins.violations.model.Severity; import hudson.plugins.violations.model.Violation; import hudson.plugins.violations.parse.AbstractTypeParser; import hudson.plugins.violations.util.StringUtil; /** * Parser for Simian XML files. * * Simian (Similarity Analyser) identifies duplication in any text files. * @see http://www.redhillconsulting.com.au/products/simian/ */ public class SimianParser extends AbstractTypeParser { class DuplicationBlock { String absolutePath; String fileName; int startLineNumber; int endLineNumber; } static final String TYPE_NAME = "simian"; private static final int LOW_LIMIT = 10; private static final int MEDIUM_LIMIT = 50; @Override protected void execute() throws IOException, XmlPullParserException { expectNextTag("simian"); getParser().next(); while (skipToTag("check")) { getParser().next(); while (skipToTag("set")) { getParser().next(); parseSetElement(); endElement(); } endElement(); } endElement(); } /** * Parses the <set/> block and adds the found duplication violations. * @throws IOException thrown by XmlPullParser * @throws XmlPullParserException thrown by XmlPullParser */ private void parseSetElement() throws IOException, XmlPullParserException { List<DuplicationBlock> blocks = new ArrayList<DuplicationBlock>(); while (skipToTag("block")) { blocks.add(parseBlockElement()); getParser().next(); endElement(); } for (DuplicationBlock block : blocks) { List<DuplicationBlock> otherBlocks = new ArrayList<DuplicationBlock>(blocks); otherBlocks.remove(block); Violation violation = createViolation(block); setViolationMessages(violation, block, otherBlocks); FullFileModel fileModel = getFileModel(block.absolutePath); fileModel.addViolation(violation); } } /** * Parses the current <block/> and returns a DuplicationBlock. * @return a DuplicationBlock representing the <block/> * @throws XmlPullParserException thrown by XmlPullParser * @throws IOException thrown by XmlPullParser */ private DuplicationBlock parseBlockElement() throws XmlPullParserException, IOException { DuplicationBlock block = new DuplicationBlock(); block.absolutePath = fixAbsolutePath(checkNotBlank("sourceFile")).replace('\\', '/'); block.fileName = new File(block.absolutePath).getName(); block.startLineNumber = Integer.parseInt(getString("startLineNumber")); block.endLineNumber = Integer.parseInt(getString("endLineNumber")); return block; } /** * Sets the message and popupMessage on the violation and using the other blocks as references. * The popupMessage will set to something along the line * "Duplication of 20 lines from line 23, line 45 in ClassFoo.java". * The message will be set to something along the line * "Duplication of 20 lines from <a href=''>line 23</a>, <a href=''>line 45 in ClassFoo.java</a>". * @param violation the violation to update the messages on * @param block the current block that is referencing the other blocks * @param otherBlocks other blocks that are used for references in the messages. */ private void setViolationMessages(Violation violation, DuplicationBlock block, List<DuplicationBlock> otherBlocks) { StringBuilder popupMessage = new StringBuilder("Duplication of "); popupMessage.append(block.endLineNumber - block.startLineNumber + 1); popupMessage.append(" lines from "); StringBuilder message = new StringBuilder(popupMessage); int i = 0; int size = otherBlocks.size(); for (Iterator<DuplicationBlock> iterator = otherBlocks.iterator(); iterator.hasNext();) { DuplicationBlock otherViolation = iterator.next(); String hrefString = getOtherHrefString(block, otherViolation); String duplicationString = getOtherDuplicationString(block, otherViolation); message.append("<a href='"); message.append(hrefString); message.append("'>"); message.append(duplicationString); message.append("</a>"); popupMessage.append(duplicationString); String postfix; i++; if (i < (size - 1)) { postfix = ", "; } else if (i == (size - 1)){ postfix = " and "; } else { postfix = "."; } popupMessage.append(postfix); message.append(postfix); } violation.setPopupMessage(popupMessage.toString()); violation.setMessage(message.toString()); } /** * Returns a href for the blocks. * @param self the current block. * @param other the other block that the href is returned for. * @return a string containing the href for the other block file. */ private String getOtherHrefString(DuplicationBlock self, DuplicationBlock other) { String path = StringUtil.relativePath(self.absolutePath + "/bats", other.absolutePath); return path + "#line" + other.startLineNumber; } /** * Returns a string describing the duplication with line numbers. * @param self the current block * @param other the other block that should be described * @return a string describing the duplication with line numbers. */ private String getOtherDuplicationString(DuplicationBlock self, DuplicationBlock other) { StringBuilder builder = new StringBuilder(); builder.append("line "); builder.append(other.startLineNumber); if (! self.absolutePath.equals(other.absolutePath)) { builder.append(" in "); builder.append(other.fileName); } return builder.toString(); } /** * Create a violation from the duplication block * @param block the block to create a violation from * @return a violation */ private Violation createViolation(DuplicationBlock block) { Violation violation = new Violation(); violation.setType(TYPE_NAME); violation.setSource("duplication"); int startLineNumber = block.startLineNumber; int endLineNumber = block.endLineNumber; int lineCount = endLineNumber - startLineNumber + 1; violation.setLine(startLineNumber); setViolationSeverity( violation, (lineCount < LOW_LIMIT) ? Severity.LOW : (lineCount < MEDIUM_LIMIT) ? Severity.MEDIUM : Severity.HIGH); return violation; } /** * Sets violation severity. * @param v violation to update. * @param severity the severity text. */ private void setViolationSeverity(Violation v, String severity) { v.setSeverity(severity); v.setSeverityLevel(Severity.getSeverityLevel(severity)); } }