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

import org.apache.axis.Constants;
import org.apache.axis.components.logger.LogFactory;
import org.apache.axis.constants.Style;
import org.apache.axis.constants.Use;
import org.apache.axis.utils.JavaUtils;
import org.apache.axis.utils.Messages;
import org.apache.axis.wsdl.symbolTable.*;
import org.apache.commons.logging.Log;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.wsdl.BindingInput;
import javax.wsdl.BindingOperation;
import javax.wsdl.Input;
import javax.wsdl.Message;
import javax.wsdl.Operation;
import javax.wsdl.Part;
import javax.wsdl.extensions.ExtensibilityElement;
import javax.wsdl.extensions.UnknownExtensibilityElement;
import javax.wsdl.extensions.mime.MIMEMultipartRelated;
import javax.wsdl.extensions.soap.SOAPBody;
import javax.wsdl.extensions.soap.SOAPOperation;
import javax.xml.namespace.QName;
import javax.xml.rpc.holders.BooleanHolder;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.Vector;

/**
 * Class Utils
 *
 * @version %I%, %G%
 */
public class Utils extends org.apache.axis.wsdl.symbolTable.Utils {

    /** Field log */
    protected static Log log = LogFactory.getLog(Utils.class.getName());

    /**
     * @see #holder(Parameter, Emitter)
     */
    public static String holder(TypeEntry type, Emitter emitter) {
        Parameter arg = new Parameter();
        // For other fields the default values will do.
        arg.setType(type);
        return holder(arg, emitter);
    }

    /**
     * Given a type, return the Java mapping of that type's holder.
     *
     * @param p          parameter whose holder class name we want to obtain.
     * @param emitter    the only {@link Emitter} object embodying the running
     *                   instance of WSDL2Java.
     * @return           the name of the holder class for <tt>p</tt>.
     */
    public static String holder(Parameter p, Emitter emitter) {
        String mimeType = (p.getMIMEInfo() == null)
                ? null
                : p.getMIMEInfo().getType();
        String mimeDimensions = (mimeType == null)
                ? ""
                : p.getMIMEInfo().getDimensions();

        // Add the holders that JAX-RPC forgot about - the MIME type holders.
        if (mimeType != null) {
            if (mimeType.equals("image/gif") || mimeType.equals("image/jpeg")) {
                return "org.apache.axis.holders.ImageHolder" + mimeDimensions;
            } else if (mimeType.equals("text/plain")) {
                return "javax.xml.rpc.holders.StringHolder" + mimeDimensions;
            } else if (mimeType.startsWith("multipart/")) {
                return "org.apache.axis.holders.MimeMultipartHolder"
                        + mimeDimensions;
            } else if (mimeType.startsWith("application/octetstream")
                    || mimeType.startsWith("application/octet-stream")) {
                return "org.apache.axis.holders.OctetStreamHolder"
                        + mimeDimensions;
            } else if (mimeType.equals("text/xml")
                    || mimeType.equals("application/xml")) {
                return "org.apache.axis.holders.SourceHolder" + mimeDimensions;
            } else {
                return "org.apache.axis.holders.DataHandlerHolder"
                        + mimeDimensions;
            }
        }

        TypeEntry type = p.getType();
        String typeValue = type.getName();

        // For base types that are nillable and are mapped to primitives,
        // need to switch to the corresponding wrapper types.
        if ((p.isOmittable() && p.getType().getDimensions().equals(""))
            || (p.getType() instanceof CollectionType && ((CollectionType) p.getType()).isWrapped()) 
            || p.getType().getUnderlTypeNillable()) {

            typeValue = getWrapperType(type);
        }


        // byte[] has a reserved holders.
        if (typeValue.equals("byte[]") &&  type.isBaseType()) { 

            return "javax.xml.rpc.holders.ByteArrayHolder";
        }

        // Anything else with [] gets its holder from the qname
        else if (typeValue.endsWith("[]")) {
            String name = emitter.getJavaName(type.getQName());
            String packagePrefix = "";

            // Make sure that holders for arrays of either primitive Java types
            // or their wrappers are generated at a predictable location.
            if ((type instanceof CollectionType)
                    && (type.getRefType() instanceof BaseType)) {
                String uri = type.getRefType().getQName().getNamespaceURI();

		// Capitalize the first character for primitive type
		// array holder classes
		if (TYPES.get(JavaUtils.replace(name,"[]","")) != null) {
                    name = capitalizeFirstChar(name);
		}

		// For wrapped primitive array holder classes append 'Wrapper' to name
		if (((CollectionType) type).isWrapped() && !typeValue.equals(type.getName())) {
                        name = name + "Wrapper";
	        }

                packagePrefix = emitter.getNamespaces().getCreate(uri, false);

                if (packagePrefix == null) {
                    packagePrefix = "";
                } else {
                    packagePrefix += '.';
                }
            }
            name = JavaUtils.replace(name, "java.lang.", "");
	    
            // This could be a special QName for a indexed property.
            // If so, change the [] to Array.
            name = JavaUtils.replace(name, "[]", "Array");
            name = addPackageName(name, "holders");

            return packagePrefix + name + "Holder";
        }

        // String also has a reserved holder
        else if (typeValue.equals("String")) {
            return "javax.xml.rpc.holders.StringHolder";
        } else if (typeValue.equals("java.lang.String")) {
            return "javax.xml.rpc.holders.StringHolder";
        }

        // Object also has a reserved holder
        else if (typeValue.equals("Object")) {
            return "javax.xml.rpc.holders.ObjectHolder";
        } else if (typeValue.equals("java.lang.Object")) {
            return "javax.xml.rpc.holders.ObjectHolder";
        }

        // Java primitive types have reserved holders
        else if (typeValue.equals("int") || typeValue.equals("long")
                || typeValue.equals("short") || typeValue.equals("float")
                || typeValue.equals("double") || typeValue.equals("boolean")
                || typeValue.equals("byte")) {
            return "javax.xml.rpc.holders." + capitalizeFirstChar(typeValue)
                    + "Holder";
        }

        // Java language classes have reserved holders (with ClassHolder)
        else if (typeValue.startsWith("java.lang.")) {
            return "javax.xml.rpc.holders"
                    + typeValue.substring(typeValue.lastIndexOf("."))
                    + "WrapperHolder";
        } else if (typeValue.indexOf(".") < 0) {
            return "javax.xml.rpc.holders" + typeValue + "WrapperHolder";
        }

        // The classes have reserved holders because they
        // represent schema/soap encoding primitives
        else if (typeValue.equals("java.math.BigDecimal")) {
            return "javax.xml.rpc.holders.BigDecimalHolder";
        } else if (typeValue.equals("java.math.BigInteger")) {
            return "javax.xml.rpc.holders.BigIntegerHolder";
        } else if (typeValue.equals("java.util.Date")) {
            return "org.apache.axis.holders.DateHolder";
        } else if (typeValue.equals("java.util.Calendar")) {
            return "javax.xml.rpc.holders.CalendarHolder";
        } else if (typeValue.equals("javax.xml.namespace.QName")) {
            return "javax.xml.rpc.holders.QNameHolder";
        } else if (typeValue.equals("javax.activation.DataHandler")) {
            return "org.apache.axis.holders.DataHandlerHolder";
        }

        // Check for Axis specific types and return their holders
        else if (typeValue.startsWith("org.apache.axis.types.")) {
            int i = typeValue.lastIndexOf('.');
            String t = typeValue.substring(i + 1);

            return "org.apache.axis.holders." + t + "Holder";
        }

        // For everything else add "holders" package and append
        // holder to the class name.
        else {
            return addPackageName(typeValue, "holders") + "Holder";
        }
    }    // holder

