Index: src/main/java/hudson/plugins/violations/TypeDescriptor.java
===================================================================
--- src/main/java/hudson/plugins/violations/TypeDescriptor.java	(revision 33635)
+++ src/main/java/hudson/plugins/violations/TypeDescriptor.java	(working copy)
@@ -14,6 +14,7 @@
 import hudson.plugins.violations.types.stylecop.StyleCopDescriptor;
 import hudson.plugins.violations.types.jcreport.JcReportDescriptor;
 import hudson.plugins.violations.types.jslint.JsLintDescriptor;
+import hudson.plugins.violations.types.codenarc.CodenarcDescriptor;
 
 /**
  * A descriptor for a violation type.
@@ -89,6 +90,7 @@
         addDescriptor(GendarmeDescriptor.DESCRIPTOR);
         addDescriptor(JcReportDescriptor.DESCRIPTOR);
         addDescriptor(JsLintDescriptor.DESCRIPTOR);
+        addDescriptor(CodenarcDescriptor.DESCRIPTOR);
     }
 }
 
Index: src/main/java/hudson/plugins/violations/types/codenarc/CodenarcDescriptor.java
===================================================================
--- src/main/java/hudson/plugins/violations/types/codenarc/CodenarcDescriptor.java	(revision 0)
+++ src/main/java/hudson/plugins/violations/types/codenarc/CodenarcDescriptor.java	(revision 0)
@@ -0,0 +1,46 @@
+package hudson.plugins.violations.types.codenarc;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import hudson.plugins.violations.TypeDescriptor;
+import hudson.plugins.violations.parse.AbstractTypeParser;
+
+/**
+ * The descriptor class for Codenarc violations type.
+ * @author Robin Bramley
+ */
+public final class CodenarcDescriptor
+    extends TypeDescriptor {
+
+    /** The descriptor for the codenarc violations type. */
+    public static final CodenarcDescriptor DESCRIPTOR
+        = new CodenarcDescriptor();
+
+    private CodenarcDescriptor() {
+        super("codenarc");
+    }
+
+    /**
+     * Create a parser for the codenarc type.
+     * @return a new codenarc parser.
+     */
+    @Override
+    public AbstractTypeParser createParser() {
+        return new CodenarcParser();
+    }
+
+    /**
+     * Get a list of target xml files to look for
+     * for this particular type.
+     * @return a list filenames to look for in the target
+     *         target directory.
+     */
+    @Override
+    public List<String> getMavenTargets() {
+        List<String> ret = new ArrayList<String>();
+        ret.add("CodeNarcXmlReport.xml");
+        return ret;
+    }
+}
+
Index: src/main/java/hudson/plugins/violations/types/codenarc/CodenarcParser.java
===================================================================
--- src/main/java/hudson/plugins/violations/types/codenarc/CodenarcParser.java	(revision 0)
+++ src/main/java/hudson/plugins/violations/types/codenarc/CodenarcParser.java	(revision 0)
@@ -0,0 +1,129 @@
+package hudson.plugins.violations.types.codenarc;
+
+import java.io.IOException;
+
+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.util.HashMapWithDefault;
+import hudson.plugins.violations.parse.AbstractTypeParser;
+
+/**
+ * Parses a codenarc xml report file.
+ * @author Robin Bramley, Opsera Ltd.
+ */
+public class CodenarcParser extends AbstractTypeParser {
+
+    private static final HashMapWithDefault<String, String> SEVERITIES
+        = new HashMapWithDefault<String, String>(Severity.MEDIUM);
+
+    static {
+        SEVERITIES.put("1", Severity.HIGH);
+        SEVERITIES.put("2", Severity.MEDIUM);
+        SEVERITIES.put("3", Severity.LOW);
+    }
+
+    /**
+     * Parse the codenarc xml file.
+     * @throws IOException if there is a problem reading the file.
+     * @throws XmlPullParserException if there is a problem parsing the file.
+     */
+    protected void execute()
+        throws IOException, XmlPullParserException {
+
+        // Ensure that the top level tag is "CodeNarc"
+        expectNextTag("CodeNarc");
+        getParser().next(); // consume the "CodeNarc" tag
+        // loop through the child elements, getting the "file" ones
+        while (skipToTag("Package")) {
+            String path = checkNotBlank("path");
+            
+            getParser().next();
+            while (skipToTag("File")) {
+                parseFileElement(path);
+            }
+            endElement();
+        }
+        endElement(); // CodeNarc
+    }
+
+/*
+<Package path='grails-app/controllers' totalFiles='30' filesWithViolations='3' priority1='0' priority2='2' priority3='3'>
+  <File name='LoginController.groovy'>
+    <Violation ruleName='UnusedImport' priority='3' lineNumber='2'>
+      <SourceLine><![CDATA[import org.grails.plugins.springsecurity.service.AuthenticateService]]></SourceLine>
+    </Violation>
+  </File>
+  <File name='RegisterController.groovy'>
+    <Violation ruleName='UnusedImport' priority='3' lineNumber='4'>
+      <SourceLine><![CDATA[import org.springframework.security.providers.UsernamePasswordAuthenticationToken as AuthToken]]></SourceLine>
+    </Violation>
+    <Violation ruleName='UnusedImport' priority='3' lineNumber='5'>
+      <SourceLine><![CDATA[import org.springframework.security.context.SecurityContextHolder as SCH]]></SourceLine></Violation>
+    <Violation ruleName='GrailsPublicControllerMethod' priority='2' lineNumber='226'>
+      <SourceLine><![CDATA[def sendValidationEmail(def person) {]]></SourceLine>
+    </Violation>
+  </File>
+</Package>
+*/
+
+    /**
+     * Handle a Codenarc File element.
+     */
+    private void parseFileElement(String path)
+        throws IOException, XmlPullParserException {
+        
+        //TODO: This doesn't always work if files are under e.g. workspace/trunk [workaround is to use the faux project path]
+        String absoluteFileName = fixAbsolutePath(getProjectPath().getAbsolutePath() + "/" + path + "/" + checkNotBlank("name"));
+        getParser().next();  // consume "file" tag
+        FullFileModel fileModel = getFileModel(absoluteFileName);
+
+        // Loop through the child elements, getting the violations
+        while (skipToTag("Violation")) {
+            fileModel.addViolation(parseViolationElement());
+            endElement();
+        }
+        endElement();
+    }
+
+    /**
+     * Convert a Codenarc violation to a Hudson Violation.
+     * @return Violation
+     */
+    private Violation parseViolationElement()
+        throws IOException, XmlPullParserException {
+
+        Violation ret = new Violation();
+        ret.setType("codenarc");
+        ret.setLine(getString("lineNumber"));
+        ret.setMessage(getString("ruleName"));
+        setSeverity(ret, getString("priority"));
+        getParser().next();
+
+        // get the contents of the embedded SourceLine element
+        expectNextTag("SourceLine");
+        ret.setSource(getNextText("Missing SourceLine"));
+
+        //TODO: the following depends upon a patch to CodeNarc 0.9 - so should be exception tolerant
+        // get the contents of the embedded Description element
+        //expectNextTag("Description");
+        //ret.setPopupMessage(getNextText("Missing Description"));
+
+        getParser().next();
+        endElement();
+        return ret;
+    }
+
+    /**
+     * Map severities from Codenarc to Hudson.
+     */
+    private void setSeverity(Violation v, String severity) {
+        v.setSeverity(SEVERITIES.get(severity));
+        v.setSeverityLevel(Severity.getSeverityLevel(
+                               v.getSeverity()));
+    }
+}
Index: src/test/java/hudson/plugins/violations/types/codenarc/CodenarcParserTest.java
===================================================================
--- src/test/java/hudson/plugins/violations/types/codenarc/CodenarcParserTest.java	(revision 0)
+++ src/test/java/hudson/plugins/violations/types/codenarc/CodenarcParserTest.java	(revision 0)
@@ -0,0 +1,47 @@
+package hudson.plugins.violations.types.codenarc;
+
+import static org.junit.Assert.*;
+import hudson.plugins.violations.model.FullBuildModel;
+import hudson.plugins.violations.model.Violation;
+import hudson.plugins.violations.types.codenarc.CodenarcParser;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Iterator;
+
+import org.junit.Test;
+
+/**
+ * Test codenarc against a test resource file.
+ *
+ * @author Robin Bramley, Opsera Ltd.
+ */
+public class CodenarcParserTest {
+    
+    private FullBuildModel getFullBuildModel(String filename) throws IOException {
+        URL url = getClass().getResource(filename);
+        File xmlFile;
+        try {
+            xmlFile = new File(url.toURI());
+        } catch(URISyntaxException e) {
+            xmlFile = new File(url.getPath());
+        }
+        
+        CodenarcParser parser = new CodenarcParser();
+        FullBuildModel model = new FullBuildModel();
+        parser.parse(model, xmlFile.getParentFile(), xmlFile.getName(), null);
+        model.cleanup();
+        return model;
+    }
+    
+    @Test
+    public void testParseFullBuildModelFromFile() throws Exception {
+        FullBuildModel model = getFullBuildModel("CodeNarcXmlReport.xml");
+        
+        assertEquals("Number of violations is incorrect", 9, model.getCountNumber("codenarc"));
+        assertEquals("Number of files is incorrect", 7, model.getFileModelMap().size());
+    }
+
+}
Index: src/test/resources/hudson/plugins/violations/types/codenarc/CodeNarcXmlReport.xml
===================================================================
--- src/test/resources/hudson/plugins/violations/types/codenarc/CodeNarcXmlReport.xml	(revision 0)
+++ src/test/resources/hudson/plugins/violations/types/codenarc/CodeNarcXmlReport.xml	(revision 0)
@@ -0,0 +1,2 @@
+<?xml version="1.0"?>
+<CodeNarc url='http://www.codenarc.org' version='0.9'><Report timestamp='09-Aug-2010 15:27:41'/><Project title=''><SourceDirectory></SourceDirectory></Project><PackageSummary totalFiles='103' filesWithViolations='7' priority1='0' priority2='4' priority3='5'></PackageSummary><Package path='grails-app' totalFiles='54' filesWithViolations='3' priority1='0' priority2='2' priority3='3'></Package><Package path='grails-app/controllers' totalFiles='30' filesWithViolations='3' priority1='0' priority2='2' priority3='3'><File name='LoginController.groovy'><Violation ruleName='UnusedImport' priority='3' lineNumber='2'><SourceLine><![CDATA[import org.grails.plugins.springsecurity.service.AuthenticateService]]></SourceLine></Violation></File><File name='RegisterController.groovy'><Violation ruleName='UnusedImport' priority='3' lineNumber='4'><SourceLine><![CDATA[import org.springframework.security.providers.UsernamePasswordAuthenticationToken as AuthToken]]></SourceLine></Violation><Violation ruleName='UnusedImport' priority='3' lineNumber='5'><SourceLine><![CDATA[import org.springframework.security.context.SecurityContextHolder as SCH]]></SourceLine></Violation><Violation ruleName='GrailsPublicControllerMethod' priority='2' lineNumber='226'><SourceLine><![CDATA[def sendValidationEmail(def person) {]]></SourceLine></Violation></File></Package><Package path='grails-app/controllers/acme' totalFiles='25' filesWithViolations='1' priority1='0' priority2='1' priority3='0'><File name='ABCController.groovy'><Violation ruleName='GrailsPublicControllerMethod' priority='2' lineNumber='163'><SourceLine><![CDATA[def isAdminOrOwner() {]]></SourceLine></Violation></File></Package><Package path='grails-app/domain' totalFiles='23' filesWithViolations='0' priority1='0' priority2='0' priority3='0'></Package><Package path='grails-app/domain/acme' totalFiles='23' filesWithViolations='0' priority1='0' priority2='0' priority3='0'></Package><Package path='grails-app/taglib' totalFiles='1' filesWithViolations='0' priority1='0' priority2='0' priority3='0'></Package><Package path='grails-app/taglib/acme' totalFiles='1' filesWithViolations='0' priority1='0' priority2='0' priority3='0'></Package><Package path='src' totalFiles='1' filesWithViolations='0' priority1='0' priority2='0' priority3='0'></Package><Package path='src/groovy' totalFiles='1' filesWithViolations='0' priority1='0' priority2='0' priority3='0'></Package><Package path='src/groovy/com' totalFiles='1' filesWithViolations='0' priority1='0' priority2='0' priority3='0'></Package><Package path='test' totalFiles='48' filesWithViolations='4' priority1='0' priority2='2' priority3='2'></Package><Package path='test/unit' totalFiles='48' filesWithViolations='4' priority1='0' priority2='2' priority3='2'></Package><Package path='test/unit/com' totalFiles='1' filesWithViolations='0' priority1='0' priority2='0' priority3='0'></Package><Package path='test/unit/acme' totalFiles='47' filesWithViolations='4' priority1='0' priority2='2' priority3='2'><File name='GHITests.groovy'><Violation ruleName='UnusedVariable' priority='2' lineNumber='17'><SourceLine><![CDATA[def ct=new GHIType(name:"ct")]]></SourceLine></Violation></File><File name='ABCControllerTests.groovy'><Violation ruleName='UnusedImport' priority='3' lineNumber='5'><SourceLine><![CDATA[import org.codehaus.groovy.grails.plugins.testing.GrailsMockHttpServletRequest]]></SourceLine></Violation></File><File name='XYZControllerTests.groovy'><Violation ruleName='UnusedImport' priority='3' lineNumber='4'><SourceLine><![CDATA[import org.codehaus.groovy.grails.plugins.testing.GrailsMockHttpServletRequest]]></SourceLine></Violation></File><File name='XYZUtilsTagLibTests.groovy'><Violation ruleName='EmptyCatchBlock' priority='2' lineNumber='34'><SourceLine><![CDATA[}catch(GrailsTagException ge){]]></SourceLine></Violation></File></Package><Rules><Rule name='BooleanInstantiation'><Description><![CDATA[Use Boolean.valueOf() for variable values or Boolean.TRUE and Boolean.FALSE for constant values instead of calling the Boolean() constructor directly or calling Boolean.valueOf(true) or Boolean.valueOf(false).]]></Description></Rule><Rule name='CatchError'><Description><![CDATA[Catching Error is dangerous; it can catch exceptions such as ThreadDeath and OutOfMemoryError.]]></Description></Rule><Rule name='CatchException'><Description><![CDATA[Catching Exception is often too broad or general. It should usually be restricted to framework or infrastructure code, rather than application code.]]></Description></Rule><Rule name='CatchNullPointerException'><Description><![CDATA[Catching NullPointerException is never appropriate. It should be avoided in the first place with proper null checking, and it can mask underlying errors.]]></Description></Rule><Rule name='CatchRuntimeException'><Description><![CDATA[Catching RuntimeException is often too broad or general. It should usually be restricted to framework or infrastructure code, rather than application code.]]></Description></Rule><Rule name='CatchThrowable'><Description><![CDATA[Catching Throwable is dangerous; it can catch exceptions such as ThreadDeath and OutOfMemoryError.]]></Description></Rule><Rule name='CloneableWithoutClone'><Description><![CDATA[A class that implements java.lang.Cloneable should define a clone() method.]]></Description></Rule><Rule name='ConstantIfExpression'><Description><![CDATA[Checks for if statements with a constant value for the if expression, such as true, false, null, or a literal constant value.]]></Description></Rule><Rule name='ConstantTernaryExpression'><Description><![CDATA[Checks for ternary expressions with a constant value for the boolean expression, such as true, false, null, or a literal constant value.]]></Description></Rule><Rule name='DuplicateImport'><Description><![CDATA[Duplicate import statements are unnecessary.]]></Description></Rule><Rule name='EmptyCatchBlock'><Description><![CDATA[In most cases, exceptions should not be caught and ignored (swallowed).]]></Description></Rule><Rule name='EmptyElseBlock'><Description><![CDATA[Empty else blocks are confusing and serve no purpose.]]></Description></Rule><Rule name='EmptyFinallyBlock'><Description><![CDATA[Empty finally blocks are confusing and serve no purpose.]]></Description></Rule><Rule name='EmptyForStatement'><Description><![CDATA[Empty for statements are confusing and serve no purpose.]]></Description></Rule><Rule name='EmptyIfStatement'><Description><![CDATA[Empty if statements are confusing and serve no purpose.]]></Description></Rule><Rule name='EmptySwitchStatement'><Description><![CDATA[Empty switch statements are confusing and serve no purpose.]]></Description></Rule><Rule name='EmptySynchronizedStatement'><Description><![CDATA[Empty synchronized statements are confusing and serve no purpose.]]></Description></Rule><Rule name='EmptyTryBlock'><Description><![CDATA[Empty try blocks are confusing and serve no purpose.]]></Description></Rule><Rule name='EmptyWhileStatement'><Description><![CDATA[Empty while statements are confusing and serve no purpose.]]></Description></Rule><Rule name='EqualsAndHashCode'><Description><![CDATA[If either the equals(Object) or the hashCode() methods are overridden within a class, then both must be overridden.]]></Description></Rule><Rule name='GrailsPublicControllerMethod'><Description><![CDATA[Checks for public methods on Grails controller classes. Static methods are ignored.]]></Description></Rule><Rule name='GrailsServletContextReference'><Description><![CDATA[Checks for references to the servletContext object from within Grails controller and taglib classes.]]></Description></Rule><Rule name='GrailsStatelessService'><Description><![CDATA[Checks for fields on Grails service classes. Grails service classes are singletons, by default, and so they should be reentrant and typically stateless. The ignoreFieldNames property specifies one or more field names that should be ignored. The ignoreFieldTypes property specifies one or more field type names that should be ignored. Both can optionally contain wildcard characters ('*' or '?').]]></Description></Rule><Rule name='ImportFromSamePackage'><Description><![CDATA[An import of a class that is within the same package is unnecessary.]]></Description></Rule><Rule name='ReturnFromFinallyBlock'><Description><![CDATA[Returning from a finally block is confusing and can hide the original exception.]]></Description></Rule><Rule name='StringInstantiation'><Description><![CDATA[Use a String literal (e.g., "...") instead of calling the corresponding String constructor (new String("..")) directly.]]></Description></Rule><Rule name='ThrowError'><Description><![CDATA[Checks for throwing an instance of java.lang.Error.]]></Description></Rule><Rule name='ThrowException'><Description><![CDATA[Checks for throwing an instance of java.lang.Exception.]]></Description></Rule><Rule name='ThrowExceptionFromFinallyBlock'><Description><![CDATA[Throwing an exception from a finally block is confusing and can hide the original exception.]]></Description></Rule><Rule name='ThrowNullPointerException'><Description><![CDATA[Checks for throwing an instance of java.lang.NullPointerException.]]></Description></Rule><Rule name='ThrowRuntimeException'><Description><![CDATA[Checks for throwing an instance of <em>java.lang.RuntimeException</em>.]]></Description></Rule><Rule name='ThrowThrowable'><Description><![CDATA[Checks for throwing an instance of java.lang.Throwable.]]></Description></Rule><Rule name='UnnecessaryGroovyImport'><Description><![CDATA[A Groovy file does not need to include an import for classes from java.lang, java.util, java.io, java.net, groovy.lang and groovy.util, as well as the classes java.math.BigDecimal and java.math.BigInteger.]]></Description></Rule><Rule name='UnnecessaryTernaryExpression'><Description><![CDATA[Checks for ternary expressions where the conditional expression always evaluates to a boolean and the true and false expressions are merely returning true and false constants. Also checks for ternary expressions where both expressions are the same constant or variable.]]></Description></Rule><Rule name='UnusedImport'><Description><![CDATA[Imports for a class that is never referenced within the source file is unnecessary.]]></Description></Rule><Rule name='UnusedPrivateField'><Description><![CDATA[Checks for private fields that are not referenced within the same class.]]></Description></Rule><Rule name='UnusedPrivateMethod'><Description><![CDATA[Checks for private methods that are not referenced within the same class.]]></Description></Rule><Rule name='UnusedVariable'><Description><![CDATA[Checks for variables that are never referenced.]]></Description></Rule></Rules></CodeNarc>
\ No newline at end of file