/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 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 end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Axis" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``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 THE APACHE SOFTWARE FOUNDATION 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.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */
package org.jboss.axis.wsdl.toJava;

import org.jboss.axis.encoding.DefaultSOAPEncodingTypeMappingImpl;
import org.jboss.axis.encoding.DefaultTypeMappingImpl;
import org.jboss.axis.encoding.TypeMapping;
import org.jboss.axis.enums.Scope;
import org.jboss.axis.i18n.Messages;
import org.jboss.axis.utils.ClassUtils;
import org.jboss.axis.utils.JavaUtils;
import org.jboss.axis.wsdl.gen.GeneratorFactory;
import org.jboss.axis.wsdl.gen.Parser;
import org.jboss.axis.wsdl.symbolTable.BaseTypeMapping;
import org.jboss.axis.wsdl.symbolTable.SymTabEntry;
import org.jboss.axis.wsdl.symbolTable.SymbolTable;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import javax.wsdl.WSDLException;
import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;
import java.util.Vector;

/**
 * This class produces java files for stubs, skeletons, and types from a
 * WSDL document.
 *
 * @author Russell Butek (butek@us.ibm.com)
 * @author Tom Jordahl (tjordahl@macromedia.com)
 * @author Rich Scheuerle (scheu@us.ibm.com)
 * @author Steve Graham (sggraham@us.ibm.com)
 */
public class Emitter extends Parser
{

   public static final String DEFAULT_NSTOPKG_FILE = "NStoPkg.properties";

   protected HashMap namespaceMap = new HashMap();
   protected String typeMappingVersion = "1.1";
   protected BaseTypeMapping baseTypeMapping = null;
   protected Namespaces namespaces = null;
   protected String NStoPkgFilename = null;

   private boolean bEmitServer = false;
   private boolean bDeploySkeleton = false;
   private boolean bEmitTestCase = false;
   private boolean bGenerateAll = false;
   private boolean bHelperGeneration = false;
   private String packageName = null;
   private Scope scope = null;
   private GeneratedFileInfo fileInfo = new GeneratedFileInfo();
   private HashMap delayedNamespacesMap = new HashMap();
   private String outputDir = null;

   /**
    * Default constructor.
    */
   public Emitter()
   {
      setFactory(new JavaGeneratorFactory(this));
   } // ctor

   ///////////////////////////////////////////////////
   //
   // Command line switches
   //

   /**
    * Turn on/off server skeleton creation
    *
    * @param value
    */
   public void setServerSide(boolean value)
   {
      this.bEmitServer = value;
   } // setServerSide

   /**
    * Indicate if we should be emitting server side code and deploy/undeploy
    */
   public boolean isServerSide()
   {
      return bEmitServer;
   } // isServerSide

   /**
    * Turn on/off server skeleton deploy
    *
    * @param value
    */
   public void setSkeletonWanted(boolean value)
   {
      bDeploySkeleton = value;
   } // setSkeletonWanted

   /**
    * Indicate if we should be deploying skeleton or implementation
    */
   public boolean isSkeletonWanted()
   {
      return bDeploySkeleton;
   } // isSkeletonWanted

   /**
    * Turn on/off Helper class generation
    *
    * @param value
    */
   public void setHelperWanted(boolean value)
   {
      bHelperGeneration = value;
   } // setHelperWanted

   /**
    * Indicate if we should be generating Helper classes
    */
   public boolean isHelperWanted()
   {
      return bHelperGeneration;
   } // isHelperWanted

   /**
    * Turn on/off test case creation
    *
    * @param value
    */
   public void setTestCaseWanted(boolean value)
   {
      this.bEmitTestCase = value;
   } // setTestCaseWanted

   public boolean isTestCaseWanted()
   {
      return bEmitTestCase;
   } // isTestCaseWanted

   /**
    * By default, code is generated only for referenced elements.
    * Call bGenerateAll(true) and WSDL2Java will generate code for all
    * elements in the scope regardless of whether they are
    * referenced.  Scope means:  by default, all WSDL files; if
    * generateImports(false), then only the immediate WSDL file.
    */
   public void setAllWanted(boolean all)
   {
      bGenerateAll = all;
   } // setAllWanted

   public boolean isAllWanted()
   {
      return bGenerateAll;
   } // isAllWanted

   public Namespaces getNamespaces()
   {
      return namespaces;
   } // getNamespaces

   /**
    * Set the output directory to use in emitted source files
    */
   public void setOutputDir(String outputDir)
   {
      this.outputDir = outputDir;
   }