    /**
     * Add package to name
     *
     * @param className full name of the class.
     * @param newPkg    name of the package to append
     * @return String name with package name added
     */
    public static String addPackageName(String className, String newPkg) {

        int index = className.lastIndexOf(".");

        if (index >= 0) {
            return className.substring(0, index) + "." + newPkg
                    + className.substring(index);
        } else {
            return newPkg + "." + className;
        }
    }

    /**
     * Given a fault message, return the fully qualified Java class name
     * of the exception to be generated from this fault
     *
     * @param faultMessage The WSDL fault message
     * @param symbolTable  the current symbol table
     * @return A Java class name for the fault
     */
    public static String getFullExceptionName(Message faultMessage,
                                              SymbolTable symbolTable) {

        MessageEntry me = symbolTable.getMessageEntry(faultMessage.getQName());

        return (String) me.getDynamicVar(
                JavaGeneratorFactory.EXCEPTION_CLASS_NAME);
    }    // getFullExceptionName

    /**
     * Given a fault message, return the XML type of the exception data.
     *
     * @param faultMessage The WSDL fault message object
     * @param symbolTable  the current symbol table
     * @return A QName for the XML type of the data
     */
    public static QName getFaultDataType(Message faultMessage,
                                         SymbolTable symbolTable) {

        MessageEntry me = symbolTable.getMessageEntry(faultMessage.getQName());

        return (QName) me.getDynamicVar(
                JavaGeneratorFactory.EXCEPTION_DATA_TYPE);
    }    // getFaultDataType

    /**
     * Given a fault message, return TRUE if the fault is a complex type fault
     *
     * @param faultMessage The WSDL fault message object
     * @param symbolTable  the current symbol table
     * @return A Java class name for the fault
     */
    public static boolean isFaultComplex(Message faultMessage,
                                         SymbolTable symbolTable) {

        MessageEntry me = symbolTable.getMessageEntry(faultMessage.getQName());
        Boolean ret =
                (Boolean) me.getDynamicVar(JavaGeneratorFactory.COMPLEX_TYPE_FAULT);

        if (ret != null) {
            return ret.booleanValue();
        } else {
            return false;
        }
    }    // isFaultComplex

