/****************************************************************************/
/*  File:       DescriptorParser.java                                       */
/*  Author:     F. Georges - H2O Consulting                                 */
/*  Date:       2010-10-07                                                  */
/*  Tags:                                                                   */
/*      Copyright (c) 2010 Florent Georges (see end of file.)               */
/* ------------------------------------------------------------------------ */


package org.expath.pkg.repo.parser;

import java.util.HashMap;
import java.util.Map;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.transform.Source;
import org.expath.pkg.repo.Package;
import org.expath.pkg.repo.PackageException;
import org.expath.pkg.repo.Storage;
import org.expath.pkg.repo.Repository;
import org.expath.pkg.repo.URISpace;

/**
 * Parser for package descriptors.
 *
 * @author Florent Georges
 */
public class DescriptorParser
{
    /**
     * Parse a package descriptor and build the corresponding {@link Package} object.
     *
     * @param desc the package descriptor
     * @param rsrc_name the resource name
     * @param storage the repository storage
     * @param repo the repository
     *
     * @return the package
     *
     * @throws PackageException if an error occurs during parsing
     */
    public Package parse(Source desc, String rsrc_name, Storage storage, Repository repo)
            throws PackageException
    {
        // parse the pkg descriptor
        XMLStreamReader parser = null;
        try {
            parser = XS_HELPER.makeParser(desc);
            // go to the package element
            XS_HELPER.ensureDocument(parser);
            XS_HELPER.ensureNextElement(parser, "package");
            // check the spec version
            String spec = XS_HELPER.getAttributeValue(parser, "spec");
            if (!"1.0".equals(spec)) {
                throw new PackageException("Spec version is not 1.0: '" + spec + "'");
            }
            // get the package attributes
            String name = XS_HELPER.getAttributeValue(parser, "name");
            String abbrev = XS_HELPER.getAttributeValue(parser, "abbrev");
            String version = XS_HELPER.getAttributeValue(parser, "version");
            // TODO: Check the module "dir" exists? (in the storage object)
            XS_HELPER.ensureNextElement(parser, "title");
            String title = XS_HELPER.getElementValue(parser);
            Package pkg = null;
            try {
                // the home URI
                String home = null;
                parser.next();
                if (XS_HELPER.isElement(parser, "home")) {
                    home = XS_HELPER.getElementValue(parser);
                    parser.next();
                }
                // create the package object
                Storage.PackageResolver resolver = storage.makePackageResolver(rsrc_name, abbrev);
                pkg = new Package(repo, resolver, name, abbrev, version, title, home);
                // the dependencies
                while (XS_HELPER.isElement(parser, "dependency")) {
                    handleDependency(parser, pkg);
                    parser.next();
                }
                // the components
                while (parser.getEventType() == XMLStreamConstants.START_ELEMENT) {
                    handleComponent(parser, pkg);
                }
            } catch (XMLStreamException ex) {
                throw new PackageException("Error parsing the package descriptor", ex);
            }

            // TODO: Here, we must be on the </package> tag.  Ensure it...
            return pkg;
        } finally {
            try {
                parser.close();
            } catch (final XMLStreamException e) {
                throw new PackageException(e.getMessage(), e);
            }
        }
    }

    /**
     * Handle a dependency element.
     *
     * @param parser the parser
     * @param pkg the package
     *
     * @throws PackageException if an error occurs
     * @throws XMLStreamException if an error occurs reading the stream
     */
    private void handleDependency(XMLStreamReader parser, Package pkg)
            throws PackageException
                 , XMLStreamException
    {
        String pkg_name  = parser.getAttributeValue(null, "package");
        String processor = parser.getAttributeValue(null, "processor");
        String versions  = parser.getAttributeValue(null, "versions");
        String semver    = parser.getAttributeValue(null, "semver");
        String min       = parser.getAttributeValue(null, "semver-min");
        String max       = parser.getAttributeValue(null, "semver-max");
        if ( pkg_name == null && processor == null ) {
            throw new PackageException("None of @package nor @processor are set on a dependency element");
        }
        if ( pkg_name != null && processor != null ) {
            throw new PackageException("@package and @processor are both set on a dependency element");
        }
        if ( pkg_name != null ) {
            pkg.addPackageDep(pkg_name, versions, semver, min, max);
        }
        else {
            pkg.addProcessorDep(processor, versions, semver, min, max);
        }
        parser.next();
    }