   /**
    * Get the output directory to use for emitted source files
    */
   public String getOutputDir()
   {
      return outputDir;
   }

   /**
    * Get global package name to use instead of mapping namespaces
    */
   public String getPackageName()
   {
      return packageName;
   }

   /**
    * Set a global package name to use instead of mapping namespaces
    */
   public void setPackageName(String packageName)
   {
      this.packageName = packageName;
   }

   /**
    * Set the scope for the deploy.xml file.
    *
    * @param scope One of 'null',
    *              Scope.APPLICATION, Scope.REQUEST, Scope.SESSION.
    *              Anything else is equivalent to 'null' null and no explicit
    *              scope tag will appear in deploy.xml.
    */
   public void setScope(Scope scope)
   {
      this.scope = scope;
   } // setScope

   /**
    * Get the scope for the deploy.xml file.
    */
   public Scope getScope()
   {
      return scope;
   } // getScope

   /**
    * Set the NStoPkg mappings filename.
    */
   public void setNStoPkg(String NStoPkgFilename)
   {
      if (NStoPkgFilename != null)
      {
         this.NStoPkgFilename = NStoPkgFilename;
      }
   } // setNStoPkg


   /**
    * Set a map of namespace -> Java package names
    */
   public void setNamespaceMap(HashMap map)
   {
      delayedNamespacesMap = map;
   }

   /**
    * Get the map of namespace -> Java package names
    */
   public HashMap getNamespaceMap()
   {
      return delayedNamespacesMap;
   }

   /**
    * Sets the <code>WriterFactory Class</code> to use
    *
    * @param factory the name of the factory <code>Class</code>
    */
   public void setFactory(String factory)
   {
      try
      {
         Class clazz = ClassUtils.forName(factory);
         GeneratorFactory genFac = null;
         try
         {
            Constructor ctor =
                    clazz.getConstructor(new Class[]{getClass()});
            genFac = (GeneratorFactory)
                    ctor.newInstance(new Object[]{this});
         }
         catch (NoSuchMethodException ex)
         {
            genFac = (GeneratorFactory)clazz.newInstance();
         }
         setFactory(genFac);
      }
      catch (Exception ex)
      {
         ex.printStackTrace();
      }
   } // setFactory

   //
   // Command line switches
   //
   ///////////////////////////////////////////////////

   /**
    * Returns an object which contains of information on all generated files
    * including the class name, filename and a type string.
    *
    * @return An org.jboss.axis.wsdl.toJava.GeneratedFileInfo object
    * @see org.jboss.axis.wsdl.toJava.GeneratedFileInfo
    */
   public GeneratedFileInfo getGeneratedFileInfo()
   {
      return fileInfo;
   }

   /**
    * This method returns a list of all generated class names.
    */
   public List getGeneratedClassNames()
   {
      return fileInfo.getClassNames();
   }

   /**
    * This method returns a list of all generated file names.
    */
   public List getGeneratedFileNames()
   {
      return fileInfo.getFileNames();
   }

   /**
    * Get the Package name for the specified namespace
    */
   public String getPackage(String namespace)
   {
      return namespaces.getCreate(namespace);
   }

   /**
    * Get the Package name for the specified QName
    */
   public String getPackage(QName qName)
   {
      return getPackage(qName.getNamespaceURI());
   }

   /**
    * Convert the specified QName into a full Java Name.
    */
   public String getJavaName(QName qName)
   {

      // If this is one of our special 'collection' qnames.
      // get the element type and append []
      if (qName.getLocalPart().indexOf("[") > 0)
      {
         String localPart = qName.getLocalPart().substring(0, qName.getLocalPart().indexOf("["));
         QName eQName = new QName(qName.getNamespaceURI(), localPart);
         return getJavaName(eQName) + "[]";
      }

      // Handle the special "java" namespace for types
      if (qName.getNamespaceURI().equalsIgnoreCase("java"))
      {
         return qName.getLocalPart();
      }

      // The QName may represent a base java name, so check this first
      String fullJavaName = getFactory().getBaseTypeMapping().getBaseName(qName);
      if (fullJavaName != null)
         return fullJavaName;

      // Use the namespace uri to get the appropriate package
      String pkg = getPackage(qName.getNamespaceURI());
      if (pkg != null)
      {
         fullJavaName = pkg + "." + Utils.xmlNameToJavaClass(qName.getLocalPart());
      }
      else
      {
         fullJavaName = Utils.xmlNameToJavaClass(qName.getLocalPart());
      }
      return fullJavaName;
   } // getJavaName


