Clover coverage report - Ant Coverage
Coverage timestamp: Tue Apr 8 2003 20:43:55 EST
file stats: LOC: 544   Methods: 29
NCLOC: 284   Classes: 3
 
 Source file Conditionals Statements Methods TOTAL
XMLValidateTask.java 45% 64.9% 75.9% 62.3%
 1   
 /*
 2   
  * The Apache Software License, Version 1.1
 3   
  *
 4   
  * Copyright (c) 2000-2003 The Apache Software Foundation.  All rights
 5   
  * reserved.
 6   
  *
 7   
  * Redistribution and use in source and binary forms, with or without
 8   
  * modification, are permitted provided that the following conditions
 9   
  * are met:
 10   
  *
 11   
  * 1. Redistributions of source code must retain the above copyright
 12   
  *    notice, this list of conditions and the following disclaimer.
 13   
  *
 14   
  * 2. Redistributions in binary form must reproduce the above copyright
 15   
  *    notice, this list of conditions and the following disclaimer in
 16   
  *    the documentation and/or other materials provided with the
 17   
  *    distribution.
 18   
  *
 19   
  * 3. The end-user documentation included with the redistribution, if
 20   
  *    any, must include the following acknowlegement:
 21   
  *       "This product includes software developed by the
 22   
  *        Apache Software Foundation (http://www.apache.org/)."
 23   
  *    Alternately, this acknowlegement may appear in the software itself,
 24   
  *    if and wherever such third-party acknowlegements normally appear.
 25   
  *
 26   
  * 4. The names "Ant" and "Apache Software
 27   
  *    Foundation" must not be used to endorse or promote products derived
 28   
  *    from this software without prior written permission. For written
 29   
  *    permission, please contact apache@apache.org.
 30   
  *
 31   
  * 5. Products derived from this software may not be called "Apache"
 32   
  *    nor may "Apache" appear in their names without prior written
 33   
  *    permission of the Apache Group.
 34   
  *
 35   
  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 36   
  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 37   
  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 38   
  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 39   
  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 40   
  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 41   
  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 42   
  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 43   
  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 44   
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 45   
  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 46   
  * SUCH DAMAGE.
 47   
  * ====================================================================
 48   
  *
 49   
  * This software consists of voluntary contributions made by many
 50   
  * individuals on behalf of the Apache Software Foundation.  For more
 51   
  * information on the Apache Software Foundation, please see
 52   
  * <http://www.apache.org/>.
 53   
  */
 54   
 package org.apache.tools.ant.taskdefs.optional;
 55   
 
 56   
 import java.io.File;
 57   
 import java.io.FileInputStream;
 58   
 import java.io.IOException;
 59   
 import java.net.MalformedURLException;
 60   
 import java.net.URL;
 61   
 import java.util.Vector;
 62   
 
 63   
 import org.apache.tools.ant.AntClassLoader;
 64   
 import org.apache.tools.ant.BuildException;
 65   
 import org.apache.tools.ant.DirectoryScanner;
 66   
 import org.apache.tools.ant.Project;
 67   
 import org.apache.tools.ant.Task;
 68   
 import org.apache.tools.ant.types.DTDLocation;
 69   
 import org.apache.tools.ant.types.FileSet;
 70   
 import org.apache.tools.ant.types.Path;
 71   
 import org.apache.tools.ant.types.Reference;
 72   
 import org.apache.tools.ant.types.XMLCatalog;
 73   
 import org.apache.tools.ant.util.FileUtils;
 74   
 import org.apache.tools.ant.util.JAXPUtils;
 75   
 import org.xml.sax.EntityResolver;
 76   
 import org.xml.sax.ErrorHandler;
 77   
 import org.xml.sax.InputSource;
 78   
 import org.xml.sax.Parser;
 79   
 import org.xml.sax.SAXException;
 80   
 import org.xml.sax.SAXNotRecognizedException;
 81   
 import org.xml.sax.SAXNotSupportedException;
 82   
 import org.xml.sax.SAXParseException;
 83   
 import org.xml.sax.XMLReader;
 84   
 import org.xml.sax.helpers.ParserAdapter;
 85   
 
 86   
 /**
 87   
  * Checks XML files are valid (or only well formed). The
 88   
  * task uses the SAX2 parser implementation provided by JAXP by default
 89   
  * (probably the one that is used by Ant itself), but one can specify any
 90   
  * SAX1/2 parser if needed
 91   
  * @author Raphael Pierquin <a href="mailto:raphael.pierquin@agisphere.com">raphael.pierquin@agisphere.com</a>
 92   
  * @author Nick Pellow <a href="mailto:nick@svana.org">nick@svana.org</a>
 93   
  */
 94   
 public class XMLValidateTask extends Task {
 95   
 
 96   
     /**
 97   
      * helper for path -> URI and URI -> path conversions.
 98   
      */
 99   
     private static FileUtils fu = FileUtils.newFileUtils();
 100   
 
 101   
     protected static String INIT_FAILED_MSG =
 102   
         "Could not start xml validation: ";
 103   
 
 104   
     // ant task properties
 105   
     // defaults
 106   
     protected boolean failOnError = true;
 107   
     protected boolean warn = true;
 108   
     protected boolean lenient = false;
 109   
     protected String  readerClassName = null;
 110   
 
 111   
     protected File file = null; // file to be validated
 112   
     protected Vector filesets = new Vector(); // sets of file to be validated
 113   
     protected Path classpath;
 114   
 
 115   
 
 116   
     /**
 117   
      * the parser is viewed as a SAX2 XMLReader. If a SAX1 parser is specified,
 118   
      * it's wrapped in an adapter that make it behave as a XMLReader.
 119   
      * a more 'standard' way of doing this would be to use the JAXP1.1 SAXParser
 120   
      * interface.
 121   
      */
 122   
     protected XMLReader xmlReader = null; // XMLReader used to validation process
 123   
     protected ValidatorErrorHandler errorHandler
 124   
         = new ValidatorErrorHandler(); // to report sax parsing errors
 125   
 
 126   
     /** The vector to store all attributes (features) to be set on the parser. **/
 127   
     private Vector attributeList = new Vector();
 128   
 
 129   
 
 130   
     private XMLCatalog xmlCatalog = new XMLCatalog();
 131   
 
 132   
     /**
 133   
      * Specify how parser error are to be handled.
 134   
      * Optional, default is <code>true</code>.
 135   
      * <p>
 136   
      * If set to <code>true</code> (default), throw a buildException if the
 137   
      * parser yields an error.
 138   
      */
 139  0
     public void setFailOnError(boolean fail) {
 140   
 
 141  0
         failOnError = fail;
 142   
     }
 143   
 
 144   
     /**
 145   
      * Specify how parser error are to be handled.
 146   
      * <p>
 147   
      * If set to <code>true</true> (default), log a warn message for each SAX warn event.
 148   
      */
 149  12
     public void setWarn(boolean bool) {
 150   
 
 151  12
         warn = bool;
 152   
     }
 153   
 
 154   
     /**
 155   
      * Specify whether the parser should be validating. Default is <code>true</code>.
 156   
      * <p>
 157   
      * If set to false, the validation will fail only if the parsed document is not well formed XML.
 158   
      * <p>
 159   
      * this option is ignored if the specified class with {@link #setClassName(String)} is not a SAX2
 160   
      * XMLReader.
 161   
      */
 162  1
     public void setLenient(boolean bool) {
 163   
 
 164  1
         lenient = bool;
 165   
     }
 166   
 
 167   
     /**
 168   
      * Specify the class name of the SAX parser to be used. (optional)
 169   
      * @param className should be an implementation of SAX2 <code>org.xml.sax.XMLReader</code>
 170   
      * or SAX2 <code>org.xml.sax.Parser</code>.
 171   
      * <p> if className is an implementation of <code>org.xml.sax.Parser</code>, {@link #setLenient(boolean)},
 172   
      * will be ignored.
 173   
      * <p> if not set, the default will be used.
 174   
      * @see org.xml.sax.XMLReader
 175   
      * @see org.xml.sax.Parser
 176   
      */
 177  0
     public void setClassName(String className) {
 178   
 
 179  0
         readerClassName = className;
 180   
     }
 181   
 
 182   
 
 183   
     /**
 184   
      * Specify the classpath to be searched to load the parser (optional)
 185   
      */
 186  0
     public void setClasspath(Path classpath) {
 187   
 
 188  0
         if (this.classpath == null) {
 189  0
             this.classpath = classpath;
 190   
         } else {
 191  0
             this.classpath.append(classpath);
 192   
         }
 193   
     }
 194   
 
 195   
     /**
 196   
      * @see #setClasspath
 197   
      */
 198  0
     public Path createClasspath() {
 199  0
         if (this.classpath == null) {
 200  0
             this.classpath = new Path(getProject());
 201   
         }
 202  0
         return this.classpath.createPath();
 203   
     }
 204   
 
 205   
     /**
 206   
      * Where to find the parser class; optional.
 207   
      * @see #setClasspath
 208   
      */
 209  0
     public void setClasspathRef(Reference r) {
 210  0
         createClasspath().setRefid(r);
 211   
     }
 212   
 
 213   
     /**
 214   
      * specify the file to be checked; optional.
 215   
      */
 216  2
     public void setFile(File file) {
 217  2
         this.file = file;
 218   
     }
 219   
 
 220   
     /**
 221   
      * add an XMLCatalog as a nested element; optional.
 222   
      */
 223  6
     public void addConfiguredXMLCatalog(XMLCatalog catalog) {
 224  6
         xmlCatalog.addConfiguredXMLCatalog(catalog);
 225   
     }
 226   
 
 227   
     /**
 228   
      * specify a set of file to be checked
 229   
      */
 230  10
     public void addFileset(FileSet set) {
 231  10
         filesets.addElement(set);
 232   
     }
 233   
 
 234   
     /**
 235   
      * Add an attribute nested element. This is used for setting arbitrary
 236   
      * features of the SAX parser.
 237   
      * Valid attributes
 238   
      * <a href=http://www.saxproject.org/apidoc/org/xml/sax/package-summary.html#package_description">include</a>
 239   
      * @since ant1.6
 240   
      */
 241  4
     public Attribute createAttribute() {
 242  4
         final Attribute feature = new Attribute();
 243  4
         attributeList.addElement(feature);
 244  4
         return feature;
 245   
     }
 246   
 
 247  12
     public void init() throws BuildException {
 248  12
         super.init();
 249  12
         xmlCatalog.setProject(getProject());
 250   
     }
 251   
 
 252   
     /**
 253   
      * Create a DTD location record; optional.
 254   
      * This stores the location of a DTD. The DTD is identified
 255   
      * by its public Id.
 256   
      */
 257  2
     public DTDLocation createDTD() {
 258  2
         DTDLocation dtdLocation = new DTDLocation();
 259  2
         xmlCatalog.addDTD(dtdLocation);
 260  2
         return dtdLocation;
 261   
     }
 262   
 
 263  12
     protected EntityResolver getEntityResolver() {
 264  12
         return xmlCatalog;
 265   
     }
 266   
 
 267  12
     public void execute() throws BuildException {
 268   
 
 269  12
         int fileProcessed = 0;
 270  12
         if (file == null && (filesets.size() == 0)) {
 271  0
             throw new BuildException("Specify at least one source - a file or a fileset.");
 272   
         }
 273   
 
 274  12
         initValidator();
 275   
 
 276  12
         if (file != null) {
 277  2
             if (file.exists() && file.canRead() && file.isFile())  {
 278  2
                 doValidate(file);
 279  1
                 fileProcessed++;
 280   
             } else {
 281  0
                 String errorMsg = "File " + file + " cannot be read";
 282  0
                 if (failOnError) {
 283  0
                     throw new BuildException(errorMsg);
 284   
                 } else {
 285  0
                     log(errorMsg, Project.MSG_ERR);
 286   
                 }
 287   
             }
 288   
         }
 289   
 
 290  11
         for (int i = 0; i < filesets.size(); i++) {
 291   
 
 292  10
             FileSet fs = (FileSet) filesets.elementAt(i);
 293  10
             DirectoryScanner ds = fs.getDirectoryScanner(getProject());
 294  10
             String[] files = ds.getIncludedFiles();
 295   
 
 296  10
             for (int j = 0; j < files.length ; j++)  {
 297  10
                 File srcFile = new File(fs.getDir(getProject()), files[j]);
 298  10
                 doValidate(srcFile);
 299  9
                 fileProcessed++;
 300   
             }
 301   
         }
 302  10
         log(fileProcessed + " file(s) have been successfully validated.");
 303   
     }
 304   
 
 305   
     /**
 306   
      * init the parser :
 307   
      * load the parser class, and set features if necessary
 308   
      */
 309  12
     private void initValidator() {
 310   
 
 311  12
         Object reader = null;
 312  12
         if (readerClassName == null) {
 313  12
             try {
 314  12
                 reader = JAXPUtils.getXMLReader();
 315   
             } catch (BuildException exc) {
 316  0
                 reader = JAXPUtils.getParser();
 317   
             }
 318   
         } else {
 319   
 
 320  0
             Class readerClass = null;
 321  0
             try {
 322   
                 // load the parser class
 323  0
                 if (classpath != null) {
 324  0
                     AntClassLoader loader
 325   
                         = getProject().createClassLoader(classpath);
 326  0
                     readerClass = loader.loadClass(readerClassName);
 327  0
                     AntClassLoader.initializeClass(readerClass);
 328   
                 } else {
 329  0
                     readerClass = Class.forName(readerClassName);
 330   
                 }
 331   
 
 332  0
                 reader = readerClass.newInstance();
 333   
             } catch (ClassNotFoundException e) {
 334  0
                 throw new BuildException(INIT_FAILED_MSG + readerClassName, e);
 335   
             } catch (InstantiationException e) {
 336  0
                 throw new BuildException(INIT_FAILED_MSG + readerClassName, e);
 337   
             } catch (IllegalAccessException e) {
 338  0
                 throw new BuildException(INIT_FAILED_MSG + readerClassName, e);
 339   
             }
 340   
         }
 341   
 
 342   
         // then check it implements XMLReader
 343  12
         if (reader instanceof XMLReader) {
 344  12
             xmlReader = (XMLReader) reader;
 345  12
             log("Using SAX2 reader " + reader.getClass().getName(),
 346   
                 Project.MSG_VERBOSE);
 347   
         } else {
 348   
 
 349   
             // see if it is a SAX1 Parser
 350  0
             if (reader instanceof Parser) {
 351  0
                 xmlReader = new ParserAdapter((Parser) reader);
 352  0
                 log("Using SAX1 parser " + reader.getClass().getName(),
 353   
                     Project.MSG_VERBOSE);
 354   
             }  else {
 355  0
                 throw new BuildException(INIT_FAILED_MSG
 356   
                                          + reader.getClass().getName()
 357   
                                          + " implements nor SAX1 Parser nor SAX2 XMLReader.");
 358   
             }
 359   
         }
 360   
 
 361  12
         xmlReader.setEntityResolver(getEntityResolver());
 362  12
         xmlReader.setErrorHandler(errorHandler);
 363   
 
 364  12
         if (!(xmlReader instanceof ParserAdapter)) {
 365   
             // turn validation on
 366  12
             if (!lenient) {
 367  12
                 setFeature("http://xml.org/sax/features/validation", true);
 368   
             }
 369   
             // set the feature from the attribute list
 370  12
             for (int i = 0; i < attributeList.size(); i++) {
 371  4
                 Attribute feature = (Attribute) attributeList.elementAt(i);
 372  4
                 setFeature(feature.getName(), feature.getValue());
 373   
 
 374   
             }
 375   
         }
 376   
     }
 377   
 
 378   
     /**
 379   
      * Set a feature on the parser.
 380   
      * @param feature the name of the feature to set
 381   
      * @param value the value of the feature
 382   
      * @param warn whether to war if the parser does not support the feature
 383   
 
 384   
      */
 385  16
     private void setFeature(String feature, boolean value)
 386   
         throws BuildException {
 387   
 
 388  16
         try {
 389  16
             xmlReader.setFeature(feature, value);
 390   
         } catch (SAXNotRecognizedException e) {
 391  0
             throw new BuildException("Parser " + xmlReader.getClass().getName()
 392   
                                      + " doesn't recognize feature "
 393   
                                      + feature, e, getLocation());
 394   
         } catch (SAXNotSupportedException  e) {
 395  0
             throw new BuildException("Parser " + xmlReader.getClass().getName()
 396   
                                      + " doesn't support feature "
 397   
                                      + feature, e, getLocation());
 398   
         }
 399   
     }
 400   
 
 401   
     /**
 402   
      * parse the file
 403   
      */
 404  12
     private void doValidate(File afile) {
 405  12
         try {
 406  12
             log("Validating " + afile.getName() + "... ", Project.MSG_VERBOSE);
 407  12
             errorHandler.init(afile);
 408  12
             InputSource is = new InputSource(new FileInputStream(afile));
 409  12
             String uri = fu.toURI(afile.getAbsolutePath());
 410  12
             is.setSystemId(uri);
 411  12
             xmlReader.parse(is);
 412   
         } catch (SAXException ex) {
 413  0
             if (failOnError) {
 414  0
                 throw new BuildException("Could not validate document "
 415   
                     + afile);
 416   
             }
 417   
         } catch (IOException ex) {
 418  1
             throw new BuildException("Could not validate document " + afile,
 419   
                 ex);
 420   
         }
 421   
 
 422  11
         if (errorHandler.getFailure()) {
 423  1
             if (failOnError) {
 424  1
                 throw new BuildException(afile + " is not a valid XML document.");
 425   
             } else {
 426  0
                 log(afile + " is not a valid XML document", Project.MSG_ERR);
 427   
             }
 428   
         }
 429   
     }
 430   
 
 431   
     /**
 432   
      * ValidatorErrorHandler role :
 433   
      * <ul>
 434   
      * <li> log SAX parse exceptions,
 435   
      * <li> remember if an error occured
 436   
      * </ul>
 437   
      */
 438   
     protected class ValidatorErrorHandler implements ErrorHandler {
 439   
 
 440   
         protected File currentFile = null;
 441   
         protected String lastErrorMessage = null;
 442   
         protected boolean failed = false;
 443   
 
 444  12
         public void init(File file) {
 445  12
             currentFile = file;
 446  12
             failed = false;
 447   
         }
 448   
 
 449   
         // did an error happen during last parsing ?
 450  11
         public boolean getFailure() {
 451   
 
 452  11
             return failed;
 453   
         }
 454   
 
 455  0
         public void fatalError(SAXParseException exception) {
 456  0
             failed = true;
 457  0
             doLog(exception, Project.MSG_ERR);
 458   
         }
 459   
 
 460  1
         public void error(SAXParseException exception) {
 461  1
             failed = true;
 462  1
             doLog(exception, Project.MSG_ERR);
 463   
         }
 464   
 
 465  0
         public void warning(SAXParseException exception) {
 466   
             // depending on implementation, XMLReader can yield hips of warning,
 467   
             // only output then if user explicitely asked for it
 468  0
             if (warn) {
 469  0
                 doLog(exception, Project.MSG_WARN);
 470   
             }
 471   
         }
 472   
 
 473  1
         private void doLog(SAXParseException e, int logLevel) {
 474   
 
 475  1
             log(getMessage(e), logLevel);
 476   
         }
 477   
 
 478  1
         private String getMessage(SAXParseException e) {
 479  1
             String sysID = e.getSystemId();
 480  1
             if (sysID != null) {
 481  1
                 try {
 482  1
                     int line = e.getLineNumber();
 483  1
                     int col = e.getColumnNumber();
 484  1
                     return new URL(sysID).getFile() +
 485   
                         (line == -1 ? "" : (":" + line +
 486   
                                             (col == -1 ? "" : (":" + col)))) +
 487   
                         ": " + e.getMessage();
 488   
                 } catch (MalformedURLException mfue) {
 489   
                 }
 490   
             }
 491  0
             return e.getMessage();
 492   
         }
 493   
     }
 494   
 
 495   
     /**
 496   
      * The class to create to set a feature of the parser.
 497   
      * @since ant1.6
 498   
      * @author <a href="mailto:nick@svana.org">Nick Pellow</a>
 499   
      */
 500   
     public class Attribute {
 501   
         /** The name of the attribute to set.
 502   
          *
 503   
          * Valid attributes <a href=http://www.saxproject.org/apidoc/org/xml/sax/package-summary.html#package_description">include.</a>
 504   
          */
 505   
         private String attributeName = null;
 506   
 
 507   
         /**
 508   
          * The value of the feature.
 509   
          **/
 510   
         private boolean attributeValue;
 511   
 
 512   
         /**
 513   
          * Set the feature name.
 514   
          * @param name the name to set
 515   
          */
 516  4
         public void setName(String name) {
 517  4
             attributeName = name;
 518   
         }
 519   
         /**
 520   
          * Set the feature value to true or false.
 521   
          * @param value
 522   
          */
 523  4
         public void setValue(boolean value) {
 524  4
             attributeValue = value;
 525   
         }
 526   
 
 527   
         /**
 528   
          * Gets the attribute name.
 529   
          * @return the feature name
 530   
          */
 531  4
         public String getName() {
 532  4
             return attributeName;
 533   
         }
 534   
 
 535   
         /**
 536   
          * Gets the attribute value.
 537   
          * @return the featuree value
 538   
          */
 539  4
         public boolean getValue() {
 540  4
             return attributeValue;
 541   
         }
 542   
     }
 543   
 }
 544