/*
 * 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.symbolTable;

import org.jboss.axis.Constants;
import org.jboss.axis.enums.Style;
import org.jboss.axis.enums.Use;
import org.jboss.axis.utils.LinkedHashMap;
import org.jboss.axis.utils.Messages;
import org.jboss.axis.utils.URLHashSet;
import org.jboss.axis.utils.XMLUtils;
import org.jboss.logging.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.wsdl.Binding;
import javax.wsdl.BindingFault;
import javax.wsdl.BindingInput;
import javax.wsdl.BindingOperation;
import javax.wsdl.BindingOutput;
import javax.wsdl.Definition;
import javax.wsdl.Fault;
import javax.wsdl.Import;
import javax.wsdl.Input;
import javax.wsdl.Message;
import javax.wsdl.Operation;
import javax.wsdl.Output;
import javax.wsdl.Part;
import javax.wsdl.Port;
import javax.wsdl.PortType;
import javax.wsdl.Service;
import javax.wsdl.WSDLException;
import javax.wsdl.extensions.UnknownExtensibilityElement;
import javax.wsdl.extensions.http.HTTPBinding;
import javax.wsdl.extensions.mime.MIMEContent;
import javax.wsdl.extensions.mime.MIMEMultipartRelated;
import javax.wsdl.extensions.mime.MIMEPart;
import javax.wsdl.extensions.soap.SOAPBinding;
import javax.wsdl.extensions.soap.SOAPBody;
import javax.wsdl.extensions.soap.SOAPFault;
import javax.wsdl.extensions.soap.SOAPHeader;
import javax.wsdl.extensions.soap.SOAPHeaderFault;
import javax.wsdl.factory.WSDLFactory;
import javax.wsdl.xml.WSDLReader;
import javax.xml.namespace.QName;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.rpc.holders.BooleanHolder;
import javax.xml.rpc.holders.IntHolder;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

/**
 * This class represents a table of all of the top-level symbols from a set of WSDL Definitions and
 * DOM Documents:  XML types; WSDL messages, portTypes, bindings, and services.
 * <p/>
 * This symbolTable contains entries of the form <key, value> where key is of type QName and value is
 * of type Vector.  The Vector's elements are all of the objects that have the given QName.  This is
 * necessary since names aren't unique among the WSDL types.  message, portType, binding, service,
 * could all have the same QName and are differentiated merely by type.  SymbolTable contains
 * type-specific getters to bypass the Vector layer:
 * public PortTypeEntry getPortTypeEntry(QName name), etc.
 */
public class SymbolTable
{
   private static final Logger log = Logger.getLogger(SymbolTable.class);

   // Should the contents of imported files be added to the symbol table?
   private boolean addImports;

   // The actual symbol table.  This symbolTable contains entries of the form
   // <key, value> where key is of type QName and value is of type Vector.  The
   // Vector's elements are all of the objects that have the given QName.  This
   // is necessary since names aren't unique among the WSDL types.  message,
   // portType, binding, service, could all have the same QName and are
   // differentiated merely by type.  SymbolTable contains type-specific
   // getters to bypass the Vector layer:
   // public PortTypeEntry getPortTypeEntry(QName name), etc.

   private HashMap symbolTable = new HashMap();

   // a map of qnames -> Elements in the symbol table
   private final Map elementTypeEntries = new HashMap();
   // an unmodifiable wrapper so that we can share the index with others, safely
   private final Map elementIndex = Collections.unmodifiableMap(elementTypeEntries);
   // a map of qnames -> Types in the symbol table
   private final Map typeTypeEntries = new HashMap();
   // an unmodifiable wrapper so that we can share the index with others, safely
   private final Map typeIndex = Collections.unmodifiableMap(typeTypeEntries);

   /**
    * cache of nodes -> base types for complexTypes.  The cache is
    * built on nodes because multiple TypeEntry objects may use the
    * same node.
    */
   protected final Map node2ExtensionBase = new HashMap(); // allow friendly access

   private boolean verbose;

   private BaseTypeMapping btm = null;

   // should we attempt to treat document/literal WSDL as "rpc-style"
   private boolean nowrap;
   // Did we encounter wraped mode WSDL
   private boolean wrapped = false;

   public static final String ANON_TOKEN = ">";

   private Definition def = null;
   private String wsdlURI = null;

   /**
    * Construct a symbol table with the given Namespaces.
    */
   public SymbolTable(BaseTypeMapping btm, boolean addImports,
                      boolean verbose, boolean nowrap)
   {
      this.btm = btm;
      this.addImports = addImports;
      this.verbose = verbose;
      this.nowrap = nowrap;
   } // ctor

   /**
    * Get the raw symbol table HashMap.
    */
   public HashMap getHashMap()
   {
      return symbolTable;
   } // getHashMap

   /**
    * Get the list of entries with the given QName.  Since symbols can share QNames, this list is
    * necessary.  This list will not contain any more than one element of any given SymTabEntry.
    */
   public Vector getSymbols(QName qname)
   {
      return (Vector)symbolTable.get(qname);
   } // get

   /**
    * Get the entry with the given QName of the given class.  If it does not exist, return null.
    */
   public SymTabEntry get(QName qname, Class cls)
   {
      Vector v = (Vector)symbolTable.get(qname);
      if (v == null)
      {
         return null;
      }
      else
      {
         for (int i = 0; i < v.size(); ++i)
         {
            SymTabEntry entry = (SymTabEntry)v.elementAt(i);
            if (cls.isInstance(entry))
            {
               return entry;
            }
         }
         return null;
      }
   } // get


   /**
    * Get the type entry for the given qname.
    *
    * @param qname
    * @param wantElementType boolean that indicates type or element (for type= or ref=)
    */
   public TypeEntry getTypeEntry(QName qname, boolean wantElementType)
   {
      if (wantElementType)
      {
         return getElement(qname);
      }
      else
         return getType(qname);
   } // getTypeEntry

   /**
    * Get the Type TypeEntry with the given QName.  If it doesn't
    * exist, return null.
    */
   public Type getType(QName qname)
   {
      return (Type)typeTypeEntries.get(qname);
   } // getType