    /**
     * If the specified node represents a supported JAX-RPC enumeration,
     * a Vector is returned which contains the base type and the enumeration values.
     * The first element in the vector is the base type (an TypeEntry).
     * Subsequent elements are values (Strings).
     * If this is not an enumeration, null is returned.
     *
     * @param node
     * @param symbolTable
     * @return
     */
    public static Vector getEnumerationBaseAndValues(Node node,
                                                     SymbolTable symbolTable) {

        if (node == null) {
            return null;
        }

        // If the node kind is an element, dive into it.
        QName nodeKind = Utils.getNodeQName(node);

        if ((nodeKind != null) && nodeKind.getLocalPart().equals("element")
                && Constants.isSchemaXSD(nodeKind.getNamespaceURI())) {
            NodeList children = node.getChildNodes();
            Node simpleNode = null;

            for (int j = 0; (j < children.getLength()) && (simpleNode == null);
                 j++) {
                QName simpleKind = Utils.getNodeQName(children.item(j));

                if ((simpleKind != null)
                        && simpleKind.getLocalPart().equals("simpleType")
                        && Constants.isSchemaXSD(
                                simpleKind.getNamespaceURI())) {
                    simpleNode = children.item(j);
                    node = simpleNode;
                }
            }
        }

        // Get the node kind, expecting a schema simpleType
        nodeKind = Utils.getNodeQName(node);

        if ((nodeKind != null) && nodeKind.getLocalPart().equals("simpleType")
                && Constants.isSchemaXSD(nodeKind.getNamespaceURI())) {

            // Under the simpleType there should be a restriction.
            // (There may be other #text nodes, which we will ignore).
            NodeList children = node.getChildNodes();
            Node restrictionNode = null;

            for (int j = 0;
                 (j < children.getLength()) && (restrictionNode == null);
                 j++) {
                QName restrictionKind = Utils.getNodeQName(children.item(j));

                if ((restrictionKind != null)
                        && restrictionKind.getLocalPart().equals("restriction")
                        && Constants.isSchemaXSD(
                                restrictionKind.getNamespaceURI())) {
                    restrictionNode = children.item(j);
                }
            }

            // The restriction node indicates the type being restricted
            // (the base attribute contains this type).
            // The base type must be a simple type, and not boolean
            TypeEntry baseEType = null;

            if (restrictionNode != null) {
                QName baseType = Utils.getTypeQName(restrictionNode,
                        new BooleanHolder(), false);

                baseEType = symbolTable.getType(baseType);

                if (baseEType != null) {
                    String javaName = baseEType.getName();

                    if (javaName.equals("boolean")
                            || !SchemaUtils.isSimpleSchemaType(
                                    baseEType.getQName())) {
                        baseEType = null;
                    }
                }
            }

            // Process the enumeration elements underneath the restriction node
            if ((baseEType != null) && (restrictionNode != null)) {
                Vector v = new Vector();
                NodeList enums = restrictionNode.getChildNodes();

                for (int i = 0; i < enums.getLength(); i++) {
                    QName enumKind = Utils.getNodeQName(enums.item(i));

                    if ((enumKind != null)
                            && enumKind.getLocalPart().equals("enumeration")
                            && Constants.isSchemaXSD(
                                    enumKind.getNamespaceURI())) {

                        // Put the enum value in the vector.
                        Node enumNode = enums.item(i);
                        String value = Utils.getAttribute(enumNode, "value");

                        if (value != null) {
                            v.add(value);
                        }
                    }
                }

                // is this really an enumeration?
                if (v.isEmpty()) {
                    return null;
                }

                // The first element in the vector is the base type (an TypeEntry).
                v.add(0, baseEType);

                return v;
            }
        }

        return null;
    }

    /**
     * Capitalize the first character of the name.
     *
     * @param name
     * @return
     */
    public static String capitalizeFirstChar(String name) {

        if ((name == null) || name.equals("")) {
            return name;
        }

        char start = name.charAt(0);

        if (Character.isLowerCase(start)) {
            start = Character.toUpperCase(start);

            return start + name.substring(1);
        }

        return name;
    }    // capitalizeFirstChar

    /**
     * Prepend an underscore to the name
     *
     * @param name
     * @return
     */
    public static String addUnderscore(String name) {

        if ((name == null) || name.equals("")) {
            return name;
        }

        return "_" + name;
    }

    /**
     * Map an XML name to a valid Java identifier
     *
     * @param name
     * @return
     */
    public static String xmlNameToJava(String name) {

        // NOTE:  This method should really go away and all callers should call
        // JavaUtils.xmlNameToJava directly.  But there are a lot of them and I wanted
        // to keep the changes to a minimum.  Besides, these are static methods so the should
        // be inlined.
        return JavaUtils.xmlNameToJava(name);
    }

    /**
     * Map an XML name to a valid Java identifier w/ capitolized first letter
     *
     * @param name
     * @return
     */
    public static String xmlNameToJavaClass(String name) {
        return capitalizeFirstChar(xmlNameToJava(name));
    }

    /**
     * Method makePackageName
     *
     * @param namespace
     * @return
     */
    public static String makePackageName(String namespace) {

        String hostname = null;
        String path = "";

        // get the target namespace of the document
        try {
            URL u = new URL(namespace);

            hostname = u.getHost();
            path = u.getPath();
        } catch (MalformedURLException e) {
            if (namespace.indexOf(":") > -1) {
                hostname = namespace.substring(namespace.indexOf(":") + 1);

                if (hostname.indexOf("/") > -1) {
                    hostname = hostname.substring(0, hostname.indexOf("/"));
                }
            } else {
                hostname = namespace;
            }
        }

        // if we didn't file a hostname, bail
        if (hostname == null) {
            return null;
        }

        // convert illegal java identifier
        hostname = hostname.replace('-', '_');
        path = path.replace('-', '_');

        // chomp off last forward slash in path, if necessary
        if ((path.length() > 0) && (path.charAt(path.length() - 1) == '/')) {
            path = path.substring(0, path.length() - 1);
        }

        // tokenize the hostname and reverse it
        StringTokenizer st = new StringTokenizer(hostname, ".:");
        String[] words = new String[st.countTokens()];

        for (int i = 0; i < words.length; ++i) {
            words[i] = st.nextToken();
        }

        StringBuffer sb = new StringBuffer(namespace.length());

        for (int i = words.length - 1; i >= 0; --i) {
            addWordToPackageBuffer(sb, words[i], (i == words.length - 1));
        }

        // tokenize the path
        StringTokenizer st2 = new StringTokenizer(path, "/");

        while (st2.hasMoreTokens()) {
            addWordToPackageBuffer(sb, st2.nextToken(), false);
        }

        return sb.toString();
    }