   /**
    * Emit appropriate Java files for a WSDL at a given URL.
    * <p/>
    * This method will time out after the number of milliseconds specified
    * by our timeoutms member.
    */
   public void run(String wsdlURL) throws Exception
   {
      setup();
      super.run(wsdlURL);
   } // run

   /**
    * 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.
    */
   public void run(String context, Document doc) throws
           IOException, SAXException, WSDLException,
           ParserConfigurationException
   {
      setup();
      super.run(context, doc);
   } // run

   private void setup() throws IOException
   {
      if (baseTypeMapping == null)
      {
         setTypeMappingVersion(typeMappingVersion);
      }
      getFactory().setBaseTypeMapping(baseTypeMapping);

      namespaces = new Namespaces(outputDir);

      if (packageName != null)
      {
         namespaces.setDefaultPackage(packageName);
      }
      else
      {
         // First, read the namespace mapping file - configurable, by default
         // NStoPkg.properties - if it exists, and load the namespaceMap HashMap
         // with its data.
         getNStoPkgFromPropsFile(namespaces);

         if (delayedNamespacesMap != null)
         {
            namespaces.putAll(delayedNamespacesMap);
         }
      }
   } // setup


   protected void sanityCheck(SymbolTable 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);
            String namespace = entry.getQName().getNamespaceURI();
            String packageName =
                    org.jboss.axis.wsdl.toJava.Utils.makePackageName(namespace);
            String localName = entry.getQName().getLocalPart();
            if (localName.equals(packageName) &&
                    packageName.equals(namespaces.getCreate(namespace)))
            {
               packageName += "_pkg";
               namespaces.put(namespace, packageName);
            }

         }
      }
   }

   /**
    * Tries to load the namespace-to-package mapping file.
    * <ol>
    * <li>if a file name is explicitly set using <code>setNStoPkg()</code>, tries
    * to load the mapping from this file. If this fails, the built-in default
    * mapping is used.
    * <p/>
    * <li>if no file name is set, tries to load the file <code>DEFAULT_NSTOPKG_FILE</code>
    * as a java resource. If this fails, the built-in dfault mapping is used.
    * </ol>
    *
    * @param namespaces a hashmap which is filled with the namespace-to-package mapping
    *                   in this method
    * @see #setNStoPkg(String)
    * @see #DEFAULT_NSTOPKG_FILE
    * @see org.jboss.axis.utils.ClassUtils#getResourceAsStream(java.lang.Class,String)
    */
   private void getNStoPkgFromPropsFile(HashMap namespaces) throws IOException
   {

      Properties mappings = new Properties();
      if (NStoPkgFilename != null)
      {
         try
         {
            mappings.load(new FileInputStream(NStoPkgFilename));
            if (verbose)
            {
               System.out.println(Messages.getMessage("nsToPkgFileLoaded00", NStoPkgFilename));
            }
         }
         catch (Throwable t)
         {
            // loading the custom mapping file failed. We do not try
            // to load the mapping from a default mapping file.
            throw new IOException(Messages.getMessage("nsToPkgFileNotFound00", NStoPkgFilename));
         }
      }
      else
      {
         try
         {
            mappings.load(new FileInputStream(DEFAULT_NSTOPKG_FILE));
            if (verbose)
            {
               System.out.println(Messages.getMessage("nsToPkgFileLoaded00", DEFAULT_NSTOPKG_FILE));
            }
         }
         catch (Throwable t)
         {
            try
            {
               mappings.load(ClassUtils.getResourceAsStream(Emitter.class, DEFAULT_NSTOPKG_FILE));
               if (verbose)
               {
                  System.out.println(Messages.getMessage("nsToPkgDefaultFileLoaded00", DEFAULT_NSTOPKG_FILE));
               }

            }
            catch (Throwable t1)
            {
               // loading the default mapping file failed.
               // The built-in default mapping is used
               // No message is given, since this is generally what happens
            }
         }
      }

      Enumeration keys = mappings.propertyNames();
      while (keys.hasMoreElements())
      {
         String key = (String)keys.nextElement();
         namespaces.put(key, mappings.getProperty(key));
      }
   } // getNStoPkgFromPropsFile

   public void setTypeMappingVersion(String typeMappingVersion)
   {
      if (typeMappingVersion.equals("1.1"))
      {
         baseTypeMapping =
                 new BaseTypeMapping()
                 {
                    final TypeMapping defaultTM = DefaultTypeMappingImpl.getSingleton();

                    public String getBaseName(QName qNameIn)
                    {
                       javax.xml.namespace.QName qName =
                               new javax.xml.namespace.QName(qNameIn.getNamespaceURI(),
                                       qNameIn.getLocalPart());
                       Class cls = defaultTM.getClassForQName(qName);
                       if (cls == null)
                          return null;
                       else
                          return JavaUtils.getTextClassName(cls.getName());
                    }
                 };
      }
      else
      {
         baseTypeMapping =
                 new BaseTypeMapping()
                 {
                    final TypeMapping defaultTM = DefaultSOAPEncodingTypeMappingImpl.create();

                    public String getBaseName(QName qNameIn)
                    {
                       javax.xml.namespace.QName qName =
                               new javax.xml.namespace.QName(qNameIn.getNamespaceURI(),
                                       qNameIn.getLocalPart());
                       Class cls = defaultTM.getClassForQName(qName);
                       if (cls == null)
                          return null;
                       else
                          return JavaUtils.getTextClassName(cls.getName());
                    }
                 };
      }
   }

   // The remainder are deprecated methods.

   /**
    * Get the GeneratorFactory.
    *
    * @deprecated Call getFactory instead.  This doesn't return
    *             a WriterFactory, it returns a GeneratorFactory.
    */
   public GeneratorFactory getWriterFactory()
   {
      return getFactory();
   } // getWriterFactory

   /**
    * Call this method if you have a uri for the WSDL document
    *
    * @param uri wsdlURI the location of the WSDL file.
    * @deprecated Call run(uri) instead.
    */
   public void emit(String uri) throws Exception
   {
      run(uri);
   } // emit

   /**
    * 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.
    * @deprecated Call run(context, doc) instead.
    */
   public void emit(String context, Document doc)
           throws IOException, SAXException, WSDLException,
           ParserConfigurationException
   {
      run(context, doc);
   } // emit

   /**
    * Turn on/off server-side binding generation
    *
    * @param value
    * @deprecated Use setServerSide(value)
    */
   public void generateServerSide(boolean value)
   {
      setServerSide(value);
   }

   /**
    * Indicate if we should be emitting server side code and deploy/undeploy
    *
    * @deprecated Use isServerSide()
    */
   public boolean getGenerateServerSide()
   {
      return isServerSide();
   }

   /**
    * Turn on/off server skeleton deploy
    *
    * @param value
    * @deprecated Use setSkeletonWanted(value)
    */
   public void deploySkeleton(boolean value)
   {
      setSkeletonWanted(value);
   }

   /**
    * Indicate if we should be deploying skeleton or implementation
    *
    * @deprecated Use isSkeletonWanted()
    */
   public boolean getDeploySkeleton()
   {
      return isSkeletonWanted();
   }

   /**
    * Turn on/off Helper class generation
    *
    * @param value
    * @deprecated Use setHelperWanted(value)
    */
   public void setHelperGeneration(boolean value)
   {
      setHelperWanted(value);
   }

   /**
    * Indicate if we should be generating Helper classes
    *
    * @deprecated Use isHelperWanted()
    */
   public boolean getHelperGeneration()
   {
      return isHelperWanted();
   }

   /**
    * Turn on/off generation of elements from imported files.
    *
    * @param generateImports
    * @deprecated Use setImports(generateImports)
    */
   public void generateImports(boolean generateImports)
   {
      setImports(generateImports);
   } // generateImports

   /**
    * Turn on/off debug messages.
    *
    * @param value
    * @deprecated Use setDebug(value)
    */
   public void debug(boolean value)
   {
      setDebug(value);
   } // debug

   /**
    * Return the status of the debug switch.
    *
    * @deprecated Use isDebug()
    */
   public boolean getDebug()
   {
      return isDebug();
   } // getDebug

   /**
    * Turn on/off verbose messages
    *
    * @param value
    * @deprecated Use setVerbose(value)
    */
   public void verbose(boolean value)
   {
      setVerbose(value);
   }

   /**
    * Return the status of the verbose switch
    *
    * @deprecated Use isVerbose()
    */
   public boolean getVerbose()
   {
      return isVerbose();
   }

   /**
    * Turn on/off test case creation
    *
    * @param value
    * @deprecated Use setTestCaseWanted()
    */
   public void generateTestCase(boolean value)
   {
      setTestCaseWanted(value);
   }

   /**
    * @deprecated Use setAllWanted(all)
    */
   public void generateAll(boolean all)
   {
      setAllWanted(all);
   } // generateAll
}