/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.axis.wsdl.gen;

import org.apache.axis.utils.Messages;
import org.apache.axis.wsdl.symbolTable.BindingEntry;
import org.apache.axis.wsdl.symbolTable.CollectionElement;
import org.apache.axis.wsdl.symbolTable.MessageEntry;
import org.apache.axis.wsdl.symbolTable.PortTypeEntry;
import org.apache.axis.wsdl.symbolTable.ServiceEntry;
import org.apache.axis.wsdl.symbolTable.SymTabEntry;
import org.apache.axis.wsdl.symbolTable.SymbolTable;
import org.apache.axis.wsdl.toJava.Utils;
import org.apache.axis.wsdl.symbolTable.Type;
import org.apache.axis.wsdl.symbolTable.TypeEntry;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import javax.wsdl.Binding;
import javax.wsdl.Definition;
import javax.wsdl.WSDLException;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Vector;

/**
 * This is a class with no documentation.
 */
public class Parser {

    /** Field debug */
    protected boolean debug = false;

    /** Field quiet */
    protected boolean quiet = false;

    /** Field imports */
    protected boolean imports = true;

    /** Field verbose */
    protected boolean verbose = false;

    /** Field nowrap */
    protected boolean nowrap = false;

    // Username and password for Authentication

    /** Field username */
    protected String username = null;

    /** Field password */
    protected String password = null;

    /** If this is false, we'll prefer "String[]" to "ArrayOfString" for literal wrapped arrays */
    protected boolean wrapArrays = false;

    // Timeout, in milliseconds, to let the Emitter do its work

    /** Field timeoutms */
    private long timeoutms = 45000;    // 45 sec default

    /** Field genFactory */
    private GeneratorFactory genFactory = null;

    /** Field symbolTable */
    private SymbolTable symbolTable = null;

    /**
     * Method isDebug
     *
     * @return
     */
    public boolean isDebug() {
        return debug;
    }    // isDebug

    /**
     * Method setDebug
     *
     * @param debug
     */
    public void setDebug(boolean debug) {
        this.debug = debug;
    }    // setDebug

    /**
     * Method isQuiet
     *
     * @return
     */
    public boolean isQuiet() {
        return quiet;
    }

    /**
     * Method setQuiet
     *
     * @param quiet
     */
    public void setQuiet(boolean quiet) {
        this.quiet = quiet;
    }

    /**
     * Method isImports
     *
     * @return
     */
    public boolean isImports() {
        return imports;
    }    // isImports

    /**
     * Method setImports
     *
     * @param imports
     */
    public void setImports(boolean imports) {
        this.imports = imports;
    }    // setImports

    /**
     * Method isVerbose
     *
     * @return
     */
    public boolean isVerbose() {
        return verbose;
    }    // isVerbose

    /**
     * Method setVerbose
     *
     * @param verbose
     */
    public void setVerbose(boolean verbose) {
        this.verbose = verbose;
    }    // setVerbose

    /**
     * Method isNowrap
     *
     * @return
     */
    public boolean isNowrap() {
        return nowrap;
    }

    /**
     * Method setNowrap
     *
     * @param nowrap
     */
    public void setNowrap(boolean nowrap) {
        this.nowrap = nowrap;
    }

    /**
     * Return the current timeout setting
     *
     * @return
     */
    public long getTimeout() {
        return timeoutms;
    }

    /**
     * Set the timeout, in milliseconds
     *
     * @param timeout
     */
    public void setTimeout(long timeout) {
        this.timeoutms = timeout;
    }

    /**
     * Method getUsername
     *
     * @return
     */
    public String getUsername() {
        return username;
    }    // getUsername

    /**
     * Method setUsername
     *
     * @param username
     */
    public void setUsername(String username) {
        this.username = username;
    }    // setUsername

    /**
     * Method getPassword
     *
     * @return
     */
    public String getPassword() {
        return password;
    }    // getPassword

    /**
     * Method setPassword
     *
     * @param password
     */
    public void setPassword(String password) {
        this.password = password;
    }    // setPassword

    /**
     * Method getFactory
     *
     * @return
     */
    public GeneratorFactory getFactory() {
        return genFactory;
    }    // getFactory

    /**
     * Method setFactory
     *
     * @param factory
     */
    public void setFactory(GeneratorFactory factory) {
        this.genFactory = factory;
    }    // setFactory