   /**
    * Get the Element TypeEntry with the given QName.  If it doesn't
    * exist, return null.
    */
   public Element getElement(QName qname)
   {
      return (Element)elementTypeEntries.get(qname);
   } // getElement

   /**
    * Get the MessageEntry with the given QName.  If it doesn't exist, return null.
    */
   public MessageEntry getMessageEntry(QName qname)
   {
      return (MessageEntry)get(qname, MessageEntry.class);
   } // getMessageEntry

   /**
    * Get the PortTypeEntry with the given QName.  If it doesn't exist, return null.
    */
   public PortTypeEntry getPortTypeEntry(QName qname)
   {
      return (PortTypeEntry)get(qname, PortTypeEntry.class);
   } // getPortTypeEntry

   /**
    * Get the BindingEntry with the given QName.  If it doesn't exist, return null.
    */
   public BindingEntry getBindingEntry(QName qname)
   {
      return (BindingEntry)get(qname, BindingEntry.class);
   } // getBindingEntry

   /**
    * Get the ServiceEntry with the given QName.  If it doesn't exist, return null.
    */
   public ServiceEntry getServiceEntry(QName qname)
   {
      return (ServiceEntry)get(qname, ServiceEntry.class);
   } // getServiceEntry

   /**
    * Get the list of all the XML schema types in the symbol table.  In other words, all entries
    * that are instances of TypeEntry.
    *
    * @deprecated use specialized get{Element,Type}Index() methods instead
    */
   public Vector getTypes()
   {
      Vector v = new Vector();
      v.addAll(elementTypeEntries.values());
      v.addAll(typeTypeEntries.values());
      return v;
   } // getTypes

   /**
    * Return an unmodifiable map of qnames -> Elements in the symbol
    * table.
    *
    * @return an unmodifiable <code>Map</code> value
    */
   public Map getElementIndex()
   {
      return elementIndex;
   }

   /**
    * Return an unmodifiable map of qnames -> Elements in the symbol
    * table.
    *
    * @return an unmodifiable <code>Map</code> value
    */
   public Map getTypeIndex()
   {
      return typeIndex;
   }

   /**
    * Return the count of TypeEntries in the symbol table.
    *
    * @return an <code>int</code> value
    */
   public int getTypeEntryCount()
   {
      return elementTypeEntries.size() + typeTypeEntries.size();
   }

   /**
    * Get the Definition.  The definition is null until
    * populate is called.
    */
   public Definition getDefinition()
   {
      return def;
   } // getDefinition

   /**
    * Get the WSDL URI.  The WSDL URI is null until populate
    * is called, and ONLY if a WSDL URI is provided.
    */
   public String getWSDLURI()
   {
      return wsdlURI;
   } // getWSDLURI

   /**
    * Are we wrapping literal soap body elements.
    */
   public boolean isWrapped()
   {
      return wrapped;
   }

   /**
    * Turn on/off element wrapping for literal soap body's.
    */
   public void setWrapped(boolean wrapped)
   {
      this.wrapped = wrapped;
   }

   /**
    * Dump the contents of the symbol table.  For debugging purposes only.
    */
   public void dump(java.io.PrintStream out)
   {
      out.println();
      out.println(Messages.getMessage("symbolTable00"));
      out.println("-----------------------");
      Iterator it = symbolTable.values().iterator();
      while (it.hasNext())
      {
         Vector v = (Vector)it.next();
         for (int i = 0; i < v.size(); ++i)
         {
            out.println(v.elementAt(i).getClass().getName());
            out.println(v.elementAt(i));
         }
      }
      out.println("-----------------------");
   } // dump


   /**
    * Call this method if you have a uri for the WSDL document
    *
    * @param uri wsdlURI the location of the WSDL file.
    */

   public void populate(String uri)
           throws IOException, WSDLException,
           SAXException, ParserConfigurationException
   {
      populate(uri, null, null);
   } // populate

   public void populate(String uri, String username, String password)
           throws IOException, WSDLException,
           SAXException, ParserConfigurationException
   {
      if (verbose)
         System.out.println(Messages.getMessage("parsing00", uri));

      Document doc = XMLUtils.newDocument(uri, username, password);
      this.wsdlURI = uri;
      try
      {
         File f = new File(uri);
         if (f.exists())
         {
            uri = f.toURL().toString();
         }
      }
      catch (Exception e)
      {
      }
      populate(uri, doc);
   } // populate

   /**
    * 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 populate(String context, Document doc)
           throws IOException, SAXException, WSDLException,
           ParserConfigurationException
   {
      WSDLReader reader = WSDLFactory.newInstance().newWSDLReader();
      reader.setFeature("javax.wsdl.verbose", verbose);
      this.def = reader.readWSDL(context, doc);

      add(context, def, doc);
   } // populate

   /**
    * Add the given Definition and Document information to the symbol table (including imported
    * symbols), populating it with SymTabEntries for each of the top-level symbols.  When the
    * symbol table has been populated, iterate through it, setting the isReferenced flag
    * appropriately for each entry.
    */
   protected void add(String context, Definition def, Document doc)
           throws IOException, SAXException, WSDLException,
           ParserConfigurationException
   {
      URL contextURL = context == null ? null : getURL(null, context);
      populate(contextURL, def, doc, null);
      checkForUndefined();
      populateParameters();
      setReferences(def, doc);  // uses wrapped flag set in populateParameters
   } // add

