/**
 * Redistribution and use of this software and associated documentation ("Software"), with or
 * without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright statements and notices. Redistributions
 * must also contain a copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of
 * conditions and the following disclaimer in the documentation and/or other materials provided with
 * the distribution.
 *
 * 3. The name "Exolab" must not be used to endorse or promote products derived from this Software
 * without prior written permission of Intalio, Inc. For written permission, please contact
 * info@exolab.org.
 *
 * 4. Products derived from this Software may not be called "Exolab" nor may "Exolab" appear in
 * their names without prior written permission of Intalio, Inc. Exolab is a registered trademark of
 * Intalio, Inc.
 *
 * 5. Due credit should be given to the Exolab Project (http://www.exolab.org/).
 *
 * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESSED OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTALIO, INC. OR ITS
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 1999 (C) Intalio, Inc. All Rights Reserved.
 *
 * $Id$
 */

package org.exolab.castor.util;

import java.net.URL;
import java.net.MalformedURLException;
import java.io.IOException;
import org.xml.sax.SAXException;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.exolab.castor.net.util.URIUtils;

/**
 * Entity resolver for various DTD/schema. Holds information and performs resolving on a variety of
 * DTD and schema, both those defined by Castor and those used by Castor and cached by it.
 * <p>
 * The following DTD and XML schema are supported:
 * <ul>
 * <li>Castor mapping DTD/Schema
 * <li>Castor JDO configuration DTD/Schema
 * <li>XML Schema DTDs
 * </ul>
 * <p>
 * This resolver can resolve both public and system identifiers, and will return an input stream
 * into a cached resource in the Castor JAR.
 * <p>
 * This resolver can be used as wrapper to another entity resolver. For example, if a resolver is
 * used for external entities in the mapping file, construct a new resolver using the
 * {@link #DTDResolver(EntityResolver)} constructor.
 * <p>
 *
 * @author <a href="mailto:arkin@intalio.com">Assaf Arkin</a>
 * @version $Revision$ $Date: 2005-12-13 14:58:48 -0700 (Tue, 13 Dec 2005) $
 */
public class DTDResolver implements EntityResolver {

  /**
   * Holds information about a given DTD of XML Schema.
   */
  static class DTDInfo {

    /**
     * The public identifier. Null if unknown.
     */
    private final String publicId;

    /**
     * The system identifier. Null if unknown.
     */
    private final String systemId;

    /**
     * The resource name, if a copy of the document is available.
     */
    private final String resource;

    /**
     * Creates an instance of DTDInfo.
     *
     * @param publicId public id
     * @param systemId system id
     * @param namespace namespace
     * @param prefix prefix
     * @param resource resource
     */
    DTDInfo(final String publicId, final String systemId, final String namespace,
        final String prefix, final String resource) {
      this.publicId = publicId;
      this.systemId = systemId;
      this.resource = resource;
    }
  }

  /**
   * Defines information about a variety of DTDs, both those defined by Castor and those defined
   * elsewhere and cached by Castor.
   */
  private final DTDInfo[] _dtdInfo = new DTDInfo[] {
      // Information for Mapping DTD and schema
      new DTDInfo("-//EXOLAB/Castor Mapping DTD Version 1.0//EN",
          "http://castor.exolab.org/mapping.dtd", "castor.exolab.org", "castor",
          "/org/exolab/castor/mapping/mapping.dtd"),
      new DTDInfo("-//EXOLAB/Castor Mapping Schema Version 1.0//EN",
          "http://castor.exolab.org/mapping.xsd", "castor.exolab.org", "castor",
          "/org/exolab/castor/mapping/mapping.xsd"),
      new DTDInfo("-//EXOLAB/Castor Mapping DTD Version 1.0//EN", "http://castor.org/mapping.dtd",
          "castor.org", "castor", "/org/exolab/castor/mapping/mapping.dtd"),
      new DTDInfo("-//EXOLAB/Castor Mapping Schema Version 1.0//EN",
          "http://castor.org/mapping.xsd", "castor.org", "castor",
          "/org/exolab/castor/mapping/mapping.xsd"),
      // Information for JDO configuration DTD and schema
      new DTDInfo("-//EXOLAB/Castor JDO Configuration DTD Version 1.0//EN",
          "http://castor.exolab.org/jdo-conf.dtd", "castor.exolab.org", "castor",
          "/org/castor/jdo/conf/jdo-conf.dtd"),
      new DTDInfo("-//EXOLAB/Castor JDO Configuration Schema Version 1.0//EN",
          "http://castor.exolab.org/jdo-conf.xsd", "castor.exolab.org", "castor",
          "/org/castor/jdo/conf/jdo-conf.xsd"),
      new DTDInfo("-//EXOLAB/Castor JDO Configuration DTD Version 1.0//EN",
          "http://castor.org/jdo-conf.dtd", "castor.org", "castor",
          "/org/castor/jdo/conf/jdo-conf.dtd"),
      new DTDInfo("-//EXOLAB/Castor JDO Configuration Schema Version 1.0//EN",
          "http://castor.org/jdo-conf.xsd", "castor.org", "castor",
          "/org/castor/jdo/conf/jdo-conf.xsd"),
      // Resolving for XML Schema DTDs
      new DTDInfo("-//W3C//DTD XMLSCHEMA 19991216//EN",
          "http://www.w3.org/TR/2000/WD-xmlschema-1-20000225/structures.dtd", null, null,
          "/org/exolab/castor/util/resources/structures.dtd"),
      new DTDInfo(null, "http://www.w3.org/TR/2000/WD-xmlschema-2-20000225/datatypes.dtd", null,
          null, "/org/exolab/castor/util/resources/datatypes.dtd"),
      new DTDInfo(null, "http://www.w3.org/TR/2000/WD-xmlschema-1-20000225/structures.xsd", null,
          null, "/org/exolab/castor/util/resources/structures.xsd"),

  };