    /**
     * Massage <tt>word</tt> into a form suitable for use in a Java package name.
     * Append it to the target string buffer with a <tt>.</tt> delimiter iff
     * <tt>word</tt> is not the first word in the package name.
     *
     * @param sb        the buffer to append to
     * @param word      the word to append
     * @param firstWord a flag indicating whether this is the first word
     */
    private static void addWordToPackageBuffer(StringBuffer sb, String word,
                                               boolean firstWord) {

        if (JavaUtils.isJavaKeyword(word)) {
            word = JavaUtils.makeNonJavaKeyword(word);
        }

        // separate with dot after the first word
        if (!firstWord) {
            sb.append('.');
        }

        // prefix digits with underscores
        if (Character.isDigit(word.charAt(0))) {
            sb.append('_');
        }

        // replace periods with underscores
        if (word.indexOf('.') != -1) {
            char[] buf = word.toCharArray();

            for (int i = 0; i < word.length(); i++) {
                if (buf[i] == '.') {
                    buf[i] = '_';
                }
            }

            word = new String(buf);
        }

        sb.append(word);
    }

    /**
     * Query Java Local Name
     *
     * @param fullName
     * @return
     */
    public static String getJavaLocalName(String fullName) {
        return fullName.substring(fullName.lastIndexOf('.') + 1);
    }    // getJavaLocalName

    /**
     * Query Java Package Name
     *
     * @param fullName
     * @return
     */
    public static String getJavaPackageName(String fullName) {

        if (fullName.lastIndexOf('.') > 0) {
            return fullName.substring(0, fullName.lastIndexOf('.'));
        } else {
            return "";
        }
    }    // getJavaPackageName

    /**
     * Does the given file already exist in the given namespace?
     *
     * @param name
     * @param namespace
     * @param namespaces
     * @return
     * @throws IOException
     */
    public static boolean fileExists(
            String name, String namespace, Namespaces namespaces)
            throws IOException {

        String packageName = namespaces.getAsDir(namespace);
        String fullName = packageName + name;

        return new File(fullName).exists();
    }    // fileExists

    /** A simple map of the primitive types and their holder objects */
    private static HashMap TYPES = new HashMap(7);

    static {
        TYPES.put("int", "java.lang.Integer");
        TYPES.put("float", "java.lang.Float");
        TYPES.put("boolean", "java.lang.Boolean");
        TYPES.put("double", "java.lang.Double");
        TYPES.put("byte", "java.lang.Byte");
        TYPES.put("short", "java.lang.Short");
        TYPES.put("long", "java.lang.Long");
    }

    /**
     * Return a string with "var" wrapped as an Object type if needed
     *
     * @param type
     * @param var
     * @return
     */
    public static String wrapPrimitiveType(TypeEntry type, String var) {

        String objType = (type == null)
                ? null
                : (String) TYPES.get(type.getName());

        if (objType != null) {
            return "new " + objType + "(" + var + ")";
        } else if ((type != null) && type.getName().equals("byte[]")
                && type.getQName().getLocalPart().equals("hexBinary")) {

            // Need to wrap byte[] in special HexBinary object to get the correct serialization
            return "new org.apache.axis.types.HexBinary(" + var + ")";
        } else {
            return var;
        }
    }    // wrapPrimitiveType

    /**
     * Return the Object variable 'var' cast to the appropriate type
     * doing the right thing for the primitive types.
     *
     * @param var
     * @return
     */
    public static String getResponseString(Parameter param,
                                           String var) {
        if (param.getType() == null) {
            return ";";
        }
        String typeName = param.getType().getName();
        MimeInfo mimeInfo = param.getMIMEInfo();

        String mimeType = (mimeInfo == null)
                ? null
                : mimeInfo.getType();
        String mimeDimensions = (mimeInfo == null)
                ? ""
                : mimeInfo.getDimensions();

        if (mimeType != null) {
            if (mimeType.equals("image/gif") || mimeType.equals("image/jpeg")) {
                return "(java.awt.Image" + mimeDimensions + ") " + var + ";";
            } else if (mimeType.equals("text/plain")) {
                return "(java.lang.String" + mimeDimensions + ") " + var + ";";
            } else if (mimeType.equals("text/xml")
                    || mimeType.equals("application/xml")) {
                return "(javax.xml.transform.Source" + mimeDimensions + ") "
                        + var + ";";
            } else if (mimeType.startsWith("multipart/")) {
                return "(javax.mail.internet.MimeMultipart" + mimeDimensions
                        + ") " + var + ";";
            } else if (mimeType.startsWith("application/octetstream")
                    || mimeType.startsWith("application/octet-stream")) {
                //the hyphenated test is new and RFC compliant; the old one was retained
                //for backwards compatibility.
                return "(org.apache.axis.attachments.OctetStream"
                        + mimeDimensions + ") " + var + ";";
            } else {
                return "(javax.activation.DataHandler" + mimeDimensions + ") "
                        + var + ";";
            }
        }

        // If minOccurs="0" and singular or array with nillable underlying
        // type get the corresponding wrapper type.
        if ((param.isOmittable() && param.getType().getDimensions().equals(""))
            || (param.getType() instanceof CollectionType
                && ((CollectionType) param.getType()).isWrapped())
            || param.getType().getUnderlTypeNillable()) { 
            
            typeName = getWrapperType(param.getType());
        }

        String objType = (String) TYPES.get(typeName);

        if (objType != null) {
            return "((" + objType + ") " + var + ")." + typeName + "Value();";
        }

        return "(" + typeName + ") " + var + ";";
    }    // getResponseString