   /**
    * Scan the Definition for undefined objects and throw an error.
    */
   private void checkForUndefined(Definition def, String filename) throws IOException
   {
      if (def != null)
      {
         // Bindings
         Iterator ib = def.getBindings().values().iterator();
         while (ib.hasNext())
         {
            Binding binding = (Binding)ib.next();
            if (binding.isUndefined())
            {
               if (filename == null)
               {
                  throw new IOException(Messages.getMessage("emitFailtUndefinedBinding01",
                          binding.getQName().getLocalPart()));
               }
               else
               {
                  throw new IOException(Messages.getMessage("emitFailtUndefinedBinding02",
                          binding.getQName().getLocalPart(), filename));
               }
            }
         }

         // portTypes
         Iterator ip = def.getPortTypes().values().iterator();
         while (ip.hasNext())
         {
            PortType portType = (PortType)ip.next();
            if (portType.isUndefined())
            {
               if (filename == null)
               {
                  throw new IOException(Messages.getMessage("emitFailtUndefinedPort01",
                          portType.getQName().getLocalPart()));
               }
               else
               {
                  throw new IOException(Messages.getMessage("emitFailtUndefinedPort02",
                          portType.getQName().getLocalPart(), filename));
               }
            }
         }

/* tomj: This is a bad idea, faults seem to be undefined
// RJB reply:  this MUST be done for those systems that do something with
// messages.  Perhaps we have to do an extra step for faults?  I'll leave
// this commented for now, until someone uses this generator for something
// other than WSDL2Java.
            // Messages
            Iterator i = def.getMessages().values().iterator();
            while (i.hasNext()) {
                Message message = (Message) i.next();
                if (message.isUndefined()) {
                    throw new IOException(
                            Messages.getMessage("emitFailtUndefinedMessage01",
                                    message.getQName().getLocalPart()));
                }
            }
*/
      }
   }

   /**
    * Scan the symbol table for undefined types and throw an exception.
    */
   private void checkForUndefined() throws IOException
   {
      Iterator it = symbolTable.values().iterator();
      while (it.hasNext())
      {
         Vector v = (Vector)it.next();
         for (int i = 0; i < v.size(); ++i)
         {
            SymTabEntry entry = (SymTabEntry)v.get(i);

            // Report undefined types
            if (entry instanceof UndefinedType)
            {
               QName qn = entry.getQName();

               // Special case dateTime/timeInstant that changed
               // from version to version.
               if ((qn.getLocalPart().equals("dateTime") &&
                       !qn.getNamespaceURI().equals(Constants.URI_2001_SCHEMA_XSD)) ||
                       (qn.getLocalPart().equals("timeInstant") &&
                       qn.getNamespaceURI().equals(Constants.URI_2001_SCHEMA_XSD)))
               {
                  throw new IOException(Messages.getMessage("wrongNamespace00",
                          qn.getLocalPart(),
                          qn.getNamespaceURI()));
               }

               // Check for a undefined XSD Schema Type and throw
               // an unsupported message instead of undefined
               if (SchemaUtils.isSimpleSchemaType(entry.getQName()))
               {
                  throw new IOException(Messages.getMessage("unsupportedSchemaType00",
                          qn.getLocalPart()));
               }

               // last case, its some other undefined thing
               throw new IOException(Messages.getMessage("undefined00",
                       entry.getQName().toString()));
            } // if undefined
            else if (entry instanceof UndefinedElement)
            {
               throw new IOException(Messages.getMessage("undefinedElem00",
                       entry.getQName().toString()));
            }
         }
      }
   } // checkForUndefined

   /**
    * Add the given Definition and Document information to the symbol table (including imported
    * symbols), populating it with SymTabEntries for each of the top-level symbols.
    * NOTE:  filename is used only by checkForUndefined so that it can report which WSDL file
    * has the problem.  If we're on the primary WSDL file, then we don't know the name and
    * filename will be null.  But we know the names of all imported files.
    */
   private URLHashSet importedFiles = new URLHashSet();

   private void populate(URL context, Definition def, Document doc,
                         String filename)
           throws IOException, ParserConfigurationException,
           SAXException, WSDLException
   {
      if (doc != null)
      {
         populateTypes(context, doc);

         if (addImports)
         {
            // Add the symbols from any xsd:import'ed documents.
            lookForImports(context, doc);
         }
      }
      if (def != null)
      {
         checkForUndefined(def, filename);
         if (addImports)
         {
            // Add the symbols from the wsdl:import'ed WSDL documents
            Map imports = def.getImports();
            Object[] importKeys = imports.keySet().toArray();
            for (int i = 0; i < importKeys.length; ++i)
            {
               Vector v = (Vector)imports.get(importKeys[i]);
               for (int j = 0; j < v.size(); ++j)
               {
                  Import imp = (Import)v.get(j);
                  if (!importedFiles.contains(imp.getLocationURI()))
                  {
                     importedFiles.add(imp.getLocationURI());
                     URL url = getURL(context, imp.getLocationURI());
                     populate(url, imp.getDefinition(),
                             XMLUtils.newDocument(url.toString()),
                             url.toString());
                  }
               }
            }
         }
         populateMessages(def);
         populatePortTypes(def);
         populateBindings(def);
         populateServices(def);
      }
   } // populate

   /**
    * This is essentially a call to "new URL(contextURL, spec)" with extra handling in case spec is
    * a file.
    */
   private static URL getURL(URL contextURL, String spec) throws IOException
   {
      // First, fix the slashes as windows filenames may have backslashes
      // in them, but the URL class wont do the right thing when we later
      // process this URL as the contextURL.
      String path = spec.replace('\\', '/');

      // See if we have a good URL.
      URL url = null;
      try
      {
         // first, try to treat spec as a full URL
         url = new URL(contextURL, path);

         // if we are deail with files in both cases, create a url
         // by using the directory of the context URL.
         if (contextURL != null &&
                 url.getProtocol().equals("file") &&
                 contextURL.getProtocol().equals("file"))
         {
            url = getFileURL(contextURL, path);
         }
      }
      catch (MalformedURLException me)
      {
         // try treating is as a file pathname
         url = getFileURL(contextURL, path);
      }

      // Everything is OK with this URL, although a file url constructed
      // above may not exist.  This will be caught later when the URL is
      // accessed.
      return url;
   } // getURL

   private static URL getFileURL(URL contextURL, String path)
           throws IOException
   {
      if (contextURL != null)
      {
         // get the parent directory of the contextURL, and append
         // the spec string to the end.
         String contextFileName = contextURL.getFile();
         URL parent = new File(contextFileName).getParentFile().toURL();
         if (parent != null)
         {
            return new URL(parent, path);
         }
      }
      return new URL("file", "", path);
   } // getFileURL