    /**
     * Get the symbol table.  The symbol table is null until
     * run is called.
     *
     * @return
     */
    public SymbolTable getSymbolTable() {
        return symbolTable;
    }    // getSymbolTable

    /**
     * Return the current definition.  The current definition is
     * null until run is called.
     *
     * @return
     */
    public Definition getCurrentDefinition() {

        return (symbolTable == null)
                ? null
                : symbolTable.getDefinition();
    }    // getCurrentDefinition

    /**
     * Get the current WSDL URI.  The WSDL URI is null until
     * run is called.
     *
     * @return
     */
    public String getWSDLURI() {

        return (symbolTable == null)
                ? null
                : symbolTable.getWSDLURI();
    }    // getWSDLURI

    /**
     * Parse a WSDL at a given URL.
     * <p/>
     * This method will time out after the number of milliseconds specified
     * by our timeoutms member.
     *
     * @param wsdlURI
     * @throws Exception
     */
    public void run(String wsdlURI) throws Exception {

        if (getFactory() == null) {
            setFactory(new NoopFactory());
        }

        symbolTable = new SymbolTable(genFactory.getBaseTypeMapping(), imports,
                verbose, nowrap);
        symbolTable.setQuiet(quiet);
        symbolTable.setWrapArrays(wrapArrays);

        // We run the actual Emitter in a thread that we can kill
        WSDLRunnable runnable = new WSDLRunnable(symbolTable, wsdlURI);
        Thread wsdlThread = new Thread(runnable);

        wsdlThread.start();

        try {
            if (timeoutms > 0) {
                wsdlThread.join(timeoutms);
            } else {
                wsdlThread.join();
            }
        } catch (InterruptedException e) {
        }

        if (wsdlThread.isAlive()) {
            wsdlThread.interrupt();

            throw new IOException(Messages.getMessage("timedOut"));
        }

        if (runnable.getFailure() != null) {
            throw runnable.getFailure();
        }
    }    // run

    /**
     * Class WSDLRunnable
     *
     * @version %I%, %G%
     */
    private class WSDLRunnable implements Runnable {

        /** Field symbolTable */
        private SymbolTable symbolTable;

        /** Field wsdlURI */
        private String wsdlURI;

        /** Field failure */
        private Exception failure = null;

        /**
         * Constructor WSDLRunnable
         *
         * @param symbolTable
         * @param wsdlURI
         */
        public WSDLRunnable(SymbolTable symbolTable, String wsdlURI) {
            this.symbolTable = symbolTable;
            this.wsdlURI = wsdlURI;
        }    // ctor

        /**
         * Method run
         */
        public void run() {

            try {
                symbolTable.populate(wsdlURI, username, password);
                generate(symbolTable);
            } catch (Exception e) {
                failure = e;
            }
        }    // run

        /**
         * Method getFailure
         *
         * @return
         */
        public Exception getFailure() {
            return failure;
        }    // getFailure
    }    // WSDLRunnable

    /**
     * Call this method if your WSDL document has already been parsed as an XML DOM document.
     *
     * @param context context This is directory context for the Document.  If the Document were from file "/x/y/z.wsdl" then the context could be "/x/y" (even "/x/y/z.wsdl" would work).  If context is null, then the context becomes the current directory.
     * @param doc     doc This is the XML Document containing the WSDL.
     * @throws IOException
     * @throws SAXException
     * @throws WSDLException
     * @throws ParserConfigurationException
     */
    public void run(String context, Document doc)
            throws IOException, SAXException, WSDLException,
            ParserConfigurationException {

        if (getFactory() == null) {
            setFactory(new NoopFactory());
        }

        symbolTable = new SymbolTable(genFactory.getBaseTypeMapping(), imports,
                verbose, nowrap);

        symbolTable.populate(context, doc);
        generate(symbolTable);
    }    // run

    /**
     * Method sanityCheck
     *
     * @param symbolTable
     */
    protected void sanityCheck(SymbolTable symbolTable) {

        // do nothing.
    }