    /**
     * Method isPrimitiveType
     *
     * @param type
     * @return
     */
    public static boolean isPrimitiveType(TypeEntry type) {
        return TYPES.get(type.getName()) != null;
    }    // isPrimitiveType

    /**
     * Return a "wrapper" type for the given type name.  In other words,
     * if it's a primitive type ("int") return the java wrapper class
     * ("java.lang.Integer").  Otherwise return the type name itself.
     *
     * @param type
     * @return the name of a java wrapper class for the type, or the type's
     *         name if it's not primitive.
     */
    public static String getWrapperType(String type) {
        String ret = (String)TYPES.get(type);
        return (ret == null) ? type : ret;
    }

    /**
     * Returns a "wrapper" type for the given TypeEntry.
     *
     * @param type
     * @return the name of a java wrapper class for the type, or the type's
     *         name if it's not a primitive.
     */
    public static String getWrapperType(TypeEntry type) {
        String    dims = type.getDimensions();
        if (!dims.equals("")) {

            TypeEntry te = type.getRefType();            
            if (te != null 
                && !te.getDimensions().equals("")) {

                return getWrapperType(te) + dims;
            }
            if (te instanceof BaseType
                ||  te instanceof DefinedElement
                    &&  te.getRefType() instanceof BaseType) {

                return getWrapperType(te) + dims;
            } 
        } 
        return  getWrapperType(type.getName());
    }

    /**
     * Return the operation QName.  The namespace is determined from
     * the soap:body namespace, if it exists, otherwise it is "".
     *
     * @param bindingOper the operation
     * @param bEntry      the symbol table binding entry
     * @param symbolTable SymbolTable
     * @return the operation QName
     */
    public static QName getOperationQName(BindingOperation bindingOper,
                                          BindingEntry bEntry,
                                          SymbolTable symbolTable) {

        Operation operation = bindingOper.getOperation();
        String operationName = operation.getName();

        // For the wrapped case, use the part element's name...which is
        // is the same as the operation name, but may have a different
        // namespace ?
        // example:
        // <part name="paramters" element="ns:myelem">
        if ((bEntry.getBindingStyle() == Style.DOCUMENT)
                && symbolTable.isWrapped()) {
            Input input = operation.getInput();

            if (input != null) {
                Map parts = input.getMessage().getParts();

                if ((parts != null) && !parts.isEmpty()) {
                    Iterator i = parts.values().iterator();
                    Part p = (Part) i.next();

                    return p.getElementName();
                }
            }
        }

        String ns = null;

        // Get a namespace from the soap:body tag, if any
        // example:
        // <soap:body namespace="this_is_what_we_want" ..>
        BindingInput bindInput = bindingOper.getBindingInput();

        if (bindInput != null) {
            Iterator it = bindInput.getExtensibilityElements().iterator();

            while (it.hasNext()) {
                ExtensibilityElement elem = (ExtensibilityElement) it.next();

                if (elem instanceof SOAPBody) {
                    SOAPBody body = (SOAPBody) elem;

                    ns = body.getNamespaceURI();
                    if (bEntry.getInputBodyType(operation) == Use.ENCODED && (ns == null || ns.length() == 0)) {
                        log.warn(Messages.getMessage("badNamespaceForOperation00",
                                bEntry.getName(),
                                operation.getName()));

                    }
                    break;
                } else if (elem instanceof MIMEMultipartRelated) {
                    Object part = null;
                    javax.wsdl.extensions.mime.MIMEMultipartRelated mpr =
                            (javax.wsdl.extensions.mime.MIMEMultipartRelated) elem;
                    List l =
                            mpr.getMIMEParts();

                    for (int j = 0;
                         (l != null) && (j < l.size()) && (part == null);
                         j++) {
                        javax.wsdl.extensions.mime.MIMEPart mp =
                                (javax.wsdl.extensions.mime.MIMEPart) l.get(j);
                        List ll =
                                mp.getExtensibilityElements();

                        for (int k = 0; (ll != null) && (k < ll.size())
                                && (part == null); k++) {
                            part = ll.get(k);

                            if (part instanceof SOAPBody) {
                                SOAPBody body = (SOAPBody) part;

                                ns = body.getNamespaceURI();
                                if (bEntry.getInputBodyType(operation) == Use.ENCODED && (ns == null || ns.length() == 0)) {
                                    log.warn(Messages.getMessage("badNamespaceForOperation00",
                                            bEntry.getName(),
                                            operation.getName()));

                                }
                                break;
                            } else {
                                part = null;
                            }
                        }
                    }
                } else if (elem instanceof UnknownExtensibilityElement) {

                    // TODO: After WSDL4J supports soap12, change this code
                    UnknownExtensibilityElement unkElement =
                            (UnknownExtensibilityElement) elem;
                    QName name =
                            unkElement.getElementType();

                    if (name.getNamespaceURI().equals(Constants.URI_WSDL12_SOAP)
                            && name.getLocalPart().equals("body")) {
                        ns = unkElement.getElement().getAttribute("namespace");
                    }
                }
            }
        }

        // If we didn't get a namespace from the soap:body, then
        // use "".  We should probably use the targetNamespace,
        // but the target namespace of what?  binding?  portType?
        // Also, we don't have enough info for to get it.
        if (ns == null) {
            ns = "";
        }

        return new QName(ns, operationName);
    }