   /**
    * Recursively find all xsd:import'ed objects and call populate for each one.
    */
   private void lookForImports(URL context, Node node)
           throws IOException, ParserConfigurationException,
           SAXException, WSDLException
   {
      NodeList children = node.getChildNodes();
      for (int i = 0; i < children.getLength(); i++)
      {
         Node child = children.item(i);
         if ("import".equals(child.getLocalName()))
         {
            NamedNodeMap attributes = child.getAttributes();
            Node namespace = attributes.getNamedItem("namespace");
            // skip XSD import of soap encoding
            if (namespace != null &&
                    isKnownNamespace(namespace.getNodeValue()))
            {
               continue;
            }
            Node importFile = attributes.getNamedItem("schemaLocation");
            if (importFile != null)
            {
               URL url = getURL(context,
                       importFile.getNodeValue());
               if (!importedFiles.contains(url))
               {
                  importedFiles.add(url);
                  String filename = url.toString();
                  populate(url, null,
                          XMLUtils.newDocument(filename), filename);
               }
            }
         }
         lookForImports(context, child);
      }
   } // lookForImports

   /**
    * Check if this is a known namespace (soap-enc or schema xsd or schema xsi or xml)
    *
    * @param namespace
    * @return true if this is a know namespace.
    */
   public boolean isKnownNamespace(String namespace)
   {
      if (Constants.isSOAP_ENC(namespace))
         return true;
      if (Constants.isSchemaXSD(namespace))
         return true;
      if (Constants.isSchemaXSI(namespace))
         return true;
      if (namespace.equals(Constants.NS_URI_XML))
         return true;
      return false;
   }

   /**
    * Populate the symbol table with all of the Types from the Document.
    */
   public void populateTypes(URL context, Document doc)
           throws IOException, SAXException, WSDLException,
           ParserConfigurationException
   {
      addTypes(context, doc, ABOVE_SCHEMA_LEVEL);
   } // populateTypes

   /**
    * Utility method which walks the Document and creates Type objects for
    * each complexType, simpleType, or element referenced or defined.
    * <p/>
    * What goes into the symbol table?  In general, only the top-level types
    * (ie., those just below
    * the schema tag).  But base types and references can
    * appear below the top level.  So anything
    * at the top level is added to the symbol table,
    * plus non-Element types (ie, base and refd)
    * that appear deep within other types.
    */
   private static final int ABOVE_SCHEMA_LEVEL = -1;
   private static final int SCHEMA_LEVEL = 0;

   private void addTypes(URL context, Node node, int level)
           throws IOException, ParserConfigurationException,
           WSDLException, SAXException
   {
      if (node == null)
      {
         return;
      }
      // Get the kind of node (complexType, wsdl:part, etc.)
      QName nodeKind = Utils.getNodeQName(node);

      if (nodeKind != null)
      {
         String localPart = nodeKind.getLocalPart();
         boolean isXSD = Constants.isSchemaXSD(nodeKind.getNamespaceURI());
         if ((isXSD && localPart.equals("complexType") ||
                 localPart.equals("simpleType")))
         {

            // If an extension or restriction is present,
            // create a type for the reference
            Node re = SchemaUtils.getRestrictionOrExtensionNode(node);
            if (re != null &&
                    Utils.getAttribute(re, "base") != null)
            {
               createTypeFromRef(re);
            }

            // This is a definition of a complex type.
            // Create a Type.
            createTypeFromDef(node, false, false);
         }
         else if (isXSD && localPart.equals("element"))
         {
            // Create a type entry for the referenced type
            createTypeFromRef(node);

            // If an extension or restriction is present,
            // create a type for the reference
            Node re = SchemaUtils.getRestrictionOrExtensionNode(node);
            if (re != null &&
                    Utils.getAttribute(re, "base") != null)
            {
               createTypeFromRef(re);
            }

            // Create a type representing an element.  (This may
            // seem like overkill, but is necessary to support ref=
            // and element=.
            createTypeFromDef(node, true, level > SCHEMA_LEVEL);
         }
         else if (isXSD && localPart.equals("attribute"))
         {
            // Create a type entry for the referenced type
            BooleanHolder forElement = new BooleanHolder();
            QName refQName = Utils.getTypeQName(node, forElement, false);

            if (refQName != null && !forElement.value)
            {
               createTypeFromRef(node);

               // Get the symbol table entry and make sure it is a simple
               // type
               if (refQName != null)
               {
                  TypeEntry refType = getTypeEntry(refQName, false);
                  if (refType != null &&
                          refType instanceof Undefined)
                  {
                     // Don't know what the type is.
                     // It better be simple so set it as simple
                     refType.setSimpleType(true);
                  }
                  else if (refType == null ||
                          (!(refType instanceof BaseType) &&
                          !refType.isSimpleType()))
                  {
                     // Problem if not simple
                     throw new IOException(Messages.getMessage("AttrNotSimpleType01",
                             refQName.toString()));
                  }
               }
            }
         }
         else if (isXSD && localPart.equals("any"))
         {
            // Map xsd:any element to special xsd:any "type"
            if (getType(Constants.XSD_ANY) == null)
            {
               Type type = new BaseType(Constants.XSD_ANY);
               symbolTablePut(type);
            }
         }
         else if (localPart.equals("part") &&
                 Constants.isWSDL(nodeKind.getNamespaceURI()))
         {

            // This is a wsdl part.  Create an TypeEntry representing the reference
            createTypeFromRef(node);
         }
         else if (isXSD && localPart.equals("include"))
         {
            String includeName = Utils.getAttribute(node, "schemaLocation");
            if (includeName != null)
            {
               URL url = getURL(context, includeName);
               Document includeDoc = XMLUtils.newDocument(url.toString());
               // Vidyanand : Fix for Bug #15124
               org.w3c.dom.Element schemaEl = includeDoc.getDocumentElement();
               if (!schemaEl.hasAttribute("targetNamespace"))
               {
                  org.w3c.dom.Element parentSchemaEl = (org.w3c.dom.Element)node.getParentNode();
                  if (parentSchemaEl.hasAttribute("targetNamespace"))
                  {
                     // we need to set two things in here
                     // 1. targetNamespace
                     // 2. setup the xmlns=<targetNamespace> attribute
                     String tns = parentSchemaEl.getAttribute("targetNamespace");
                     schemaEl.setAttribute("targetNamespace", tns);
                     schemaEl.setAttribute("xmlns", tns);
                  }
               }
               populate(url, null, includeDoc, url.toString());
            }
         }
      }

      if (level == ABOVE_SCHEMA_LEVEL)
      {
         if (nodeKind != null && nodeKind.getLocalPart().equals("schema"))
         {
            level = SCHEMA_LEVEL;
         }
      }
      else
      {
         ++level;
      }

      // Recurse through children nodes
      NodeList children = node.getChildNodes();
      for (int i = 0; i < children.getLength(); i++)
      {
         addTypes(context, children.item(i), level);
      }
   } // addTypes

