Clover coverage report - Ant Coverage
Coverage timestamp: Tue Apr 8 2003 20:43:55 EST
file stats: LOC: 1,138   Methods: 30
NCLOC: 571   Classes: 4
 
 Source file Conditionals Statements Methods TOTAL
XMLCatalog.java 52.8% 65.2% 80% 63.1%
 1   
 /*
 2   
  * The Apache Software License, Version 1.1
 3   
  *
 4   
  * Copyright (c) 2002-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   
 
 55   
 package org.apache.tools.ant.types;
 56   
 
 57   
 import java.lang.reflect.Method;
 58   
 
 59   
 import java.io.File;
 60   
 import java.io.FileInputStream;
 61   
 import java.io.FileNotFoundException;
 62   
 import java.io.IOException;
 63   
 import java.io.InputStream;
 64   
 import java.net.MalformedURLException;
 65   
 import java.net.URL;
 66   
 import java.util.Enumeration;
 67   
 import java.util.Stack;
 68   
 import java.util.Vector;
 69   
 import javax.xml.parsers.ParserConfigurationException;
 70   
 import javax.xml.parsers.SAXParserFactory;
 71   
 import javax.xml.transform.Source;
 72   
 import javax.xml.transform.TransformerException;
 73   
 import javax.xml.transform.URIResolver;
 74   
 import javax.xml.transform.sax.SAXSource;
 75   
 import org.apache.tools.ant.AntClassLoader;
 76   
 import org.apache.tools.ant.BuildException;
 77   
 import org.apache.tools.ant.DirectoryScanner;
 78   
 import org.apache.tools.ant.Project;
 79   
 import org.apache.tools.ant.util.FileUtils;
 80   
 import org.apache.tools.ant.util.JAXPUtils;
 81   
 import org.xml.sax.EntityResolver;
 82   
 import org.xml.sax.InputSource;
 83   
 import org.xml.sax.SAXException;
 84   
 import org.xml.sax.XMLReader;
 85   
 
 86   
 
 87   
 
 88   
 /**
 89   
  * <p>This data type provides a catalog of resource locations (such as
 90   
  * DTDs and XML entities), based on the <a
 91   
  * href="http://oasis-open.org/committees/entity/spec-2001-08-06.html">
 92   
  * OASIS "Open Catalog" standard</a>.  The catalog entries are used
 93   
  * both for Entity resolution and URI resolution, in accordance with
 94   
  * the {@link org.xml.sax.EntityResolver EntityResolver} and {@link
 95   
  * javax.xml.transform.URIResolver URIResolver} interfaces as defined
 96   
  * in the <a href="http://java.sun.com/xml/jaxp">Java API for XML
 97   
  * Processing Specification</a>.</p>
 98   
  *
 99   
  * <p>Resource locations can be specified either in-line or in
 100   
  * external catalog file(s), or both.  In order to use an external
 101   
  * catalog file, the xml-commons resolver library ("resolver.jar")
 102   
  * must be in your classpath.  External catalog files may be either <a
 103   
  * href="http://oasis-open.org/committees/entity/background/9401.html">
 104   
  * plain text format</a> or <a
 105   
  * href="http://www.oasis-open.org/committees/entity/spec-2001-08-06.html">
 106   
  * XML format</a>.  If the xml-commons resolver library is not found
 107   
  * in the classpath, external catalog files, specified in
 108   
  * <code>&lt;catalogpath&gt;</code> paths, will be ignored and a warning will
 109   
  * be logged.  In this case, however, processing of inline entries will proceed
 110   
  * normally.</p>
 111   
  *
 112   
  * <p>Currently, only <code>&lt;dtd&gt;</code> and
 113   
  * <code>&lt;entity&gt;</code> elements may be specified inline; these
 114   
  * correspond to OASIS catalog entry types <code>PUBLIC</code> and
 115   
  * <code>URI</code> respectively.</p>
 116   
  *
 117   
  * <p>The following is a usage example:</p>
 118   
  *
 119   
  * <code>
 120   
  * &lt;xmlcatalog&gt;<br>
 121   
  * &nbsp;&nbsp;&lt;dtd publicId="" location="/path/to/file.jar" /&gt;<br>
 122   
  * &nbsp;&nbsp;&lt;dtd publicId="" location="/path/to/file2.jar" /&gt;<br>
 123   
  * &nbsp;&nbsp;&lt;entity publicId="" location="/path/to/file3.jar" /&gt;<br>
 124   
  * &nbsp;&nbsp;&lt;entity publicId="" location="/path/to/file4.jar" /&gt;<br>
 125   
  * &nbsp;&nbsp;&lt;catalogpath&gt;<br>
 126   
  * &nbsp;&nbsp;&nbsp;&nbsp;&lt;pathelement location="/etc/sgml/catalog"/&gt;<br>
 127   
  * &nbsp;&nbsp;&lt;/catalogpath&gt;<br>
 128   
  * &nbsp;&nbsp;&lt;catalogfiles dir="/opt/catalogs/" includes="**\catalog.xml" /&gt;<br>
 129   
  * &lt;/xmlcatalog&gt;<br>
 130   
  * </code>
 131   
  * <p>
 132   
  * Tasks wishing to use <code>&lt;xmlcatalog&gt;</code> must provide a method called
 133   
  * <code>createXMLCatalog</code> which returns an instance of
 134   
  * <code>XMLCatalog</code>. Nested DTD and entity definitions are handled by
 135   
  * the XMLCatalog object and must be labeled <code>dtd</code> and
 136   
  * <code>entity</code> respectively.</p>
 137   
  *
 138   
  * <p>The following is a description of the resolution algorithm:
 139   
  * entities/URIs/dtds are looked up in each of the following contexts,
 140   
  * stopping when a valid and readable resource is found:
 141   
  * <ol>
 142   
  * <li>In the local filesystem</li>
 143   
  * <li>In the classpath</li>
 144   
  * <li>Using the Apache xml-commons resolver (if it is available)</li>
 145   
  * <li>In URL-space</li>
 146   
  * </ol>
 147   
  * </p>
 148   
  *
 149   
  * <p>See {@link
 150   
  * org.apache.tools.ant.taskdefs.optional.XMLValidateTask
 151   
  * XMLValidateTask} for an example of a task that has integrated
 152   
  * support for XMLCatalogs.</p>
 153   
  *
 154   
  * <p>Possible future extension could provide for additional OASIS
 155   
  * entry types to be specified inline.</p>
 156   
  *
 157   
  * @author dIon Gillard
 158   
  * @author Erik Hatcher
 159   
  * @author <a href="mailto:cstrong@arielpartners.com">Craeg Strong</a>
 160   
  * @author Jeff Turner
 161   
  * @version $Id: XMLCatalog.java,v 1.24 2003/02/10 14:14:31 bodewig Exp $
 162   
  */
 163   
 public class XMLCatalog extends DataType
 164   
     implements Cloneable, EntityResolver, URIResolver {
 165   
 
 166   
     /** helper for some File.toURL connversions */
 167   
     private static FileUtils fileUtils = FileUtils.newFileUtils();
 168   
 
 169   
     //-- Fields ----------------------------------------------------------------
 170   
 
 171   
     /** Holds dtd/entity objects until needed. */
 172   
     private Vector elements = new Vector();
 173   
 
 174   
     /**
 175   
      * Classpath in which to attempt to resolve resources.
 176   
      */
 177   
     private Path classpath;
 178   
 
 179   
     /**
 180   
      * Path listing external catalog files to search when resolving entities
 181   
      */
 182   
     private Path catalogPath;
 183   
 
 184   
     /**
 185   
      * The name of the bridge to the Apache xml-commons resolver
 186   
      * class, used to determine whether resolver.jar is present in the
 187   
      * classpath.
 188   
      */
 189   
     public static final String APACHE_RESOLVER
 190   
         = "org.apache.tools.ant.types.resolver.ApacheCatalogResolver";
 191   
 
 192   
     //-- Methods ---------------------------------------------------------------
 193   
 
 194  52
     public XMLCatalog() {
 195  52
         setChecked( false );
 196   
     }
 197   
 
 198   
     /**
 199   
      * Returns the elements of the catalog - ResourceLocation objects.
 200   
      *
 201   
      * @return the elements of the catalog - ResourceLocation objects
 202   
      */
 203  86
     private Vector getElements() {
 204  86
         return elements;
 205   
     }
 206   
 
 207   
     /**
 208   
      * Returns the classpath in which to attempt to resolve resources.
 209   
      *
 210   
      * @return the classpath
 211   
      */
 212  11
     private Path getClasspath() {
 213  11
         return classpath;
 214   
     }
 215   
 
 216   
     /**
 217   
      * Set the list of ResourceLocation objects in the catalog.
 218   
      * Not allowed if this catalog is itself a reference to another catalog --
 219   
      * that is, a catalog cannot both refer to another <em>and</em> contain
 220   
      * elements or other attributes.
 221   
      *
 222   
      * @param aVector the new list of ResourceLocations
 223   
      * to use in the catalog.
 224   
      */
 225  8
     private void setElements(Vector aVector) {
 226  8
         if (isReference()) {
 227  0
             throw noChildrenAllowed();
 228   
         }
 229  8
         elements = aVector;
 230   
     }
 231   
 
 232   
     /**
 233   
      * Allows nested classpath elements. Not allowed if this catalog
 234   
      * is itself a reference to another catalog -- that is, a catalog
 235   
      * cannot both refer to another <em>and</em> contain elements or
 236   
      * other attributes.
 237   
      */
 238  11
     public Path createClasspath() {
 239  11
         if (isReference()) {
 240  0
             throw noChildrenAllowed();
 241   
         }
 242  11
         if (this.classpath == null) {
 243  10
             this.classpath = new Path(getProject());
 244   
         }
 245  11
         setChecked( false );
 246  11
         return this.classpath.createPath();
 247   
     }
 248   
 
 249   
     /**
 250   
      * Allows simple classpath string.  Not allowed if this catalog is
 251   
      * itself a reference to another catalog -- that is, a catalog
 252   
      * cannot both refer to another <em>and</em> contain elements or
 253   
      * other attributes.
 254   
      */
 255  7
     public void setClasspath(Path classpath) {
 256  7
         if (isReference()) {
 257  0
             throw tooManyAttributes();
 258   
         }
 259  7
         if (this.classpath == null) {
 260  7
             this.classpath = classpath;
 261   
         } else {
 262  0
             this.classpath.append(classpath);
 263   
         }
 264  7
         setChecked( false );
 265   
     }
 266   
 
 267   
     /**
 268   
      * Allows classpath reference.  Not allowed if this catalog is
 269   
      * itself a reference to another catalog -- that is, a catalog
 270   
      * cannot both refer to another <em>and</em> contain elements or
 271   
      * other attributes.
 272   
      */
 273  0
     public void setClasspathRef(Reference r) {
 274  0
         if (isReference()) {
 275  0
             throw tooManyAttributes();
 276   
         }
 277  0
         createClasspath().setRefid(r);
 278  0
         setChecked( false );
 279   
     }
 280   
 
 281   
     /** Creates a nested <code>&lt;catalogpath&gt;</code> element.
 282   
      * Not allowed if this catalog is itself a reference to another
 283   
      * catalog -- that is, a catalog cannot both refer to another
 284   
      * <em>and</em> contain elements or other attributes.
 285   
      *
 286   
      * @exception BuildException
 287   
      * if this is a reference and no nested elements are allowed.
 288   
      */
 289  15
     public Path createCatalogPath() {
 290  15
         if (isReference()) {
 291  0
             throw noChildrenAllowed();
 292   
         }
 293  15
         if (this.catalogPath == null) {
 294  15
             this.catalogPath = new Path(getProject());
 295   
         }
 296  15
         setChecked( false );
 297  15
         return this.catalogPath.createPath();
 298   
     }
 299   
 
 300   
     /**
 301   
      * Allows catalogpath reference.  Not allowed if this catalog is
 302   
      * itself a reference to another catalog -- that is, a catalog
 303   
      * cannot both refer to another <em>and</em> contain elements or
 304   
      * other attributes.
 305   
      */
 306  0
     public void setCatalogPathRef(Reference r) {
 307  0
         if (isReference()) {
 308  0
             throw tooManyAttributes();
 309   
         }
 310  0
         createCatalogPath().setRefid(r);
 311  0
         setChecked( false );
 312   
     }
 313   
 
 314   
 
 315   
     /**
 316   
      * Returns the catalog path in which to attempt to resolve DTDs.
 317   
      *
 318   
      * @return the catalog path
 319   
      */
 320  51
     public Path getCatalogPath() {
 321  51
         return this.catalogPath;
 322   
     }
 323   
 
 324   
 
 325   
     /**
 326   
      * Creates the nested <code>&lt;dtd&gt;</code> element.  Not
 327   
      * allowed if this catalog is itself a reference to another
 328   
      * catalog -- that is, a catalog cannot both refer to another
 329   
      * <em>and</em> contain elements or other attributes.
 330   
      *
 331   
      * @param dtd the information about the PUBLIC resource mapping to
 332   
      *            be added to the catalog
 333   
      * @exception BuildException if this is a reference and no nested
 334   
      *       elements are allowed.
 335   
      */
 336  27
     public void addDTD(ResourceLocation dtd) throws BuildException {
 337  27
         if (isReference()) {
 338  0
             throw noChildrenAllowed();
 339   
         }
 340   
 
 341  27
         getElements().addElement(dtd);
 342  27
         setChecked( false );
 343   
     }
 344   
 
 345   
     /**
 346   
      * Creates the nested <code>&lt;entity&gt;</code> element.    Not
 347   
      * allowed if this catalog is itself a reference to another
 348   
      * catalog -- that is, a catalog cannot both refer to another
 349   
      * <em>and</em> contain elements or other attributes.
 350   
      *
 351   
      * @param entity the information about the URI resource mapping to be
 352   
      *       added to the catalog.
 353   
      * @exception BuildException if this is a reference and no nested
 354   
      *       elements are allowed.
 355   
      */
 356  7
     public void addEntity(ResourceLocation entity) throws BuildException {
 357  7
         addDTD(entity);
 358   
     }
 359   
 
 360   
     /**
 361   
      * Loads a nested <code>&lt;xmlcatalog&gt;</code> into our
 362   
      * definition.  Not allowed if this catalog is itself a reference
 363   
      * to another catalog -- that is, a catalog cannot both refer to
 364   
      * another <em>and</em> contain elements or other attributes.
 365   
      *
 366   
      * @param catalog Nested XMLCatalog
 367   
      */
 368  12
     public void addConfiguredXMLCatalog(XMLCatalog catalog) {
 369  12
         if (isReference()) {
 370  1
             throw noChildrenAllowed();
 371   
         }
 372   
 
 373   
         // Add all nested elements to our catalog
 374  11
         Vector newElements = catalog.getElements();
 375  11
         Vector ourElements = getElements();
 376  11
         Enumeration enum = newElements.elements();
 377  11
         while (enum.hasMoreElements()) {
 378  11
             ourElements.addElement(enum.nextElement());
 379   
         }
 380   
 
 381   
         // Append the classpath of the nested catalog
 382  11
         Path nestedClasspath = catalog.getClasspath();
 383  11
         createClasspath().append(nestedClasspath);
 384   
 
 385   
         // Append the catalog path of the nested catalog
 386  11
         Path nestedCatalogPath = catalog.getCatalogPath();
 387  11
         createCatalogPath().append(nestedCatalogPath);
 388  11
         setChecked( false );
 389   
     }
 390   
 
 391   
     /**
 392   
      * Makes this instance in effect a reference to another XMLCatalog
 393   
      * instance.
 394   
      *
 395   
      * <p>You must not set another attribute or nest elements inside
 396   
      * this element if you make it a reference.  That is, a catalog
 397   
      * cannot both refer to another <em>and</em> contain elements or
 398   
      * attributes.</p>
 399   
      *
 400   
      * @param r the reference to which this catalog instance is associated
 401   
      * @exception BuildException if this instance already has been configured.
 402   
      */
 403  10
     public void setRefid(Reference r) throws BuildException {
 404  10
         if (!elements.isEmpty()) {
 405  1
             throw tooManyAttributes();
 406   
         }
 407   
         // change this to get the objects from the other reference
 408  9
         Object o = r.getReferencedObject(getProject());
 409   
         // we only support references to other XMLCatalogs
 410  8
         if (o instanceof XMLCatalog) {
 411   
             // set all elements from referenced catalog to this one
 412  8
             XMLCatalog catalog = (XMLCatalog) o;
 413  8
             setElements(catalog.getElements());
 414   
         } else {
 415  0
             String msg = r.getRefId() + " does not refer to an XMLCatalog";
 416  0
             throw new BuildException(msg);
 417   
         }
 418  8
         super.setRefid(r);
 419   
     }
 420   
 
 421   
     /**
 422   
      * Implements the EntityResolver.resolveEntity() interface method.
 423   
      *
 424   
      * @see org.xml.sax.EntityResolver#resolveEntity
 425   
      */
 426  21
     public InputSource resolveEntity(String publicId, String systemId)
 427   
         throws SAXException, IOException {
 428   
 
 429  21
         if (!isChecked()) {
 430   
             // make sure we don't have a circular reference here
 431  21
             Stack stk = new Stack();
 432  21
             stk.push(this);
 433  21
             dieOnCircularReference(stk, getProject());
 434   
         }
 435   
 
 436  19
         log("resolveEntity: '" + publicId + "': '" + systemId + "'",
 437   
             Project.MSG_DEBUG);
 438   
 
 439  19
         InputSource inputSource =
 440   
             getCatalogResolver().resolveEntity(publicId, systemId);
 441   
 
 442  19
         if (inputSource == null) {
 443  5
             log("No matching catalog entry found, parser will use: '" +
 444   
                 systemId + "'", Project.MSG_DEBUG);
 445   
         }
 446   
 
 447  19
         return inputSource;
 448   
     }
 449   
 
 450   
     /**
 451   
      * Implements the URIResolver.resolve() interface method.
 452   
      *
 453   
      * @see javax.xml.transform.URIResolver#resolve
 454   
      */
 455  10
     public Source resolve(String href, String base)
 456   
         throws TransformerException {
 457   
 
 458  10
         if (!isChecked()) {
 459   
             // make sure we don't have a circular reference here
 460  10
             Stack stk = new Stack();
 461  10
             stk.push(this);
 462  10
             dieOnCircularReference(stk, getProject());
 463   
         }
 464   
 
 465  10
         SAXSource source = null;
 466   
 
 467  10
         String uri = removeFragment(href);
 468   
 
 469  10
         log("resolve: '" + uri + "' with base: '" + base + "'", Project.MSG_DEBUG);
 470   
 
 471  10
         source = (SAXSource)getCatalogResolver().resolve(uri, base);
 472   
 
 473  10
         if (source == null) {
 474  0
             log("No matching catalog entry found, parser will use: '" +
 475   
                 href + "'", Project.MSG_DEBUG);
 476   
             //
 477   
             // Cannot return a null source, because we have to call
 478   
             // setEntityResolver (see setEntityResolver javadoc comment)
 479   
             //
 480  0
             source = new SAXSource();
 481  0
             URL baseURL = null;
 482  0
             try {
 483  0
                 if (base == null) {
 484  0
                     baseURL = fileUtils.getFileURL(getProject().getBaseDir());
 485   
                 }
 486   
                 else {
 487  0
                     baseURL = new URL(base);
 488   
                 }
 489  0
                 URL url = (uri.length() == 0 ? baseURL : new URL(baseURL, uri));
 490  0
                 source.setInputSource(new InputSource(url.toString()));
 491   
             }
 492   
             catch (MalformedURLException ex) {
 493   
                 // At this point we are probably in failure mode, but
 494   
                 // try to use the bare URI as a last gasp
 495  0
                 source.setInputSource(new InputSource(uri));
 496   
             }
 497   
         }
 498   
 
 499  10
         setEntityResolver(source);
 500  10
         return source;
 501   
     }
 502   
 
 503   
     /**
 504   
      * The instance of the CatalogResolver strategy to use.
 505   
      */
 506   
     private CatalogResolver catalogResolver = null;
 507   
 
 508   
     /**
 509   
      * Factory method for creating the appropriate CatalogResolver
 510   
      * strategy implementation.
 511   
      * <p> Until we query the classpath, we don't know whether the Apache
 512   
      * resolver (Norm Walsh's library from xml-commons) is available or not.
 513   
      * This method determines whether the library is available and creates the
 514   
      * appropriate implementation of CatalogResolver based on the answer.</p>
 515   
      * <p>This is an application of the Gang of Four Strategy Pattern
 516   
      * combined with Template Method.</p>
 517   
      */
 518  29
     private CatalogResolver getCatalogResolver() {
 519   
 
 520  29
         if (catalogResolver == null) {
 521   
 
 522  22
             AntClassLoader loader = null;
 523   
 
 524  22
             loader = getProject().createClassLoader(Path.systemClasspath);
 525   
 
 526  22
             try {
 527  22
                 Class clazz = loader.forceLoadSystemClass(APACHE_RESOLVER);
 528  22
                 Object obj  = clazz.newInstance();
 529   
                 //
 530   
                 // Success!  The xml-commons resolver library is
 531   
                 // available, so use it.
 532   
                 //
 533  22
                 catalogResolver = new ApacheResolver(clazz, obj);
 534   
             }
 535   
             catch (Throwable ex) {
 536   
                 //
 537   
                 // The xml-commons resolver library is not
 538   
                 // available, so we can't use it.
 539   
                 //
 540  0
                 catalogResolver = new InternalResolver();
 541  0
                 if (getCatalogPath() != null &&
 542   
                     getCatalogPath().list().length != 0) {
 543  0
                         log("Warning: catalogpath listing external catalogs"+
 544   
                                 " will be ignored",
 545   
                             Project.MSG_WARN);
 546   
                 }
 547   
             }
 548   
         }
 549  29
         return catalogResolver;
 550   
     }
 551   
 
 552   
     /**
 553   
      * <p>This is called from the URIResolver to set an EntityResolver
 554   
      * on the SAX parser to be used for new XML documents that are
 555   
      * encountered as a result of the document() function, xsl:import,
 556   
      * or xsl:include.  This is done because the XSLT processor calls
 557   
      * out to the SAXParserFactory itself to create a new SAXParser to
 558   
      * parse the new document.  The new parser does not automatically
 559   
      * inherit the EntityResolver of the original (although arguably
 560   
      * it should).  See below:</p>
 561   
      *
 562   
      * <tt>"If an application wants to set the ErrorHandler or
 563   
      * EntityResolver for an XMLReader used during a transformation,
 564   
      * it should use a URIResolver to return the SAXSource which
 565   
      * provides (with getXMLReader) a reference to the XMLReader"</tt>
 566   
      *
 567   
      * <p>...quoted from page 118 of the Java API for XML
 568   
      * Processing 1.1 specification</p>
 569   
      *
 570   
      */
 571  10
     private void setEntityResolver(SAXSource source) throws TransformerException {
 572   
 
 573  10
         XMLReader reader = source.getXMLReader();
 574  10
         if (reader == null) {
 575  10
             SAXParserFactory spFactory = SAXParserFactory.newInstance();
 576  10
             spFactory.setNamespaceAware(true);
 577  10
             try {
 578  10
                 reader = spFactory.newSAXParser().getXMLReader();
 579   
             }
 580   
             catch (ParserConfigurationException ex) {
 581  0
                 throw new TransformerException(ex);
 582   
             }
 583   
             catch (SAXException ex) {
 584  0
                 throw new TransformerException(ex);
 585   
             }
 586   
         }
 587  10
         reader.setEntityResolver(this);
 588  10
         source.setXMLReader(reader);
 589   
     }
 590   
 
 591   
     /**
 592   
      * Find a ResourceLocation instance for the given publicId.
 593   
      *
 594   
      * @param publicId the publicId of the Resource for which local information
 595   
      *        is required.
 596   
      * @return a ResourceLocation instance with information on the local location
 597   
      *         of the Resource or null if no such information is available.
 598   
      */
 599  29
     private ResourceLocation findMatchingEntry(String publicId) {
 600  29
         Enumeration enum = getElements().elements();
 601  29
         ResourceLocation element = null;
 602  29
         while (enum.hasMoreElements()) {
 603  24
             Object o = enum.nextElement();
 604  24
             if (o instanceof ResourceLocation) {
 605  24
                 element = (ResourceLocation)o;
 606  24
                 if (element.getPublicId().equals(publicId)) {
 607  18
                     return element;
 608   
                 }
 609   
             }
 610   
         }
 611  11
         return null;
 612   
     }
 613   
 
 614   
     /**
 615   
      * Utility method to remove trailing fragment from a URI.
 616   
      * For example,
 617   
      * <code>http://java.sun.com/index.html#chapter1</code>
 618   
      * would return <code>http://java.sun.com/index.html</code>.
 619   
      *
 620   
      * @param uri The URI to process.  It may or may not contain a
 621   
      *            fragment.
 622   
      * @return The URI sans fragment.
 623   
      */
 624  10
     private String removeFragment(String uri) {
 625  10
         String result = uri;
 626  10
         int hashPos = uri.indexOf("#");
 627  10
         if (hashPos >= 0) {
 628  0
             result = uri.substring(0, hashPos);
 629   
         }
 630  10
         return result;
 631   
     }
 632   
 
 633   
     /**
 634   
      * Utility method to lookup a ResourceLocation in the filesystem.
 635   
      *
 636   
      * @return An InputSource for reading the file, or <code>null</code>
 637   
      *     if the file does not exist or is not readable.
 638   
      */
 639  18
     private InputSource filesystemLookup(ResourceLocation matchingEntry) {
 640   
 
 641  18
         String uri = matchingEntry.getLocation();
 642  18
         URL baseURL = null;
 643   
 
 644   
         //
 645   
         // The ResourceLocation may specify a relative path for its
 646   
         // location attribute.  This is resolved using the appropriate
 647   
         // base.
 648   
         //
 649  18
         if (matchingEntry.getBase() != null) {
 650  3
             baseURL = matchingEntry.getBase();
 651   
         } else {
 652  15
             try {
 653  15
                 baseURL = fileUtils.getFileURL(getProject().getBaseDir());
 654   
             }
 655   
             catch (MalformedURLException ex) {
 656  0
                 throw new BuildException("Project basedir cannot be converted to a URL");
 657   
             }
 658   
         }
 659   
 
 660  18
         InputSource source = null;
 661  18
         URL url = null;
 662   
 
 663  18
         try {
 664  18
             url = new URL(baseURL, uri);
 665   
         }
 666   
         catch (MalformedURLException ex) {
 667   
             // ignore
 668   
         }
 669   
 
 670  18
         if (url != null) {
 671  18
             String fileName = url.getFile();
 672  18
             if (fileName != null) {
 673  18
                 log("fileName " + fileName, Project.MSG_DEBUG);
 674  18
                 File resFile = new File(fileName);
 675  18
                 if (resFile.exists() && resFile.canRead()) {
 676  12
                     try {
 677  12
                         source = new InputSource(new FileInputStream(resFile));
 678  12
                         String sysid = JAXPUtils.getSystemId(resFile);
 679  12
                         source.setSystemId(sysid);
 680  12
                         log("catalog entry matched a readable file: '" +
 681   
                             sysid + "'", Project.MSG_DEBUG);
 682   
                     } catch(FileNotFoundException ex) {
 683   
                         // ignore
 684   
                     } catch(IOException ex) {
 685   
                         // ignore
 686   
                     }
 687   
                 }
 688   
             }
 689   
         }
 690  18
         return source;
 691   
     }
 692   
 
 693   
     /**
 694   
      * Utility method to lookup a ResourceLocation in the classpath.
 695   
      *
 696   
      * @return An InputSource for reading the resource, or <code>null</code>
 697   
      *    if the resource does not exist in the classpath or is not readable.
 698   
      */
 699  6
     private InputSource classpathLookup(ResourceLocation matchingEntry) {
 700   
 
 701  6
         InputSource source = null;
 702   
 
 703  6
         AntClassLoader loader = null;
 704  6
         Path cp = classpath;
 705  6
         if (cp != null) {
 706  6
             cp = classpath.concatSystemClasspath("ignore");
 707   
         } else {
 708  0
             cp = (new Path(getProject())).concatSystemClasspath("last");
 709   
         }
 710  6
         loader = getProject().createClassLoader(cp);
 711   
 
 712   
         //
 713   
         // for classpath lookup we ignore the base directory
 714   
         //
 715  6
         InputStream is
 716   
             = loader.getResourceAsStream(matchingEntry.getLocation());
 717   
 
 718  6
         if (is != null) {
 719  6
             source = new InputSource(is);
 720  6
             URL entryURL = loader.getResource(matchingEntry.getLocation());
 721  6
             String sysid = entryURL.toExternalForm();
 722  6
             source.setSystemId(sysid);
 723  6
             log("catalog entry matched a resource in the classpath: '" +
 724   
                 sysid + "'", Project.MSG_DEBUG);
 725   
         }
 726   
 
 727  6
         return source;
 728   
     }
 729   
 
 730   
     /**
 731   
      * Utility method to lookup a ResourceLocation in URL-space.
 732   
      *
 733   
      * @return An InputSource for reading the resource, or <code>null</code>
 734   
      *    if the resource does not identify a valid URL or is not readable.
 735   
      */
 736  0
     private InputSource urlLookup(ResourceLocation matchingEntry) {
 737   
 
 738  0
         String uri = matchingEntry.getLocation();
 739  0
         URL baseURL = null;
 740   
 
 741   
         //
 742   
         // The ResourceLocation may specify a relative url for its
 743   
         // location attribute.  This is resolved using the appropriate
 744   
         // base.
 745   
         //
 746  0
         if (matchingEntry.getBase() != null) {
 747  0
             baseURL = matchingEntry.getBase();
 748   
         } else {
 749  0
             try {
 750  0
                 baseURL = fileUtils.getFileURL(getProject().getBaseDir());
 751   
             }
 752   
             catch (MalformedURLException ex) {
 753  0
                 throw new BuildException("Project basedir cannot be converted to a URL");
 754   
             }
 755   
         }
 756   
 
 757  0
         InputSource source = null;
 758  0
         URL url = null;
 759   
 
 760  0
         try {
 761  0
             url = new URL(baseURL, uri);
 762   
         }
 763   
         catch (MalformedURLException ex) {
 764   
             // ignore
 765   
         }
 766   
 
 767  0
         if (url != null) {
 768  0
             try {
 769  0
                 InputStream is = url.openStream();
 770  0
                 if (is != null) {
 771  0
                     source = new InputSource(is);
 772  0
                     String sysid = url.toExternalForm();
 773  0
                     source.setSystemId(sysid);
 774  0
                     log("catalog entry matched as a URL: '" +
 775   
                         sysid + "'", Project.MSG_DEBUG);
 776   
                 }
 777   
             } catch(IOException ex) {
 778   
                 // ignore
 779   
             }
 780   
         }
 781   
 
 782  0
         return source;
 783   
 
 784   
     }
 785   
 
 786   
     /**
 787   
      * Interface implemented by both the InternalResolver strategy and
 788   
      * the ApacheResolver strategy.
 789   
      */
 790   
     private interface CatalogResolver extends URIResolver, EntityResolver {
 791   
 
 792   
         InputSource resolveEntity(String publicId, String systemId);
 793   
 
 794   
         Source resolve(String href, String base) throws TransformerException;
 795   
     }
 796   
 
 797   
     /**
 798   
      * The InternalResolver strategy is used if the Apache resolver
 799   
      * library (Norm Walsh's library from xml-commons) is not
 800   
      * available.  In this case, external catalog files will be
 801   
      * ignored.
 802   
      *
 803   
      */
 804   
     private class InternalResolver implements CatalogResolver {
 805   
 
 806  0
         public InternalResolver() {
 807  0
             log("Apache resolver library not found, internal resolver will be used",
 808   
                 Project.MSG_VERBOSE);
 809   
         }
 810   
 
 811  0
         public InputSource resolveEntity(String publicId,
 812   
                                          String systemId) {
 813  0
             InputSource result = null;
 814  0
             ResourceLocation matchingEntry = findMatchingEntry(publicId);
 815   
 
 816  0
             if (matchingEntry != null) {
 817   
 
 818  0
                 log("Matching catalog entry found for publicId: '" +
 819   
                     matchingEntry.getPublicId() + "' location: '" +
 820   
                     matchingEntry.getLocation() + "'",
 821   
                     Project.MSG_DEBUG);
 822   
 
 823  0
                 result = filesystemLookup(matchingEntry);
 824   
 
 825  0
                 if (result == null) {
 826  0
                     result = classpathLookup(matchingEntry);
 827   
                 }
 828   
 
 829  0
                 if (result == null) {
 830  0
                     result = urlLookup(matchingEntry);
 831   
                 }
 832   
             }
 833  0
             return result;
 834   
         }
 835   
 
 836  0
         public Source resolve(String href, String base)
 837   
             throws TransformerException {
 838   
 
 839  0
             SAXSource result = null;
 840  0
             InputSource source = null;
 841   
 
 842  0
             ResourceLocation matchingEntry = findMatchingEntry(href);
 843   
 
 844  0
             if (matchingEntry != null) {
 845   
 
 846  0
                 log("Matching catalog entry found for uri: '" +
 847   
                     matchingEntry.getPublicId() + "' location: '" +
 848   
                     matchingEntry.getLocation() + "'",
 849   
                     Project.MSG_DEBUG);
 850   
 
 851   
                 //
 852   
                 // Use the passed in base in preference to the base
 853   
                 // from matchingEntry, which is either null or the
 854   
                 // directory in which the external catalog file from
 855   
                 // which it was obtained is located.  We make a copy
 856   
                 // so matchingEntry's original base is untouched.
 857   
                 //
 858   
                 // This is the standard behavior as per my reading of
 859   
                 // the JAXP and XML Catalog specs.  CKS 11/7/2002
 860   
                 //
 861  0
                 ResourceLocation entryCopy = matchingEntry;
 862  0
                 if (base != null) {
 863  0
                     try {
 864  0
                         URL baseURL = new URL(base);
 865  0
                         entryCopy = new ResourceLocation();
 866  0
                         entryCopy.setBase(baseURL);
 867   
                     }
 868   
                     catch (MalformedURLException ex) {
 869   
                         // ignore
 870   
                     }
 871   
                 }
 872  0
                 entryCopy.setPublicId(matchingEntry.getPublicId());
 873  0
                 entryCopy.setLocation(matchingEntry.getLocation());
 874   
 
 875  0
                 source = filesystemLookup(entryCopy);
 876   
 
 877  0
                 if (source == null) {
 878  0
                     source = classpathLookup(entryCopy);
 879   
                 }
 880   
 
 881  0
                 if (source == null) {
 882  0
                     source = urlLookup(entryCopy);
 883   
                 }
 884   
 
 885  0
                 if (source != null) {
 886  0
                     result = new SAXSource(source);
 887   
                 }
 888   
             }
 889  0
             return result;
 890   
         }
 891   
     }
 892   
 
 893   
     /**
 894   
      * The ApacheResolver strategy is used if the Apache resolver
 895   
      * library (Norm Walsh's library from xml-commons) is available in
 896   
      * the classpath.  The ApacheResolver is a essentially a superset
 897   
      * of the InternalResolver.
 898   
      *
 899   
      */
 900   
     private class ApacheResolver implements CatalogResolver {
 901   
 
 902   
         private Method setXMLCatalog = null;
 903   
         private Method parseCatalog = null;
 904   
         private Method resolveEntity = null;
 905   
         private Method resolve = null;
 906   
 
 907   
         /** The instance of the ApacheCatalogResolver bridge class */
 908   
         private Object resolverImpl = null;
 909   
 
 910   
         private boolean externalCatalogsProcessed = false;
 911   
 
 912  22
         public ApacheResolver(Class resolverImplClass,
 913   
                               Object resolverImpl) {
 914   
 
 915  22
             this.resolverImpl = resolverImpl;
 916   
 
 917   
             //
 918   
             // Get Method instances for each of the methods we need to
 919   
             // call on the resolverImpl using reflection.  We can't
 920   
             // call them directly, because they require on the
 921   
             // xml-commons resolver library which may not be available
 922   
             // in the classpath.
 923   
             //
 924  22
             try {
 925  22
                 setXMLCatalog =
 926   
                     resolverImplClass.getMethod("setXMLCatalog",
 927   
                                                 new Class[]
 928   
                         { XMLCatalog.class });
 929   
 
 930  22
                 parseCatalog =
 931   
                     resolverImplClass.getMethod("parseCatalog",
 932   
                                                 new Class[]
 933   
                         { String.class });
 934   
 
 935  22
                 resolveEntity =
 936   
                     resolverImplClass.getMethod("resolveEntity",
 937   
                                                 new Class[]
 938   
                         { String.class, String.class });
 939   
 
 940  22
                 resolve =
 941   
                     resolverImplClass.getMethod("resolve",
 942   
                                                 new Class[]
 943   
                         { String.class, String.class });
 944   
             }
 945   
             catch (NoSuchMethodException ex) {
 946  0
                 throw new BuildException(ex);
 947   
             }
 948   
 
 949  22
             log("Apache resolver library found, xml-commons resolver will be used",
 950   
                 Project.MSG_VERBOSE);
 951   
         }
 952   
 
 953  19
         public InputSource resolveEntity(String publicId,
 954   
                                          String systemId) {
 955  19
             InputSource result = null;
 956   
 
 957  19
             processExternalCatalogs();
 958   
 
 959  19
             ResourceLocation matchingEntry = findMatchingEntry(publicId);
 960   
 
 961  19
             if (matchingEntry != null) {
 962   
 
 963  14
                 log("Matching catalog entry found for publicId: '" +
 964   
                     matchingEntry.getPublicId() + "' location: '" +
 965   
                     matchingEntry.getLocation() + "'",
 966   
                     Project.MSG_DEBUG);
 967   
 
 968  14
                 result = filesystemLookup(matchingEntry);
 969   
 
 970  14
                 if (result == null) {
 971  5
                     result = classpathLookup(matchingEntry);
 972   
                 }
 973   
 
 974  14
                 if (result == null) {
 975  0
                     try {
 976  0
                         result =
 977   
                             (InputSource)resolveEntity.invoke(resolverImpl,
 978   
                                                               new Object[]
 979   
                                 { publicId, systemId });
 980   
                     }
 981   
                     catch (Exception ex) {
 982  0
                         throw new BuildException(ex);
 983   
                     }
 984   
                 }
 985   
             }
 986   
             else {
 987   
                 //
 988   
                 // We didn't match a ResourceLocation, but since we
 989   
                 // only support PUBLIC and URI entry types internally,
 990   
                 // it is still possible that there is another entry in
 991   
                 // an external catalog that will match.  We call
 992   
                 // Apache resolver's resolveEntity method to cover
 993   
                 // this possibility.
 994   
                 //
 995  5
                 try {
 996  5
                     result =
 997   
                         (InputSource)resolveEntity.invoke(resolverImpl,
 998   
                                                           new Object[]
 999   
                             { publicId, systemId });
 1000   
                 }
 1001   
                 catch (Exception ex) {
 1002  0
                     throw new BuildException(ex);
 1003   
                 }
 1004   
             }
 1005   
 
 1006  19
             return result;
 1007   
         }
 1008   
 
 1009  10
         public Source resolve(String href, String base)
 1010   
             throws TransformerException {
 1011   
 
 1012  10
             SAXSource result = null;
 1013  10
             InputSource source = null;
 1014   
 
 1015  10
             processExternalCatalogs();
 1016   
 
 1017  10
             ResourceLocation matchingEntry = findMatchingEntry(href);
 1018   
 
 1019  10
             if (matchingEntry != null) {
 1020   
 
 1021  4
                 log("Matching catalog entry found for uri: '" +
 1022   
                     matchingEntry.getPublicId() + "' location: '" +
 1023   
                     matchingEntry.getLocation() + "'",
 1024   
                     Project.MSG_DEBUG);
 1025   
 
 1026   
                 //
 1027   
                 // Use the passed in base in preference to the base
 1028   
                 // from matchingEntry, which is either null or the
 1029   
                 // directory in which the external catalog file from
 1030   
                 // which it was obtained is located.  We make a copy
 1031   
                 // so matchingEntry's original base is untouched.  Of
 1032   
                 // course, if there is no base, no need to make a
 1033   
                 // copy...
 1034   
                 //
 1035   
                 // This is the standard behavior as per my reading of
 1036   
                 // the JAXP and XML Catalog specs.  CKS 11/7/2002
 1037   
                 //
 1038  4
                 ResourceLocation entryCopy = matchingEntry;
 1039  4
                 if (base != null) {
 1040  1
                     try {
 1041  1
                         URL baseURL = new URL(base);
 1042  1
                         entryCopy = new ResourceLocation();
 1043  1
                         entryCopy.setBase(baseURL);
 1044   
                     }
 1045   
                     catch (MalformedURLException ex) {
 1046   
                         // ignore
 1047   
                     }
 1048   
                 }
 1049  4
                 entryCopy.setPublicId(matchingEntry.getPublicId());
 1050  4
                 entryCopy.setLocation(matchingEntry.getLocation());
 1051   
 
 1052  4
                 source = filesystemLookup(entryCopy);
 1053   
 
 1054  4
                 if (source == null) {
 1055  1
                     source = classpathLookup(entryCopy);
 1056   
                 }
 1057   
 
 1058  4
                 if (source != null) {
 1059  4
                     result = new SAXSource(source);
 1060   
                 } else {
 1061  0
                     try {
 1062  0
                         result =
 1063   
                             (SAXSource)resolve.invoke(resolverImpl,
 1064   
                                                       new Object[]
 1065   
                                 { href, base });
 1066   
                     }
 1067   
                     catch (Exception ex) {
 1068  0
                         throw new BuildException(ex);
 1069   
                     }
 1070   
                 }
 1071   
             }
 1072   
             else {
 1073   
                 //
 1074   
                 // We didn't match a ResourceLocation, but since we
 1075   
                 // only support PUBLIC and URI entry types internally,
 1076   
                 // it is still possible that there is another entry in
 1077   
                 // an external catalog that will match.  We call
 1078   
                 // Apache resolver's resolveEntity method to cover
 1079   
                 // this possibility.
 1080   
                 //
 1081  6
                 try {
 1082  6
                     result =
 1083   
                         (SAXSource)resolve.invoke(resolverImpl,
 1084   
                                                   new Object[]
 1085   
                             { href, base });
 1086   
                 }
 1087   
                 catch (Exception ex) {
 1088  0
                     throw new BuildException(ex);
 1089   
                 }
 1090   
             }
 1091  10
             return result;
 1092   
         }
 1093   
 
 1094   
         /**
 1095   
          * Process each external catalog file specified in a
 1096   
          * <code>&lt;catalogpath&gt;</code>.  It will be
 1097   
          * parsed by the resolver library, and the individual elements
 1098   
          * will be added back to us (that is, the controlling
 1099   
          * XMLCatalog instance) via a callback mechanism.
 1100   
          */
 1101  29
         private void processExternalCatalogs() {
 1102   
 
 1103  29
             if (externalCatalogsProcessed == false) {
 1104   
 
 1105  22
                 try {
 1106  22
                     setXMLCatalog.invoke(resolverImpl,
 1107   
                                          new Object[]
 1108   
                     { XMLCatalog.this });
 1109   
                 }
 1110   
                 catch (Exception ex) {
 1111  0
                     throw new BuildException(ex);
 1112   
                 }
 1113   
 
 1114   
                 // Parse each catalog listed in nested <catalogpath> elements
 1115  22
                 Path catPath = getCatalogPath();
 1116  22
                 if (catPath != null) {
 1117  9
                     log("Using catalogpath '" + getCatalogPath()+"'", Project.MSG_DEBUG);
 1118  9
                     String[] catPathList = getCatalogPath().list();
 1119   
 
 1120  9
                     for (int i=0; i< catPathList.length; i++) {
 1121  4
                         File catFile = new File(catPathList[i]);
 1122  4
                         log("Parsing "+catFile, Project.MSG_DEBUG);
 1123  4
                         try {
 1124  4
                             parseCatalog.invoke(resolverImpl,
 1125   
                                     new Object[]
 1126   
                                     { catFile.getPath() });
 1127   
                         }
 1128   
                         catch (Exception ex) {
 1129  0
                             throw new BuildException(ex);
 1130   
                         }
 1131   
                     }
 1132   
                 }
 1133   
             }
 1134  29
             externalCatalogsProcessed = true;
 1135   
         }
 1136   
     }
 1137   
 } //-- XMLCatalog
 1138