    /**
     * Return the SOAPAction (if any) of this binding operation
     *
     * @param bindingOper the operation to look at
     * @return the SOAPAction or null if not found
     */
    public static String getOperationSOAPAction(BindingOperation bindingOper) {
        // Find the SOAPAction.
        List elems = bindingOper.getExtensibilityElements();
        Iterator it = elems.iterator();
        boolean found = false;
        String action = null;

        while (!found && it.hasNext()) {
            ExtensibilityElement elem =
                    (ExtensibilityElement) it.next();

            if (elem instanceof SOAPOperation) {
                SOAPOperation soapOp = (SOAPOperation) elem;
                action = soapOp.getSoapActionURI();
                found = true;
            } else if (elem instanceof UnknownExtensibilityElement) {

                // TODO: After WSDL4J supports soap12, change this code
                UnknownExtensibilityElement unkElement =
                        (UnknownExtensibilityElement) elem;
                QName name =
                        unkElement.getElementType();

                if (name.getNamespaceURI().equals(
                        Constants.URI_WSDL12_SOAP)
                        && name.getLocalPart().equals("operation")) {
                    action = unkElement.getElement().getAttribute(
                                    "soapAction");
                    found = true;
                }
            }
        }
        return action;
    }

    /**
     * Common code for generating a QName in emitted code.  Note that there's
     * no semicolon at the end, so we can use this in a variety of contexts.
     *
     * @param qname
     * @return
     */
    public static String getNewQName(javax.xml.namespace.QName qname) {
        return "new javax.xml.namespace.QName(\"" + qname.getNamespaceURI()
                + "\", \"" + qname.getLocalPart() + "\")";
    }

    public static String getNewQNameWithLastLocalPart(javax.xml.namespace.QName qname) {
        return "new javax.xml.namespace.QName(\"" + qname.getNamespaceURI()
                + "\", \"" + getLastLocalPart(qname.getLocalPart()) + "\")";
    }

    /**
     * Get the parameter type name.  If this is a MIME type, then
     * figure out the appropriate type from the MIME type, otherwise
     * use the name of the type itself.
     *
     * @param parm
     * @return
     */
    public static String getParameterTypeName(Parameter parm) {

        String ret;

        if (parm.getMIMEInfo() == null) {
            ret = parm.getType().getName();

            // If minOccurs="0" and singular or array with nillable underlying
            // type get the corresponding wrapper type.
            if ((parm.isOmittable() && parm.getType().getDimensions().equals(""))
                || (parm.getType() instanceof CollectionType 
                    && ((CollectionType) parm.getType()).isWrapped())
                || parm.getType().getUnderlTypeNillable()) {

                ret = getWrapperType(parm.getType());
            }
        } else {
            String mime = parm.getMIMEInfo().getType();

            ret = JavaUtils.mimeToJava(mime);

            if (ret == null) {
                ret = parm.getType().getName();
            } else {
                ret += parm.getMIMEInfo().getDimensions();
            }
        }

        return ret;
    }    // getParameterTypeName

    /**
     * Get the QName that could be used in the xsi:type
     * when serializing an object for this parameter/return
     *
     * @param param is a parameter
     * @return the QName of the parameter's xsi type
     */
    public static QName getXSIType(Parameter param) {

        if (param.getMIMEInfo() != null) {
            return getMIMETypeQName(param.getMIMEInfo().getType());
        }

        return getXSIType(param.getType());
    }    // getXSIType

    /**
     * Get the QName that could be used in the xsi:type
     * when serializing an object of the given type.
     *
     * @param te is the type entry
     * @return the QName of the type's xsi type
     */
    public static QName getXSIType(TypeEntry te) {

        QName xmlType = null;

        // If the TypeEntry describes an Element, get
        // the referenced Type.
        if ((te != null) && (te instanceof Element)
                && (te.getRefType() != null)) {
            te = te.getRefType();
        }

        // If the TypeEntry is a CollectionTE, use
        // the TypeEntry representing the component Type
        // So for example a parameter that takes a
        // collection type for
        // <element name="A" type="xsd:string" maxOccurs="unbounded"/>
        // will be
        // new ParameterDesc(<QName of A>, IN,
        // <QName of xsd:string>,
        // String[])
        if ((te != null) && (te instanceof CollectionTE)
                && (te.getRefType() != null)) {
            te = te.getRefType();
        }

        if (te != null) {
            xmlType = te.getQName();
        }

        return xmlType;
    }