   /**
    * Create a TypeEntry from the indicated node, which defines a type
    * that represents a complexType, simpleType or element (for ref=).
    */
   private void createTypeFromDef(Node node, boolean isElement,
                                  boolean belowSchemaLevel) throws IOException
   {
      // Get the QName of the node's name attribute value
      QName qName = Utils.getNodeNameQName(node);
      if (qName != null)
      {

         // If the qname is already registered as a base type,
         // don't create a defining type/element.
         if (!isElement && btm.getBaseName(qName) != null)
         {
            return;
         }

         // If the node has a type or ref attribute, get the
         // qname representing the type
         BooleanHolder forElement = new BooleanHolder();
         QName refQName = Utils.getTypeQName(node, forElement, false);

         if (refQName != null)
         {
            // Error check - bug 12362
            if (qName.getLocalPart().length() == 0)
            {
               String name = Utils.getAttribute(node, "name");
               if (name == null)
               {
                  name = "unknown";
               }
               throw new IOException(Messages.getMessage("emptyref00", name));
            }

            // Now get the TypeEntry
            TypeEntry refType = getTypeEntry(refQName, forElement.value);
            if (!belowSchemaLevel)
            {
               if (refType == null)
               {
                  throw new IOException(Messages.getMessage("absentRef00", refQName.toString(), qName.toString()));
               }
               symbolTablePut(new DefinedElement(qName, refType, node, ""));
            }
         }
         else
         {
            // Flow to here indicates no type= or ref= attribute.

            // See if this is an array or simple type definition.
            IntHolder numDims = new IntHolder();
            numDims.value = 0;
            QName arrayEQName = SchemaUtils.getArrayComponentQName(node, numDims);

            if (arrayEQName != null)
            {
               // Get the TypeEntry for the array element type
               refQName = arrayEQName;
               TypeEntry refType = getTypeEntry(refQName, false);
               if (refType == null)
               {
                  // Not defined yet, add one
                  String baseName = btm.getBaseName(refQName);
                  if (baseName != null)
                     refType = new BaseType(refQName);
                  else
                     refType = new UndefinedType(refQName);
                  symbolTablePut(refType);
               }

               // Create a defined type or element that references refType
               String dims = "";
               while (numDims.value > 0)
               {
                  dims += "[]";
                  numDims.value--;
               }

               TypeEntry defType = null;
               if (isElement)
               {
                  if (!belowSchemaLevel)
                  {
                     defType = new DefinedElement(qName, refType, node, dims);
                  }
               }
               else
               {
                  defType = new DefinedType(qName, refType, node, dims);
               }
               if (defType != null)
               {
                  symbolTablePut(defType);
               }
            }
            else
            {

               // Create a TypeEntry representing this  type/element
               String baseName = btm.getBaseName(qName);
               if (baseName != null)
               {
                  symbolTablePut(new BaseType(qName));
               }
               else
               {

                  // Create a type entry, set whether it should
                  // be mapped as a simple type, and put it in the
                  // symbol table.
                  TypeEntry te = null;
                  if (!isElement)
                  {
                     te = new DefinedType(qName, node);

                     // check if we are an anonymous type underneath
                     // an element.  If so, we point the refType of the
                     // element to us (the real type).
                     if (qName.getLocalPart().indexOf(ANON_TOKEN) >= 0)
                     {
                        Node parent = node.getParentNode();
                        QName parentQName = Utils.getNodeNameQName(parent);
                        TypeEntry parentType = getElement(parentQName);
                        if (parentType != null)
                        {
                           parentType.setRefType(te);
                        }
                     }

                  }
                  else
                  {
                     if (!belowSchemaLevel)
                     {
                        te = new DefinedElement(qName, node);
                     }
                  }
                  if (te != null)
                  {
                     if (SchemaUtils.isSimpleTypeOrSimpleContent(node))
                     {
                        te.setSimpleType(true);
                     }
                     symbolTablePut(te);
                  }
               }
            }
         }
      }
   } // createTypeFromDef

   /**
    * Node may contain a reference (via type=, ref=, or element= attributes) to
    * another type.  Create a Type object representing this referenced type.
    */
   private void createTypeFromRef(Node node) throws IOException
   {
      // Get the QName of the node's type attribute value
      BooleanHolder forElement = new BooleanHolder();
      QName qName = Utils.getTypeQName(node, forElement, false);
      if (qName != null)
      {
         // Error check - bug 12362
         if (qName.getLocalPart().length() == 0)
         {
            String name = Utils.getAttribute(node, "name");
            if (name == null)
            {
               name = "unknown";
            }
            throw new IOException(Messages.getMessage("emptyref00", name));
         }

         // Get Type or Element depending on whether type attr was used.
         TypeEntry type = getTypeEntry(qName, forElement.value);

         // A symbol table entry is created if the TypeEntry is not found
         if (type == null)
         {
            // See if this is a special QName for collections
            if (qName.getLocalPart().indexOf("[") > 0)
            {
               QName containedQName = Utils.getTypeQName(node, forElement, true);
               TypeEntry containedTE = getTypeEntry(containedQName, forElement.value);
               if (!forElement.value)
               {
                  // Case of type and maxOccurs
                  if (containedTE == null)
                  {
                     // Collection Element Type not defined yet, add one.
                     String baseName = btm.getBaseName(containedQName);
                     if (baseName != null)
                     {
                        containedTE = new BaseType(containedQName);
                     }
                     else
                     {
                        containedTE = new UndefinedType(containedQName);
                     }
                     symbolTablePut(containedTE);
                  }
                  symbolTablePut(new CollectionType(qName, containedTE, node, "[]"));
               }
               else
               {
                  // Case of ref and maxOccurs
                  if (containedTE == null)
                  {
                     containedTE = new UndefinedElement(containedQName);
                     symbolTablePut(containedTE);
                  }
                  symbolTablePut(new CollectionElement(qName, containedTE, node, "[]"));
               }
            }
            else
            {
               // Add a BaseType or Undefined Type/Element
               String baseName = btm.getBaseName(qName);
               if (baseName != null)
                  symbolTablePut(new BaseType(qName));
               else if (forElement.value == false)
                  symbolTablePut(new UndefinedType(qName));
               else
                  symbolTablePut(new UndefinedElement(qName));
            }
         }
      }
   } // createTypeFromRef

