////////////////////////////////////////////////////////////////////////////////
// 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 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.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import javax.xml.xpath.XPathFactoryConfigurationException;
import net.sf.saxon.om.NamespaceConstant;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.xpath.XPathFactoryImpl;
import org.xml.sax.InputSource;
import com.puppycrawl.tools.checkstyle.api.Check;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.XmlTokenTypes;
/**
* <p>
* Checks for the number of nodes matched by a XPath 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 file with less
* than 1500 child for a node:
* </p>
* <pre>
* <module name="XPathCheck">
* <property name="expression" value="//*[count(*)>=100]"/>
* <property name="min" value="0"/>
* <property name="max" value="1500"/>
* </module>
* </pre>
* @author Yoann Ciabaud<y.ciabaud@gmail.com>
*/
public class XPathCheck 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;
/** The starting context, a node for example */
private String item = "/";
private XPathExpression itemExpression;
/** String value of XPath expression*/
private String expression;
/** XPath expression*/
private XPathExpression xPathExpression;
/** */
private String[] namespaces;
/** {@inheritDoc} */
@Override
public int[] getDefaultTokens() {
return new int[]{XmlTokenTypes.DOCUMENT};
}
/** {@inheritDoc} */
@Override
public void init() {
super.init();
//création du XPath
XPathFactory fabrique = null;
try {
System.setProperty("javax.xml.xpath.XPathFactory:"+NamespaceConstant.OBJECT_MODEL_SAXON, "net.sf.saxon.xpath.XPathFactoryImpl");
fabrique = XPathFactory.newInstance(NamespaceConstant.OBJECT_MODEL_SAXON);
((XPathFactoryImpl)fabrique).getConfiguration().setLineNumbering(true);
} catch (XPathFactoryConfigurationException ex) {
log(0, "XPath engine error" + ex.getMessage());
ex.printStackTrace();
return;
}
XPath xpath = fabrique.newXPath();
if( namespaces != null )
{
final Map<String, String> ns = new HashMap<String, String>();
for( int index = 0; index < namespaces.length; index+=2)
{
ns.put(namespaces[index], namespaces[index+1]);
}
xpath.setNamespaceContext(new NamespaceContext() {
private Map<String, String> namespaceMap = ns;
@Override
public Iterator getPrefixes(String arg0) {
return null; //not used
}
@Override
public String getPrefix(String arg0) {
return null; //not used
}
@Override
public String getNamespaceURI(String prefix) {
return namespaceMap.get(prefix);
}
});
}
try {
itemExpression = xpath.compile(item);
xPathExpression = xpath.compile(expression);
} catch (XPathExpressionException ex) {
log(0, "Invalid XPath expression: " + expression);
ex.printStackTrace();
}
}
/** {@inheritDoc} */
@Override
public void visitToken(DetailAST aAST) {
if(getFileContents() == null){
log(aAST.getLineNo(),
"no XML document available in this context");
return;
}
if(xPathExpression == null){
log(aAST.getLineNo(),
"no XPath expression available in this context");
return;
}
List contexts = null;
try {
final String fullText = getFileContents().getText().getFullText().toString();
InputSource document = new InputSource();
document.setCharacterStream(new StringReader(fullText));
contexts = (List) itemExpression.evaluate(document, XPathConstants.NODESET);
int contextsMatches = contexts != null ? contexts.size() : 0;
if( contextsMatches > 0 )
{
for ( int contentsIndex = 0 ; contentsIndex < contextsMatches; contentsIndex++)
{
List resultat = (List) xPathExpression.evaluate(contexts.get(contentsIndex), XPathConstants.NODESET);
int nbMatches = resultat != null ? resultat.size() : 0;
if( nbMatches < min ) {
log(0, "xpath.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,
"xpath.invalidPath",
aAST.getText(),
expression, nbMatches, min, max);
}
}
}
}
} catch (XPathExpressionException ex) {
log(aAST.getLineNo(),
"XPath evaluation failed: " + ex.getMessage());
ex.printStackTrace();
return;
}
}
/**
* 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 XPath 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;
}
public void setItem(String item) {
this.item = item;
}
}