    /**
     * Given a MIME type, return the AXIS-specific type QName.
     *
     * @param mimeName the MIME type name
     * @return the AXIS-specific QName for the MIME type
     */
    public static QName getMIMETypeQName(String mimeName) {

        if ("text/plain".equals(mimeName)) {
            return Constants.MIME_PLAINTEXT;
        } else if ("image/gif".equals(mimeName)
                || "image/jpeg".equals(mimeName)) {
            return Constants.MIME_IMAGE;
        } else if ("text/xml".equals(mimeName)
                || "applications/xml".equals(mimeName)) {
            return Constants.MIME_SOURCE;
        } else if ("application/octet-stream".equals(mimeName) ||
                   "application/octetstream".equals(mimeName)) {
            return Constants.MIME_OCTETSTREAM;
        } else if ((mimeName != null) && mimeName.startsWith("multipart/")) {
            return Constants.MIME_MULTIPART;
        } else {
            return Constants.MIME_DATA_HANDLER;
        }
    }    // getMIMEType

    /**
     * Are there any MIME parameters in the given binding?
     *
     * @param bEntry
     * @return
     */
    public static boolean hasMIME(BindingEntry bEntry) {

        List operations = bEntry.getBinding().getBindingOperations();

        for (int i = 0; i < operations.size(); ++i) {
            BindingOperation operation = (BindingOperation) operations.get(i);

            if (hasMIME(bEntry, operation)) {
                return true;
            }
        }

        return false;
    }    // hasMIME

    /**
     * Are there any MIME parameters in the given binding's operation?
     *
     * @param bEntry
     * @param operation
     * @return
     */
    public static boolean hasMIME(BindingEntry bEntry,
                                  BindingOperation operation) {

        Parameters parameters = bEntry.getParameters(operation.getOperation());

        if (parameters != null) {
            for (int idx = 0; idx < parameters.list.size(); ++idx) {
                Parameter p = (Parameter) parameters.list.get(idx);

                if (p.getMIMEInfo() != null) {
                    return true;
                }
            }
        }

        return false;
    }    // hasMIME

    /** Field constructorMap */
    private static HashMap constructorMap = new HashMap(50);

    /** Field constructorThrowMap */
    private static HashMap constructorThrowMap = new HashMap(50);

    static {

        // Type maps to a valid initialization value for that type
        // Type var = new Type(arg)
        // Where "Type" is the key and "new Type(arg)" is the string stored
        // Used in emitting test cases and server skeletons.
        constructorMap.put("int", "0");
        constructorMap.put("float", "0");
        constructorMap.put("boolean", "true");
        constructorMap.put("double", "0");
        constructorMap.put("byte", "(byte)0");
        constructorMap.put("short", "(short)0");
        constructorMap.put("long", "0");
        constructorMap.put("java.lang.Boolean", "new java.lang.Boolean(false)");
        constructorMap.put("java.lang.Byte", "new java.lang.Byte((byte)0)");
        constructorMap.put("java.lang.Double", "new java.lang.Double(0)");
        constructorMap.put("java.lang.Float", "new java.lang.Float(0)");
        constructorMap.put("java.lang.Integer", "new java.lang.Integer(0)");
        constructorMap.put("java.lang.Long", "new java.lang.Long(0)");
        constructorMap.put("java.lang.Short", "new java.lang.Short((short)0)");
        constructorMap.put("java.math.BigDecimal",
                "new java.math.BigDecimal(0)");
        constructorMap.put("java.math.BigInteger",
                "new java.math.BigInteger(\"0\")");
        constructorMap.put("java.lang.Object", "new java.lang.String()");
        constructorMap.put("byte[]", "new byte[0]");
        constructorMap.put("java.util.Calendar",
                "java.util.Calendar.getInstance()");
        constructorMap.put(
                "javax.xml.namespace.QName",
                "new javax.xml.namespace.QName(\"http://double-double\", \"toil-and-trouble\")");
        constructorMap.put(
                "org.apache.axis.types.NonNegativeInteger",
                "new org.apache.axis.types.NonNegativeInteger(\"0\")");
        constructorMap.put("org.apache.axis.types.PositiveInteger",
                "new org.apache.axis.types.PositiveInteger(\"1\")");
        constructorMap.put(
                "org.apache.axis.types.NonPositiveInteger",
                "new org.apache.axis.types.NonPositiveInteger(\"0\")");
        constructorMap.put("org.apache.axis.types.NegativeInteger",
                "new org.apache.axis.types.NegativeInteger(\"-1\")");

        // These constructors throw exception
        constructorThrowMap.put(
                "org.apache.axis.types.Time",
                "new org.apache.axis.types.Time(\"15:45:45.275Z\")");
        constructorThrowMap.put("org.apache.axis.types.UnsignedLong",
                "new org.apache.axis.types.UnsignedLong(0)");
        constructorThrowMap.put("org.apache.axis.types.UnsignedInt",
                "new org.apache.axis.types.UnsignedInt(0)");
        constructorThrowMap.put("org.apache.axis.types.UnsignedShort",
                "new org.apache.axis.types.UnsignedShort(0)");
        constructorThrowMap.put("org.apache.axis.types.UnsignedByte",
                "new org.apache.axis.types.UnsignedByte(0)");
        constructorThrowMap.put(
                "org.apache.axis.types.URI",
                "new org.apache.axis.types.URI(\"urn:testing\")");
        constructorThrowMap.put("org.apache.axis.types.Year",
                "new org.apache.axis.types.Year(2000)");
        constructorThrowMap.put("org.apache.axis.types.Month",
                "new org.apache.axis.types.Month(1)");
        constructorThrowMap.put("org.apache.axis.types.Day",
                "new org.apache.axis.types.Day(1)");
        constructorThrowMap.put("org.apache.axis.types.YearMonth",
                "new org.apache.axis.types.YearMonth(2000,1)");
        constructorThrowMap.put("org.apache.axis.types.MonthDay",
                "new org.apache.axis.types.MonthDay(1, 1)");
    }