   /**
    * Populate the symbol table with all of the MessageEntry's from the Definition.
    */
   private void populateMessages(Definition def) throws IOException
   {
      Iterator i = def.getMessages().values().iterator();
      while (i.hasNext())
      {
         Message message = (Message)i.next();
         MessageEntry mEntry = new MessageEntry(message);
         symbolTablePut(mEntry);
      }
   } // populateMessages

   /**
    * ensures that a message in a <code>&lt;input&gt;</code>, <code>&lt;output&gt;</code>,
    * or <code>&lt;fault&gt;</fault> element in an <code>&lt;operation&gt;</code>
    * element is valid. In particular, ensures that
    * <ol>
    * <li>an attribute <code>message</code> is present (according to the
    * XML Schema for WSDL 1.1 <code>message</code> is <strong>required</strong>
    * <p/>
    * <li>the value of attribute <code>message</code> (a QName) refers to
    * an already defined message
    * </ol>
    * <p/>
    * <strong>Note</strong>: this method should throw a <code>javax.wsdl.WSDLException</code> rather than
    * a <code>java.io.IOException</code>
    *
    * @param message the message object
    * @throws IOException thrown, if the message is not valid
    */
   protected void ensureOperationMessageValid(Message message) throws IOException
   {

      // make sure the message is not null (i.e. there is an
      // attribute 'message ')
      //
      if (message == null)
      {
         throw new IOException("<input>,<output>, or <fault> in <operation ..> without attribute 'message' found. Attribute 'message' is required.");
      }

      // make sure the value of the attribute refers to an
      // already defined message
      //
      if (message.isUndefined())
      {
         throw new IOException("<input ..>, <output ..> or <fault ..> in <portType> with undefined message found. message name is '"
                 + message.getQName().toString()
                 + "'");
      }
   }


   /**
    * ensures that an an element <code>&lt;operation&gt;</code> within
    * an element <code>&lt;portType&gt;<code> is valid. Throws an exception
    * if the operation is not valid.
    * <p/>
    * <strong>Note</strong>: this method should throw a <code>javax.wsdl.WSDLException</code>
    * rather than a <code>java.io.IOException</code>
    *
    * @param operation the operation element
    * @throws IOException              thrown, if the element is not valid.
    * @throws IllegalArgumentException thrown, if operation is null
    */
   protected void ensureOperationValid(Operation operation) throws IOException
   {

      if (operation == null)
      {
         throw new IllegalArgumentException("parameter 'operation' must not be null");
      }

      Input input = operation.getInput();
      if (input != null)
      {
         ensureOperationMessageValid(input.getMessage());
      }

      Output output = operation.getOutput();
      if (output != null)
      {
         ensureOperationMessageValid(output.getMessage());
      }

      Map faults = operation.getFaults();
      if (faults != null)
      {
         Iterator it = faults.values().iterator();
         while (it.hasNext())
         {
            ensureOperationMessageValid(((Fault)it.next()).getMessage());
         }
      }
   }

   /**
    * ensures that an an element <code>&lt;portType&gt;</code>
    * is valid. Throws an exception if the portType is not valid.
    * <p/>
    * <strong>Note</strong>: this method should throw a <code>javax.wsdl.WSDLException</code>
    * rather than a <code>java.io.IOException</code>
    *
    * @param portType the portType element
    * @throws IOException              thrown, if the element is not valid.
    * @throws IllegalArgumentException thrown, if operation is null
    */

   protected void ensureOperationsOfPortTypeValid(PortType portType) throws IOException
   {
      if (portType == null)
         throw new IllegalArgumentException("parameter 'portType' must not be null");

      List operations = portType.getOperations();

      // no operations defined ? -> valid according to the WSDL 1.1 schema
      //
      if (operations == null || operations.size() == 0) return;

      // check operations defined in this portType
      //
      Iterator it = operations.iterator();
      while (it.hasNext())
      {
         Operation operation = (Operation)it.next();
         ensureOperationValid(operation);
      }
   }

   /**
    * Populate the symbol table with all of the PortTypeEntry's from the Definition.
    */
   private void populatePortTypes(Definition def) throws IOException
   {
      Iterator i = def.getPortTypes().values().iterator();
      while (i.hasNext())
      {
         PortType portType = (PortType)i.next();


         // 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 with it.
         if (!portType.isUndefined())
         {
            ensureOperationsOfPortTypeValid(portType);
            PortTypeEntry ptEntry = new PortTypeEntry(portType);
            symbolTablePut(ptEntry);
         }
      }
   } // populatePortTypes