  /**
   * The wrapped resolver.
   */
  private EntityResolver _resolver;

  /**
   * Base URL, if known.
   */
  private URL _baseUrl;

  /**
   * Constructs a new DTD resolver. This resolver wraps another resolver and will delegate all
   * resolving not related to the Castor mapping files to that resolver. The wrapper resolver will
   * typically be used for entities appearing in the actual mapping file.
   */
  public DTDResolver(EntityResolver resolver) {
    _resolver = resolver;
  }

  /**
   * Constructs a new DTD resolver.
   */
  public DTDResolver() {}

  /**
   * Sets the base URL to use.
   * 
   * @param baseUrl Base URL.
   */
  public void setBaseURL(final URL baseUrl) {
    _baseUrl = baseUrl;

    // //-- make sure we have a document base and not
    // //-- a full URL to an actual file
    // if (baseUrl != null) {
    // String urlString = baseUrl.toExternalForm();
    // String docBase = URIUtils.getDocumentBase(urlString);
    // if (urlString.equals(docBase)) {
    // _baseUrl = baseUrl;
    // }
    // else if ((docBase != null) && (docBase.length() > 0)) {
    // try {
    // _baseUrl = new URL(docBase);
    // }
    // catch(MalformedURLException mue) {
    // // TODO: bubble up exception instead of
    // // rethrowing
    // String error = "Malformed URL: " + docBase;
    // throw new IllegalStateException(error);
    // }
    // }
    // else {
    // _baseUrl = null;
    // }
    // }
  }

  /**
   * Returns the base URL in use.
   * 
   * @return The base URL.
   */
  public URL getBaseURL() {
    return _baseUrl;
  }

  /**
   * Resolves public & system ids to files stored within the JAR.
   *
   * @see org.xml.sax.EntityResolver#resolveEntity(java.lang.String, java.lang.String)
   */
  public InputSource resolveEntity(final String publicId, final String systemId)
      throws IOException, SAXException {
    int i;
    InputSource source = null;

    // First, resolve all the DTD/schema.
    for (i = 0; i < _dtdInfo.length; ++i) {
      if (publicId != null && publicId.equals(_dtdInfo[i].publicId)) {
        source = new InputSource(getClass().getResourceAsStream(_dtdInfo[i].resource));
        source.setPublicId(publicId);
        return source;
      }
      if (systemId != null && systemId.equals(_dtdInfo[i].systemId)) {
        source = new InputSource(getClass().getResourceAsStream(_dtdInfo[i].resource));
        source.setSystemId(systemId);
        return source;
      }
    }

    // If a resolver was specified, use it.
    if (_resolver != null) {
      source = _resolver.resolveEntity(publicId, systemId);
      if (source != null) {
        return source;
      }
    }

    // Can't resolve public id, but might be able to resolve relative
    // system id, since we have a base URI.
    if (systemId != null && _baseUrl != null) {
      URL url;

      try {
        url = new URL(_baseUrl, systemId);
        source = new InputSource(url.openStream());
        source.setSystemId(systemId);
        return source;
      } catch (MalformedURLException except) {
        try {
          String absURL = URIUtils.resolveAsString(systemId, _baseUrl.toString());
          url = new URL(absURL);
          source = new InputSource(url.openStream());
          source.setSystemId(systemId);
          return source;
        } catch (MalformedURLException ex2) {
          // nothing to do
        }
      } catch (java.io.FileNotFoundException fnfe) {
        try {
          String absURL = URIUtils.resolveAsString(systemId, _baseUrl.toString());
          url = new URL(absURL);
          source = new InputSource(url.openStream());
          source.setSystemId(systemId);
          return source;
        } catch (MalformedURLException ex2) {
          // nothing to do
        }
      }
      return null;
    }

    // No resolving.
    return null;
  }

}