    /**
     * Handle any single parse event after pkg:dependency elements.
     *
     * @param parser the parser
     * @param pkg the package
     *
     * @throws PackageException if an error occurs
     * @throws XMLStreamException if an error occurs reading the stream
     */
    private void handleComponent(XMLStreamReader parser, Package pkg)
            throws PackageException
                 , XMLStreamException
    {
        if ( PKG_NS.equals(parser.getNamespaceURI()) ) {
            String local = parser.getLocalName();
            PkgComponentHandler handler = MY_HANDLERS.get(local);
            if ( handler == null ) {
                throw new PackageException("Unknown component type: " + local);
            }
            handler.handleDescription(parser, pkg, XS_HELPER);
        }
        else {
            // ignore elements not in the EXPath Pkg namespace
            XS_HELPER.ignoreElement(parser);
        }
        parser.next();
    }

    /** The static handlers map. */
    private static final Map<String, PkgComponentHandler> MY_HANDLERS;
    static {
        Map<String, PkgComponentHandler> handlers = new HashMap<String, PkgComponentHandler>();
        try {
            handlers.put("xslt",       new PkgHandlerUriFile(URISpace.XSLT,       "import-uri", "file"));
            handlers.put("xproc",      new PkgHandlerUriFile(URISpace.XPROC,      "import-uri", "file"));
            handlers.put("xsd",        new PkgHandlerUriFile(URISpace.XSD,        "import-uri", "namespace", "file"));
            handlers.put("rng",        new PkgHandlerUriFile(URISpace.RNG,        "import-uri", "file"));
            handlers.put("rnc",        new PkgHandlerUriFile(URISpace.RNC,        "import-uri", "file"));
            handlers.put("schematron", new PkgHandlerUriFile(URISpace.SCHEMATRON, "import-uri", "file"));
            handlers.put("nvdl",       new PkgHandlerUriFile(URISpace.NVDL,       "import-uri", "file"));
            handlers.put("xquery",     new PkgHandlerUriFile(URISpace.XQUERY,     "import-uri", "namespace", "file"));
            handlers.put("dtd",        new PkgHandlerDtd());
        }
        catch ( PackageException ex ) {
            throw new RuntimeException("Exception occured during static members initialization", ex);
        }
        MY_HANDLERS = handlers;
    }
    /** The namespace of the package descriptor elements. */
    public static final String PKG_NS = "http://expath.org/ns/pkg";
    /** The XML Streaming API helper object. */
    public static final XMLStreamHelper XS_HELPER = new XMLStreamHelper(PKG_NS);
}


/* ------------------------------------------------------------------------ */
/*  DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS COMMENT.               */
/*                                                                          */
/*  The contents of this file are subject to the Mozilla Public License     */
/*  Version 1.0 (the "License"); you may not use this file except in        */
/*  compliance with the License. You may obtain a copy of the License at    */
/*  http://www.mozilla.org/MPL/.                                            */
/*                                                                          */
/*  Software distributed under the License is distributed on an "AS IS"     */
/*  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See    */
/*  the License for the specific language governing rights and limitations  */
/*  under the License.                                                      */
/*                                                                          */
/*  The Original Code is: all this file.                                    */
/*                                                                          */
/*  The Initial Developer of the Original Code is Florent Georges.          */
/*                                                                          */
/*  Contributor(s): none.                                                   */
/* ------------------------------------------------------------------------ */