   /**
    * Create the parameters and store them in the bindingEntry.
    */
   private void populateParameters() throws IOException
   {
      Iterator it = symbolTable.values().iterator();
      while (it.hasNext())
      {
         Vector v = (Vector)it.next();
         for (int i = 0; i < v.size(); ++i)
         {
            if (v.get(i) instanceof BindingEntry)
            {
               BindingEntry bEntry = (BindingEntry)v.get(i);
               // Skip non-soap bindings
               if (bEntry.getBindingType() != BindingEntry.TYPE_SOAP)
                  continue;

               Binding binding = bEntry.getBinding();
               Collection bindOperations = bEntry.getOperations();
               PortType portType = binding.getPortType();

               LinkedHashMap parameters = new LinkedHashMap();
               Iterator operations = portType.getOperations().iterator();

               // get parameters
               while (operations.hasNext())
               {
                  Operation operation = (Operation)operations.next();

                  // See if the PortType operation has a corresponding
                  // Binding operation and report an error if it doesn't.
                  if (!bindOperations.contains(operation))
                  {
                     throw  new IOException(Messages.getMessage("emitFailNoMatchingBindOperation01",
                             operation.getName(),
                             portType.getQName().getLocalPart()));
                  }

                  String namespace = portType.getQName().getNamespaceURI();
                  Parameters parms = getOperationParameters(operation,
                          namespace,
                          bEntry);
                  parameters.put(operation, parms);
               }
               bEntry.setParameters(parameters);
            }
         }
      }
   } // populateParameters

   /**
    * For the given operation, this method returns the parameter info conveniently collated.
    * There is a bit of processing that is needed to write the interface, stub, and skeleton.
    * Rather than do that processing 3 times, it is done once, here, and stored in the
    * Parameters object.
    */
   public Parameters getOperationParameters(Operation operation,
                                            String namespace,
                                            BindingEntry bindingEntry) throws IOException
   {
      Parameters parameters = new Parameters();

      // The input and output Vectors of Parameters
      Vector inputs = new Vector();
      Vector outputs = new Vector();

      List parameterOrder = operation.getParameterOrdering();

      // Handle parameterOrder="", which is techinically illegal
      if (parameterOrder != null && parameterOrder.isEmpty())
      {
         parameterOrder = null;
      }

      // All input parts MUST be in the parameterOrder list.  It is an error otherwise.
      if (parameterOrder != null)
      {
         Input input = operation.getInput();
         if (input != null)
         {
            Message inputMsg = input.getMessage();
            Map allInputs = inputMsg.getParts();
            Collection orderedInputs = inputMsg.getOrderedParts(parameterOrder);
            if (allInputs.size() != orderedInputs.size())
            {
               throw new IOException(Messages.getMessage("emitFail00", operation.getName()));
            }
         }
      }

      boolean literalInput = false;
      boolean literalOutput = false;
      if (bindingEntry != null)
      {
         literalInput = (bindingEntry.getInputBodyType(operation) == Use.LITERAL);
         literalOutput = (bindingEntry.getOutputBodyType(operation) == Use.LITERAL);
      }

      // Collect all the input parameters
      Input input = operation.getInput();
      if (input != null && input.getMessage() != null)
      {
         getParametersFromParts(inputs,
                 input.getMessage().getOrderedParts(null),
                 literalInput,
                 operation.getName(),
                 bindingEntry);
      }

      // Collect all the output parameters
      Output output = operation.getOutput();
      if (output != null && output.getMessage() != null)
      {
         getParametersFromParts(outputs,
                 output.getMessage().getOrderedParts(null),
                 literalOutput,
                 operation.getName(),
                 bindingEntry);
      }

      if (parameterOrder != null)
      {
         // Construct a list of the parameters in the parameterOrder list, determining the
         // mode of each parameter and preserving the parameterOrder list.
         for (int i = 0; i < parameterOrder.size(); ++i)
         {
            String name = (String)parameterOrder.get(i);

            // index in the inputs Vector of the given name, -1 if it doesn't exist.
            int index = getPartIndex(name, inputs);

            // index in the outputs Vector of the given name, -1 if it doesn't exist.
            int outdex = getPartIndex(name, outputs);

            if (index >= 0)
            {
               // The mode of this parameter is either in or inout
               addInishParm(inputs, outputs, index, outdex, parameters, true);
            }
            else if (outdex >= 0)
            {
               addOutParm(outputs, outdex, parameters, true);
            }
            else
            {
               System.err.println(Messages.getMessage("noPart00", name));
            }
         }
      }

      // Some special case logic for JAX-RPC, but also to make things
      // nicer for the user.
      // If we have a single input and output with the same name
      //   instead of: void echo(StringHolder inout)
      //   Do this:  string echo(string in)
      if (wrapped && inputs.size() == 1 && outputs.size() == 1 &&
              ((Parameter)inputs.get(0)).getName().equals(((Parameter)outputs.get(0)).getName()))
      {
         // add the input and make sure its a IN not an INOUT
         addInishParm(inputs, null, 0, -1, parameters, false);
      }
      else
      {
         // Get the mode info about those parts that aren't in the
         // parameterOrder list. Since they're not in the parameterOrder list,
         // the order is, first all in (and inout) parameters, then all out
         // parameters, in the order they appear in the messages.
         for (int i = 0; i < inputs.size(); i++)
         {
            Parameter p = (Parameter)inputs.get(i);
            int outdex = getPartIndex(p.getName(), outputs);
            addInishParm(inputs, outputs, i, outdex, parameters, false);
         }
      }

      // Now that the remaining in and inout parameters are collected,
      // determine the status of outputs.  If there is only 1, then it
      // is the return value.  If there are more than 1, then they are
      // out parameters.
      if (outputs.size() == 1)
      {
         parameters.returnParam = (Parameter)outputs.get(0);
         parameters.returnParam.setMode(Parameter.OUT);
         if (parameters.returnParam.getType() instanceof DefinedElement)
         {
            parameters.returnParam.setQName(parameters.returnParam.getType()
                    .getQName());
         }
         ++parameters.outputs;
      }
      else
      {
         for (int i = 0; i < outputs.size(); i++)
         {
            addOutParm(outputs, i, parameters, false);
         }
      }
      parameters.faults = operation.getFaults();

      // before we return the paramters,
      // make sure we dont have a duplicate name
      Vector used = new Vector(parameters.list.size());
      Iterator i = parameters.list.iterator();
      while (i.hasNext())
      {
         Parameter parameter = (Parameter)i.next();
         int count = 2;
         while (used.contains(parameter.getName()))
         {
            // duplicate, add a suffix and try again
            parameter.setName(parameter.getName() + Integer.toString(count++));
         }
         used.add(parameter.getName());
      }

      return parameters;
   } // parameters

