diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java index 042ead7aa0..b36f31565d 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/lang/rule/xpath/SaxonXPathRuleQuery.java @@ -14,12 +14,9 @@ import net.sourceforge.pmd.lang.ast.Node; import net.sourceforge.pmd.lang.ast.xpath.saxon.DocumentNode; import net.sourceforge.pmd.lang.ast.xpath.saxon.ElementNode; import net.sourceforge.pmd.lang.xpath.Initializer; -import net.sourceforge.pmd.properties.BooleanProperty; -import net.sourceforge.pmd.properties.EnumeratedProperty; -import net.sourceforge.pmd.properties.IntegerProperty; import net.sourceforge.pmd.properties.PropertyDescriptor; -import net.sourceforge.pmd.properties.StringProperty; +import net.sf.saxon.om.Item; import net.sf.saxon.om.ValueRepresentation; import net.sf.saxon.sxpath.AbstractStaticContext; import net.sf.saxon.sxpath.IndependentContext; @@ -29,9 +26,16 @@ import net.sf.saxon.sxpath.XPathExpression; import net.sf.saxon.sxpath.XPathStaticContext; import net.sf.saxon.sxpath.XPathVariable; import net.sf.saxon.trans.XPathException; +import net.sf.saxon.value.BigIntegerValue; import net.sf.saxon.value.BooleanValue; +import net.sf.saxon.value.DoubleValue; +import net.sf.saxon.value.EmptySequence; +import net.sf.saxon.value.FloatValue; import net.sf.saxon.value.Int64Value; +import net.sf.saxon.value.SequenceExtent; import net.sf.saxon.value.StringValue; +import net.sf.saxon.value.UntypedAtomicValue; + /** * This is a Saxon based XPathRule query. @@ -83,31 +87,7 @@ public class SaxonXPathRuleQuery extends AbstractXPathRuleQuery { String name = xpathVariable.getVariableQName().getLocalName(); for (Map.Entry, Object> entry : super.properties.entrySet()) { if (name.equals(entry.getKey().name())) { - PropertyDescriptor propertyDescriptor = entry.getKey(); - Object value = entry.getValue(); - ValueRepresentation valueRepresentation; - - // TODO Need to handle null values? - // TODO Need to handle more PropertyDescriptors, is - // there an easy factory in Saxon we can use for this? - if (propertyDescriptor instanceof StringProperty) { - valueRepresentation = new StringValue((String) value); - } else if (propertyDescriptor instanceof BooleanProperty) { - valueRepresentation = BooleanValue.get((Boolean) value); - } else if (propertyDescriptor instanceof IntegerProperty) { - valueRepresentation = Int64Value.makeIntegerValue((Integer) value); - } else if (propertyDescriptor instanceof EnumeratedProperty) { - if (value instanceof String) { - valueRepresentation = new StringValue((String) value); - } else { - throw new RuntimeException( - "Unable to create ValueRepresentaton for non-String EnumeratedProperty value: " - + value); - } - } else { - throw new RuntimeException("Unable to create ValueRepresentaton for PropertyDescriptor: " - + propertyDescriptor); - } + ValueRepresentation valueRepresentation = getRepresentation(entry.getKey(), entry.getValue()); xpathDynamicContext.setVariable(xpathVariable, valueRepresentation); } } @@ -123,6 +103,49 @@ public class SaxonXPathRuleQuery extends AbstractXPathRuleQuery { return results; } + + private ValueRepresentation getRepresentation(PropertyDescriptor descriptor, Object value) { + if (descriptor.isMultiValue()) { + List val = (List) value; + if (val.isEmpty()) { + return EmptySequence.getInstance(); + } + Item[] converted = new Item[val.size()]; + for (int i = 0; i < val.size(); i++) { + converted[i] = getItemRepresentation(val.get(i)); + } + return new SequenceExtent(converted); + } else { + return getItemRepresentation(value); + } + } + + + private Item getItemRepresentation(Object value) { + if (value == null) { + return UntypedAtomicValue.ZERO_LENGTH_UNTYPED; + } else if (value instanceof String) { + return new StringValue((String) value); + } else if (value instanceof Boolean) { + return BooleanValue.get((Boolean) value); + } else if (value instanceof Integer) { + return Int64Value.makeIntegerValue((Integer) value); + } else if (value instanceof Long) { + return new BigIntegerValue((Long) value); + } else if (value instanceof Double) { + return new DoubleValue((Double) value); + } else if (value instanceof Character) { + return new StringValue(value.toString()); + } else if (value instanceof Float) { + return new FloatValue((Float) value); + } else { + // We could maybe use UntypedAtomicValue + throw new RuntimeException("Unable to create ValueRepresentation " + + "for value of type: " + (value == null ? null : value.getClass())); + } + } + + private DocumentNode getDocumentNode(Node node) { // Get the root AST node Node root = node; diff --git a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptorUtil.java b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptorUtil.java index c332f95835..8949e91d30 100644 --- a/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptorUtil.java +++ b/pmd-core/src/main/java/net/sourceforge/pmd/properties/PropertyDescriptorUtil.java @@ -39,7 +39,7 @@ public class PropertyDescriptorUtil { temp.put("List[Float]", FloatMultiProperty.extractor()); temp.put("Double", DoubleProperty.extractor()); temp.put("List[Double]", DoubleMultiProperty.extractor()); - // temp.put("Enum", EnumeratedProperty.FACTORY); // TODO:cf implement that + // temp.put("Enum", EnumeratedProperty.FACTORY); // TODO:cf we need new syntax in the xml to support that // temp.put("List[Enum]", EnumeratedMultiProperty.FACTORY); temp.put("Class", TypeProperty.extractor()); diff --git a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java index 623e42797a..077e1b5db7 100644 --- a/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java +++ b/pmd-java/src/test/java/net/sourceforge/pmd/lang/java/rule/XPathRuleTest.java @@ -8,13 +8,16 @@ import static org.junit.Assert.assertEquals; import java.io.StringReader; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import org.junit.Before; import org.junit.Test; import net.sourceforge.pmd.PMD; +import net.sourceforge.pmd.PMDException; import net.sourceforge.pmd.Report; +import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RuleContext; import net.sourceforge.pmd.RuleSet; import net.sourceforge.pmd.RuleSetFactory; @@ -32,6 +35,7 @@ import net.sourceforge.pmd.lang.rule.xpath.JaxenXPathRuleQuery; import net.sourceforge.pmd.lang.rule.xpath.SaxonXPathRuleQuery; import net.sourceforge.pmd.lang.rule.xpath.XPathRuleQuery; import net.sourceforge.pmd.properties.PropertyDescriptor; +import net.sourceforge.pmd.properties.StringMultiProperty; import net.sourceforge.pmd.properties.StringProperty; import net.sourceforge.pmd.testframework.RuleTst; @@ -53,17 +57,36 @@ public class XPathRuleTest extends RuleTst { public void testPluginname() throws Exception { rule.setXPath("//VariableDeclaratorId[string-length(@Image) < 3]"); rule.setMessage("{0}"); - PMD p = new PMD(); - RuleContext ctx = new RuleContext(); - Report report = new Report(); - ctx.setReport(report); - ctx.setSourceCodeFilename("n/a"); - RuleSet rules = new RuleSetFactory().createSingleRuleRuleSet(rule); - p.getSourceCodeProcessor().processSourceCode(new StringReader(TEST1), new RuleSets(rules), ctx); + Report report = getReportForTestString(rule, TEST1); RuleViolation rv = report.iterator().next(); assertEquals("a", rv.getDescription()); } + + @Test + public void testXPathMultiProperty() throws Exception { + rule.setXPath("//VariableDeclaratorId[@Image=$forbiddenNames]"); + rule.setMessage("Avoid vars"); + rule.setVersion(XPathRuleQuery.XPATH_2_0); + StringMultiProperty varDescriptor + = StringMultiProperty.named("forbiddenNames") + .desc("Forbidden names") + .defaultValues("forbid1", "forbid2") + .delim('$') + .build(); + + rule.definePropertyDescriptor(varDescriptor); + + Report report = getReportForTestString(rule, TEST3); + Iterator rv = report.iterator(); + int i = 0; + for (; rv.hasNext(); ++i) { + rv.next(); + } + assertEquals(2, i); + } + + @Test public void testVariables() throws Exception { rule.setXPath("//VariableDeclaratorId[@Image=$var]"); @@ -71,17 +94,12 @@ public class XPathRuleTest extends RuleTst { StringProperty varDescriptor = new StringProperty("var", "Test var", null, 1.0f); rule.definePropertyDescriptor(varDescriptor); rule.setProperty(varDescriptor, "fiddle"); - PMD p = new PMD(); - RuleContext ctx = new RuleContext(); - Report report = new Report(); - ctx.setReport(report); - ctx.setSourceCodeFilename("n/a"); - RuleSet rules = new RuleSetFactory().createSingleRuleRuleSet(rule); - p.getSourceCodeProcessor().processSourceCode(new StringReader(TEST2), new RuleSets(rules), ctx); + Report report = getReportForTestString(rule, TEST2); RuleViolation rv = report.iterator().next(); assertEquals(3, rv.getBeginLine()); } + /** * Test for problem reported in bug #1219 PrimarySuffix/@Image does not work * in some cases in xpath 2.0 @@ -168,8 +186,24 @@ public class XPathRuleTest extends RuleTst { assertEquals(5, nodes.get(1).getBeginLine()); } + private static Report getReportForTestString(Rule r, String test) throws PMDException { + PMD p = new PMD(); + RuleContext ctx = new RuleContext(); + Report report = new Report(); + ctx.setReport(report); + ctx.setSourceCodeFilename("n/a"); + RuleSet rules = new RuleSetFactory().createSingleRuleRuleSet(r); + p.getSourceCodeProcessor().processSourceCode(new StringReader(test), new RuleSets(rules), ctx); + return report; + } + + private static final String TEST1 = "public class Foo {" + PMD.EOL + " int a;" + PMD.EOL + "}"; private static final String TEST2 = "public class Foo {" + PMD.EOL + " int faddle;" + PMD.EOL + " int fiddle;" + PMD.EOL + "}"; + + + private static final String TEST3 = "public class Foo {" + PMD.EOL + " int forbid1; int forbid2; int forbid1$forbid2;" + PMD.EOL + "}"; + }