    /**
     * Return a constructor for the provided Parameter
     * This string will be suitable for assignment:
     * <p/>
     * Foo var = <i>string returned</i>
     * <p/>
     * Handles basic java types (int, float, etc), wrapper types (Integer, etc)
     * and certain java.math (BigDecimal, BigInteger) types.
     * Will also handle all Axis specific types (org.apache.axis.types.*)
     * <p/>
     * Caller should expect to wrap the construction in a try/catch block
     * if bThrow is set to <i>true</i>.
     *
     * @param param       info about the parameter we need a constructor for
     * @param symbolTable used to lookup enumerations
     * @param bThrow      set to true if contructor needs try/catch block
     * @return
     */
    public static String getConstructorForParam(Parameter param,
                                                SymbolTable symbolTable,
                                                BooleanHolder bThrow) {

        String paramType = param.getType().getName();

        // For base types that are nillable and are mapped to primitives,
        // need to switch to the corresponding wrapper types.
        if ((param.isOmittable() && param.getType().getDimensions().equals(""))
            || (param.getType() instanceof CollectionType
                && ((CollectionType) param.getType()).isWrapped())
            || param.getType().getUnderlTypeNillable()) {

            paramType = getWrapperType(param.getType());
        }

        String mimeType = (param.getMIMEInfo() == null)
                ? null
                : param.getMIMEInfo().getType();
        String mimeDimensions = (param.getMIMEInfo() == null)
                ? ""
                : param.getMIMEInfo().getDimensions();
        String out = null;

        // Handle mime types
        if (mimeType != null) {
            if (mimeType.equals("image/gif") || mimeType.equals("image/jpeg")) {
                return "null";
            } else if (mimeType.equals("text/xml")
                    || mimeType.equals("application/xml")) {
                if (mimeDimensions.length() <= 0) {
                    return "new javax.xml.transform.stream.StreamSource()";
                } else {
                    return "new javax.xml.transform.stream.StreamSource[0]";
                }
            } else if (mimeType.equals("application/octet-stream")||
                       mimeType.equals("application/octetstream")) {
                if (mimeDimensions.length() <= 0) {
                    return "new org.apache.axis.attachments.OctetStream()";
                } else {
                    return "new org.apache.axis.attachments.OctetStream[0]";
                }
            } else {
                return "new " + Utils.getParameterTypeName(param) + "()";
            }
        }

        // Look up paramType in the table
        out = (String) constructorMap.get(paramType);

        if (out != null) {
            return out;
        }

        // Look up paramType in the table of constructors that can throw exceptions
        out = (String) constructorThrowMap.get(paramType);

        if (out != null) {
            bThrow.value = true;

            return out;
        }

        // Handle arrays
        if (paramType.endsWith("[]")) {
            return "new " + JavaUtils.replace(paramType, "[]", "[0]");
        }

        /** * We have some constructed type. */

        // Check for enumeration
        Vector v = Utils.getEnumerationBaseAndValues(param.getType().getNode(),
                symbolTable);

        if (v != null) {

            // This constructed type is an enumeration.  Use the first one.
            String enumeration =
                    (String) JavaEnumTypeWriter.getEnumValueIds(v).get(0);

            return paramType + "." + enumeration;
        }

        if(param.getType().getRefType()!= null){
            // Check for enumeration
            Vector v2 = Utils.getEnumerationBaseAndValues(param.getType().getRefType().getNode(),
                    symbolTable);

            if (v2 != null) {

                // This constructed type is an enumeration.  Use the first one.
                String enumeration =
                        (String) JavaEnumTypeWriter.getEnumValueIds(v2).get(0);

                return paramType + "." + enumeration;
            }
        }

        // This constructed type is a normal type, instantiate it.
        return "new " + paramType + "()";
    }

    public static boolean shouldEmit(TypeEntry type) {
        // 1) Don't register types that are base (primitive) types
        //    or attributeGroups or xs:groups.
        // If the baseType != null && getRefType() != null this
        // is a simpleType that must be registered.
        // 2) Don't register the special types for collections
        // (indexed properties) or elements
        // 3) Don't register types that are not referenced
        // or only referenced in a literal context.
        return (!(((type.getBaseType() != null) && (type.getRefType() == null))
                || (type instanceof CollectionTE)
                || (type instanceof Element) || !type.isReferenced()
                || type.isOnlyLiteralReferenced()
                || ((type.getNode() != null)
                && (isXsNode(type.getNode(), "group") ||
                        isXsNode(type.getNode(), "attributeGroup")))));
    }


    /**
    * Determines if the DOM Node represents an xs:<node>
    */
    public static boolean isXsNode (Node node, String nameName)
    {
		return (node.getLocalName().equals(nameName)
                && Constants.isSchemaXSD (node.getNamespaceURI ()));
	}


    public static QName getItemQName(TypeEntry te) {
        if (te instanceof DefinedElement) {
            te = te.getRefType();
        }
        return te.getItemQName();
    }

    public static QName getItemType(TypeEntry te) {
        if (te instanceof DefinedElement) {
            te = te.getRefType();
        }
        return te.getComponentType();
    }
}    // class Utils