   // True if the paramter is already in the given list
   private boolean isInParameterList(Vector params, String partName)
   {
      for (int i = 0; i < params.size(); i++)
      {
         Parameter parameter = (Parameter)params.get(i);
         if (partName.equals(parameter.getName()))
            return true;
      }
      return false;
   }

   /**
    * Return the index of the given name in the given Vector, -1 if it doesn't exist.
    */
   private int getPartIndex(String name, Vector v)
   {
      for (int i = 0; i < v.size(); i++)
      {
         if (name.equals(((Parameter)v.get(i)).getName()))
         {
            return i;
         }
      }
      return -1;
   } // getPartIndex

   /**
    * Add an in or inout parameter to the parameters object.
    */
   private void addInishParm(Vector inputs,
                             Vector outputs,
                             int index,
                             int outdex,
                             Parameters parameters,
                             boolean trimInput)
   {
      Parameter p = (Parameter)inputs.get(index);
      // If this is an element, we want the XML to reflect the element name
      // not the part name.  Same check is made in addOutParam below.
      if (p.getType() instanceof DefinedElement)
      {
         DefinedElement de = (DefinedElement)p.getType();
         p.setQName(de.getQName());
      }
      // If this is a collection we want the XML to reflect the type in
      // the collection, not foo[unbounded].
      // Same check is made in addOutParam below.
      if (p.getType() instanceof CollectionElement)
      {
         p.setQName(p.getType().getRefType().getQName());
      }

      // Should we remove the given parameter type/name entries from the Vector?
      if (trimInput)
      {
         inputs.remove(index);
      }

      // At this point we know the name and type of the parameter, and that it's at least an
      // in parameter.  Now check to see whether it's also in the outputs Vector.  If it is,
      // then it's an inout parameter.
      if (outdex >= 0)
      {
         Parameter outParam = (Parameter)outputs.get(outdex);
         if (p.getType().equals(outParam.getType()))
         {
            outputs.remove(outdex);
            p.setMode(Parameter.INOUT);
            ++parameters.inouts;
         }
         else
         {
            // If we're here, we have both an input and an output
            // part with the same name but different types.... guess
            // it's not really an inout....
            //
            //throw new IOException(Messages.getMessage("differentTypes00",
            //     new String[] { p.getName(),
            //                    p.getType().getQName().toString(),
            //                   outParam.getType().getQName().toString()
            //                  }
            //));

            // There is some controversy about this, and the specs are
            // a bit vague about what should happen if the types don't
            // agree.  Throwing an error is not correct with document/lit
            // operations, as part names get resused (i.e. "body").
            // See WSDL 1.1 section 2.4.6,
            //     WSDL 1.2 working draft 9 July 2002 section 2.3.1
            ++parameters.inputs;
         }
      }
      else
      {
         ++parameters.inputs;
      }

      parameters.list.add(p);
   } // addInishParm

   /**
    * Add an output parameter to the parameters object.
    */
   private void addOutParm(Vector outputs,
                           int outdex,
                           Parameters parameters,
                           boolean trim)
   {
      Parameter p = (Parameter)outputs.get(outdex);

      // If this is an element, we want the XML to reflect the element name
      // not the part name.  Same check is made in addInishParam above.
      if (p.getType() instanceof DefinedElement)
      {
         DefinedElement de = (DefinedElement)p.getType();
         p.setQName(de.getQName());
      }
      // If this is a collection we want the XML to reflect the type in
      // the collection, not foo[unbounded].
      // Same check is made in addInishParam above.
      if (p.getType() instanceof CollectionElement)
      {
         p.setQName(p.getType().getRefType().getQName());
      }

      if (trim)
      {
         outputs.remove(outdex);
      }

      p.setMode(Parameter.OUT);
      ++parameters.outputs;

      parameters.list.add(p);
   } // addOutParm

   /**
    * This method returns a vector containing Parameters which represent
    * each Part (shouldn't we call these "Parts" or something?)
    */
   public void getParametersFromParts(Vector v,
                                      Collection parts,
                                      boolean literal,
                                      String opName,
                                      BindingEntry bindingEntry)
           throws IOException
   {

      // HACK ALERT!  This whole method is waaaay too complex.
      // It needs rewriting (for instance, we sometimes new up
      // a Parameter, then ignore it in favor of another we new up.)

      // Determine if there's only one element.  For wrapped
      // style, we normally only have 1 part which is an
      // element.  But with MIME we could have any number of
      // types along with that single element.  As long as
      // there's only ONE element, and it's the same name as
      // the operation, we can unwrap it.
      int numberOfElements = 0;
      boolean possiblyWrapped = false;
      Iterator i = parts.iterator();
      while (i.hasNext())
      {
         Part part = (Part)i.next();
         if (part.getElementName() != null)
         {
            ++numberOfElements;
            if (part.getElementName().getLocalPart().equals(opName))
            {
               possiblyWrapped = true;
            }
         }
      }

      // Hack alert - Try to sense "wrapped" document literal mode
      // if we haven't been told not to.
      // Criteria:
      //  - If there is a single element part,
      //  - That part is an element
      //  - That element has the same name as the operation
      //  - That element has no attributes (check done below)
      if (!nowrap &&
              literal &&
              numberOfElements == 1 &&
              possiblyWrapped)
      {
         wrapped = true;
      }

      i = parts.iterator();
      while (i.hasNext())
      {
         Parameter param = new Parameter();
         Part part = (Part)i.next();
         QName elementName = part.getElementName();
         QName typeName = part.getTypeName();
         String partName = part.getName();

         // We're either:
         // 1. encoded
         // 2. literal & not wrapped.
         if (!literal || !wrapped || elementName == null)
         {

            param.setName(partName);

            // Add this type or element name
            if (typeName != null)
            {
               param.setType(getType(typeName));
            }
            else if (elementName != null)
            {
               // Just an FYI: The WSDL spec says that for use=encoded
               // that parts reference an abstract type using the type attr
               // but we kinda do the right thing here, so let it go.
               // if (!literal)
               //   error...
               param.setType(getElement(elementName));
            }
            else
            {
               // no type or element
               throw new IOException(Messages.getMessage("no