    /**
     * Method generate
     *
     * @param symbolTable
     * @throws IOException
     */
    private void generate(SymbolTable symbolTable) throws IOException {

        sanityCheck(symbolTable);

        Definition def = symbolTable.getDefinition();

        genFactory.generatorPass(def, symbolTable);

        if (isDebug()) {
            symbolTable.dump(System.out);
        }

        // Generate bindings for types
        generateTypes(symbolTable);

        Iterator it = symbolTable.getHashMap().values().iterator();

        while (it.hasNext()) {
            Vector v = (Vector) it.next();

            for (int i = 0; i < v.size(); ++i) {
                SymTabEntry entry = (SymTabEntry) v.elementAt(i);
                Generator gen = null;

                if (entry instanceof MessageEntry) {
                    gen = genFactory.getGenerator(
                            ((MessageEntry) entry).getMessage(), symbolTable);
                } else if (entry instanceof PortTypeEntry) {
                    PortTypeEntry pEntry = (PortTypeEntry) entry;

                    // If the portType is undefined, then we're parsing a Definition
                    // that didn't contain a portType, merely a binding that referred
                    // to a non-existent port type.  Don't bother writing it.
                    if (pEntry.getPortType().isUndefined()) {
                        continue;
                    }

                    gen = genFactory.getGenerator(pEntry.getPortType(),
                            symbolTable);
                } else if (entry instanceof BindingEntry) {
                    BindingEntry bEntry = (BindingEntry) entry;
                    Binding binding = bEntry.getBinding();

                    // If the binding is undefined, then we're parsing a Definition
                    // that didn't contain a binding, merely a service that referred
                    // to a non-existent binding.  Don't bother writing it.
                    if (binding.isUndefined() || !bEntry.isReferenced()) {
                        continue;
                    }

                    gen = genFactory.getGenerator(binding, symbolTable);
                } else if (entry instanceof ServiceEntry) {
                    gen = genFactory.getGenerator(
                            ((ServiceEntry) entry).getService(), symbolTable);
                }

                if (gen != null) {
                    gen.generate();
                }
            }
        }

        // Output extra stuff (deployment files and faults)
        // outside of the recursive emit method.
        Generator gen = genFactory.getGenerator(def, symbolTable);

        gen.generate();
    }    // generate

    /**
     * Generate bindings (classes and class holders) for the complex types.
     * If generating serverside (skeleton) spit out beanmappings
     *
     * @param symbolTable
     * @throws IOException
     */
    private void generateTypes(SymbolTable symbolTable) throws IOException {

        Map elements = symbolTable.getElementIndex();
        Collection elementCollection = elements.values();
        for (Iterator i = elementCollection.iterator(); i.hasNext(); ) {
            TypeEntry type = (TypeEntry) i.next();

            // Write out the type if and only if:
            // - we found its definition (getNode())
            // - it is referenced
            // - it is not a base type or an attributeGroup or xs:group
            // - it is a Type (not an Element) or a CollectionElement
            // (Note that types that are arrays are passed to getGenerator
            // because they may require a Holder)
            // A CollectionElement is an array that might need a holder
            boolean isType = ((type instanceof Type)
                    || (type instanceof CollectionElement));

            if ((type.getNode() != null)
                    && !Utils.isXsNode(type.getNode(), "attributeGroup")
                    && !Utils.isXsNode(type.getNode(), "group")
                    && type.isReferenced() && isType
                    && (type.getBaseType() == null)) {
                Generator gen = genFactory.getGenerator(type, symbolTable);

                gen.generate();
            }
        }

        Map types = symbolTable.getTypeIndex();
        Collection typeCollection = types.values();
        for (Iterator i = typeCollection.iterator(); i.hasNext(); ) {
            TypeEntry type = (TypeEntry) i.next();

            // Write out the type if and only if:
            // - we found its definition (getNode())
            // - it is referenced
            // - it is not a base type or an attributeGroup or xs:group
            // - it is a Type (not an Element) or a CollectionElement
            // (Note that types that are arrays are passed to getGenerator
            // because they may require a Holder)
            // A CollectionElement is an array that might need a holder
            boolean isType = ((type instanceof Type)
                    || (type instanceof CollectionElement));

            if ((type.getNode() != null)
                    && !Utils.isXsNode(type.getNode(), "attributeGroup")
                    && !Utils.isXsNode(type.getNode(), "group")
                    && type.isReferenced() && isType
                    && (type.getBaseType() == null)) {
                Generator gen = genFactory.getGenerator(type, symbolTable);

                gen.generate();
            }
        }
    }    // generateTypes
}    // class Parser
