//////////////////////////////////////////////////////////////////////////////// // checkstyle: Checks Java source code for adherence to a set of rules. // Copyright (C) 2001-2011 Oliver Burn // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// package com.puppycrawl.tools.checkstyle.checks; import com.puppycrawl.tools.checkstyle.api.XmlTokenTypes; import com.puppycrawl.tools.checkstyle.api.Check; import com.puppycrawl.tools.checkstyle.api.DetailAST; import java.io.StringReader; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import javax.xml.namespace.NamespaceContext; import javax.xml.transform.sax.SAXSource; import net.sf.saxon.Configuration; import net.sf.saxon.om.DocumentInfo; import net.sf.saxon.om.NodeInfo; import net.sf.saxon.query.DynamicQueryContext; import net.sf.saxon.query.StaticQueryContext; import net.sf.saxon.query.XQueryExpression; import net.sf.saxon.trans.XPathException; import org.xml.sax.InputSource; /** * <p> * Checks for the number of nodes matched by a XQuery expression . * </p> * <p> * Business: Implement logical rules to match your needs. * </p> * <p> * An example of how to configure the check so that it accepts files with at * no results for the following XQuery : * </p> * <pre> * <module name="XQueryCheck"> * <property name="expression" value=" * for $x in //ELT, * $name in $x/NAP_NAME/@value, * $server in $x/NET_OND_OUTGOING_NAP_DETAILS/OND_SERVER_ADDRESS/@value, * $port in $x/NET_OND_OUTGOING_NAP_DETAILS/OND_CONNECTION_PORT/@value * where $server != concat('%NAP_', $name, '_IP_ADDR%') * and $port != concat('%NAP_', $name, '_PORT%') * return $x * "/> * <property name="min" value="0"/> * <property name="max" value="0"/> * </module> * </pre> * @author Yoann Ciabaud<y.ciabaud@gmail.com> */ public class XQueryCheck extends Check { /** Default value for min and max. */ private static final int DEFAULT_VALUE = 0; /** Maximum number occurencies of the expression. */ private int max = DEFAULT_VALUE; /** Minimum number occurencies of the expression. */ private int min = DEFAULT_VALUE; /** String value of XQuery expression*/ private String expression; /** XQuery expression. */ private XQueryExpression xQueryExpression; private String[] namespaces; /** Static query context */ private StaticQueryContext sqc; /** Dynamic query context */ private DynamicQueryContext env; /** {@inheritDoc} */ @Override public int[] getDefaultTokens() { return new int[]{XmlTokenTypes.DOCUMENT}; } /** {@inheritDoc} */ @Override public void init() { super.init(); Configuration c = new Configuration(); c.setLineNumbering(true); try { sqc = new StaticQueryContext(c); if( namespaces != null ) { for( int index = 0; index < namespaces.length; index+=2) { sqc.declareNamespace(namespaces[index], namespaces[index+1]); } } env = new DynamicQueryContext(c); xQueryExpression = sqc.compileQuery(expression); } catch (XPathException ex) { log(0, "Invalid XQuery request: " + ex.getMessage()); ex.printStackTrace(); return; } } /** {@inheritDoc} */ @Override public void visitToken(DetailAST aAST) { if (getFileContents() == null) { log(aAST.getLineNo(), "no XML document available in this context"); return; } if (xQueryExpression == null) { log(aAST.getLineNo(), "no XQuery expression available in this context"); return; } List resultat = null; try{ final String fullText = getFileContents().getText().getFullText().toString(); InputSource document = new InputSource(); document.setCharacterStream(new StringReader(fullText)); DocumentInfo di = sqc.buildDocument(new SAXSource(document)); env.setContextItem(di.getRoot()); resultat = xQueryExpression.evaluate(env); } catch (XPathException ex) { log(0, "Invalid XQuery expression: " + expression); ex.printStackTrace(); } int nbMatches = resultat != null ? resultat.size() : 0; if (nbMatches < min ) { log(0, "xquery.lessMatches", expression, nbMatches, min); } else if( nbMatches > max) { for(int i=max; i<resultat.size(); i++){ int line = aAST.getLineNo(); int col = aAST.getColumnNo(); if(resultat.get(i) instanceof NodeInfo){ line = ((NodeInfo)resultat.get(i)).getLineNumber(); col = ((NodeInfo)resultat.get(i)).getColumnNumber() - 1; } log(line, col, "xquery.invalidPath", aAST.getText(), expression, nbMatches, min, max); } } } /** * Setter of max. * @param max the max value */ public void setMax(int max) { this.max = max; } /** * Setter of min. * @param min the min value */ public void setMin(int min) { this.min = min; } /** * Setter of expression. * @param expression the XQuery expression value */ public void setExpression(String expression) { this.expression = expression; } /** * Setter of namespaces. * For instance: * <pre> * <property name="namespaces" value="bk, urn:xmlns:25hoursaday-com:bookstore, inv, urn:xmlns:25hoursaday-com:inventory-tracking"/> * </pre> * * @param namespaces an array containing a prefix + namespace pair */ public void setNamespaces(String[] namespaces) { this.namespaces = namespaces; } }