/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.axis2.jaxws.runtime.description.marshal.impl;

import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.java.security.AccessController;
import org.apache.axis2.jaxws.ExceptionFactory;
import org.apache.axis2.jaxws.description.EndpointDescription;
import org.apache.axis2.jaxws.description.EndpointDescriptionJava;
import org.apache.axis2.jaxws.description.EndpointInterfaceDescription;
import org.apache.axis2.jaxws.description.FaultDescription;
import org.apache.axis2.jaxws.description.OperationDescription;
import org.apache.axis2.jaxws.description.ParameterDescription;
import org.apache.axis2.jaxws.description.ServiceDescription;
import org.apache.axis2.jaxws.description.ServiceDescriptionWSDL;
import org.apache.axis2.jaxws.runtime.description.marshal.AnnotationDesc;
import org.apache.axis2.jaxws.runtime.description.marshal.FaultBeanDesc;
import org.apache.axis2.jaxws.runtime.description.marshal.MarshalServiceRuntimeDescription;
import org.apache.axis2.jaxws.util.WSDL4JWrapper;
import org.apache.axis2.jaxws.util.WSDLWrapper;
import org.apache.axis2.jaxws.utility.ClassUtils;
import org.apache.axis2.jaxws.utility.JavaUtils;
import org.apache.axis2.jaxws.wsdl.SchemaReader;
import org.apache.axis2.jaxws.wsdl.SchemaReaderException;
import org.apache.axis2.jaxws.wsdl.impl.SchemaReaderImpl;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import javax.jws.WebService;
import javax.wsdl.Definition;
import javax.wsdl.WSDLException;
import javax.xml.bind.JAXBElement;
import javax.xml.ws.Holder;
import javax.xml.ws.Response;

import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Future;

/**
 * In order to marshal or unmarshal the user data, we need to know the set of packages involved.
 * The set of packages is used to construct an appropriate JAXBContext object during the
 * marshalling/unmarshalling.
 * <p/>
 * There are two ways to get this data.
 * <p/>
 * Schema Walk (preferred):  Get the list of packages by walking the schemas that are referenced by
 * the wsdl (or generated wsdl).  Each schema represents a different package.  The package is
 * obtained using the jaxb customization or JAXB default ns<->package rule.
 * <p/>
 * Annotation Walk(secondary) : Walk the list of Endpoints, Operations, Parameters, etc. and build a
 * list of packages by looking at the classes involved.
 * <p/>
 * The Schema Walk is faster and more complete, but relies on the presence of the schema or wsdl.
 * <p/>
 * The Annotation Walk is slower and is not complete.  For example, the annotation walk may not
 * discover the packages for derived types that are defined in a different schema than the formal
 * parameter types.
 */
public class PackageSetBuilder {

    private static Log log = LogFactory.getLog(PackageSetBuilder.class);

    /** This is a static utility class.  The constructor is intentionally private */
    private PackageSetBuilder() {
    }

    /**
     * Walks the schemas of the serviceDesc's wsdl (or generated wsdl) to determine the list of
     * packages. This is the preferred algorithm for discovering the package set.
     *
     * @param serviceDesc ServiceDescription
     * @return Set of Packages
     */
    public static TreeSet<String> getPackagesFromSchema(ServiceDescription serviceDesc) {
    	
    	if (log.isDebugEnabled()) {
    		log.debug("start getPackagesFromSchema");
    		log.debug("ServiceDescription = " + serviceDesc.toString());
    	}

        TreeSet<String> set = new TreeSet<String>();
        //If we are on client side we will get wsdl definition from ServiceDescription. If we are on server side we will have to
        //read wsdlLocation from @WebService Annotation.
        ServiceDescriptionWSDL sdw = (ServiceDescriptionWSDL)serviceDesc;
        Definition wsdlDefinition = sdw.getWSDLDefinition();
        Collection<EndpointDescription> endpointDescs = serviceDesc.getEndpointDescriptions_AsCollection();
        if (endpointDescs != null) {
            for (EndpointDescription ed:endpointDescs) {

                if (wsdlDefinition == null) {
                    // TODO I don't think we should be trying to load the wsdlDefinition here.

                    //Let see if we can get wsdl definition from endpoint @WebService annotation.
                    if (ed instanceof EndpointDescriptionJava) {
                        String wsdlLocation =
                                ((EndpointDescriptionJava)ed).getAnnoWebServiceWSDLLocation();
                        wsdlDefinition = getWSDLDefinition(wsdlLocation);
                    }
                }
                //So at this point either we got wsdl definition from ServiceDescription (which means we are running this code
                //on client side) or we got it from the @WebService annotation (which means we are running this code on server side)
                if (wsdlDefinition != null) {
                    SchemaReader sr = new SchemaReaderImpl();
                    try {
                        Set<String> pkgSet = sr.readPackagesFromSchema(wsdlDefinition);
                        set.addAll(pkgSet);
                    } catch (SchemaReaderException e) {
                        throw ExceptionFactory.makeWebServiceException(e);
                    }
                }
            }
        }
        if (log.isDebugEnabled()) {
        	log.debug("end getPackagesFromSchema");
        }
        return set;
    }

    /**
     * @param serviceDescription ServiceDescription
     * @return Set of Packages
     */
    public static TreeSet<String> getPackagesFromAnnotations(ServiceDescription serviceDesc,
                                                             MarshalServiceRuntimeDescription msrd) {
    	if (log.isDebugEnabled()) {
    		log.debug("start getPackagesFromAnnotations");
    		log.debug("ServiceDescription = " + serviceDesc.toString());
    		log.debug("MarshalServiceRuntimeDescription = " + msrd.toString());
    	}
        TreeSet<String> set = new TreeSet<String>();
        Collection<EndpointDescription> endpointDescs = serviceDesc.getEndpointDescriptions_AsCollection();
        
        // Build a set of packages from all of the endpoints
        if (endpointDescs != null) {
            for (EndpointDescription endpointDesc: endpointDescs) {
                set.addAll(getPackagesFromAnnotations(endpointDesc, msrd));
            }
        }
        if (log.isDebugEnabled()) {
        	log.debug("end getPackagesFromAnnotations");
        }
        return set;
    }

    /**
     * @param endpointDesc EndpointDescription
     * @return Set of Packages
     */
    private static TreeSet<String> getPackagesFromAnnotations(EndpointDescription endpointDesc,
                                                              MarshalServiceRuntimeDescription msrd) {
    	if (log.isDebugEnabled()) {
    		log.debug("start getPackagesFromAnnotations for EndpointDescription " + endpointDesc.getName());
    	}
        TreeSet<String> set = new TreeSet<String>();
        String implClassName = getServiceImplClassName(endpointDesc);
        if (implClassName != null) {
        	if (log.isDebugEnabled()) {
        		log.debug("EndpointDescription implClassName = " + implClassName);
        	}
            Class clz = loadClass(implClassName);
            if(clz == null){
                clz = loadClass(implClassName, endpointDesc.getAxisService().getClassLoader());
            }
            if (clz != null) {
                addXmlSeeAlsoPackages(clz, msrd, set);
            }
        }
        EndpointInterfaceDescription endpointInterfaceDesc =
                endpointDesc.getEndpointInterfaceDescription();
        if (endpointInterfaceDesc != null) {
            getPackagesFromAnnotations(endpointDesc, endpointInterfaceDesc, set, msrd);
        }
        if (log.isDebugEnabled()) {
    		log.debug("end getPackagesFromAnnotations for EndpointDescription " + endpointDesc.getName());
    	}
        return set;
    }

    /**
     * @param endpointInterfaceDescription EndpointInterfaceDescription
     * @param Set of Packages
     * @param msrd
     */
    private static void getPackagesFromAnnotations(
            EndpointDescription ed,
            EndpointInterfaceDescription endpointInterfaceDesc,
            TreeSet<String> set,
            MarshalServiceRuntimeDescription msrd) {
        
    	if (log.isDebugEnabled()) {
    		log.debug("start getPackagesFromAnnotations for EndpointInterfaceDescription " + 
    					endpointInterfaceDesc.getPortType());
    	}
        OperationDescription[] opDescs = endpointInterfaceDesc.getDispatchableOperations();

        // Inspect the @XmlSeeAlso classes on the interface.
        // A) The SEI class is accessible via the getSEIClass method -OR-
        // B) The endpoint directly implements the sei class
        //    (The @XmlSeeAlso annotations were picked up when the endpoint is examined) -OR-
        // C) Find the SEI class using the @WebService annotation
        Class seicls = endpointInterfaceDesc.getSEIClass();
        if (log.isDebugEnabled()) {
        	log.debug("SEI Class is " + seicls);
        }
        if (seicls == null) {
        	String implClassName = getServiceImplClassName(ed);
            if (implClassName != null) {
            	if (log.isDebugEnabled()) {
            		log.debug("EndpointDescription implClassName = " + implClassName);
            	}
                Class clz = loadClass(implClassName);
                if(clz == null){
                    clz = loadClass(implClassName, ed.getAxisService().getClassLoader());
                }
                if (clz != null) {
                	WebService ws = (WebService) getAnnotation(clz, WebService.class);
                	if (ws != null) {
                		String intClassName = ws.endpointInterface();
                		if (log.isDebugEnabled()) {
                    		log.debug("WebService endpointinterface = " + intClassName);
                    	}
                        seicls = loadClass(intClassName);
                        if (seicls== null){
                            seicls = loadClass(intClassName, ed.getAxisService().getClassLoader());
                        }
                	}
                }
            }
        }
        addXmlSeeAlsoPackages(seicls, msrd, set);
        
        
        // Build a set of packages from all of the operations
        if (opDescs != null) {
            for (int i = 0; i < opDescs.length; i++) {
                getPackagesFromAnnotations(ed, opDescs[i], set, msrd);
            }
        }
        if (log.isDebugEnabled()) {
    		log.debug("end getPackagesFromAnnotations for EndpointInterfaceDescription " + 
    					endpointInterfaceDesc.getPortType());
    	}
        return;
    }

    /**
     * Update the package set with the packages referenced by this OperationDesc
     *
     * @param opDesc OperationDescription
     * @param set    Set<Package> that is updated
     */
    private static void getPackagesFromAnnotations(EndpointDescription ed, OperationDescription opDesc, TreeSet<String> set,
                                                   MarshalServiceRuntimeDescription msrd) {

        // Walk the parameter information
        ParameterDescription[] parameterDescs = opDesc.getParameterDescriptions();
        if (parameterDescs != null) {
            for (int i = 0; i < parameterDescs.length; i++) {
                getPackagesFromAnnotations(parameterDescs[i], set, msrd);
            }
        }

        // Walk the fault information
        FaultDescription[] faultDescs = opDesc.getFaultDescriptions();
        if (faultDescs != null) {
            for (int i = 0; i < faultDescs.length; i++) {
                getPackagesFromAnnotations(ed, faultDescs[i], set, msrd);
            }
        }

        // Also consider the request and response wrappers
        String requestWrapperName = msrd.getRequestWrapperClassName(opDesc);
        String requestWrapperPkg = getPackageFromClassName(requestWrapperName);
        if (log.isDebugEnabled()) {
            log.debug("Package from Request Wrapper annotation = " + requestWrapperPkg);
        }
        if (requestWrapperPkg != null) {
            set.add(requestWrapperPkg);
            set.add("@" + requestWrapperPkg);  // Indicates a package from an actual class reference (versus namespace)
            set.add("[" + requestWrapperName + "]");  // Indicates a actual class reference
        }
        String responseWrapperName = msrd.getResponseWrapperClassName(opDesc);
        String responseWrapperPkg = getPackageFromClassName(responseWrapperName);
        if (log.isDebugEnabled()) {
            log.debug("Package from Response Wrapper annotation = " + responseWrapperPkg);
        }
        if (responseWrapperPkg != null) {
            set.add(responseWrapperPkg);
            set.add("@" + responseWrapperPkg);  // Indicates a package from an actual class reference (versus namespace)
            set.add("[" + responseWrapperName + "]");  // Indicates a actual class reference
        }
      
        // The wrapper class and the element defining the wrapper may be 
        // in different namespaces and packages.  So also look at the namespaces.
        String ns = opDesc.getRequestWrapperTargetNamespace();
        if (ns != null && ns.length() > 0) {
            if (log.isDebugEnabled()) {
                log.debug("TargetNamespace from Request Wrapper annotation = " + ns);
            }
            List packages = makePackages(ns);
            set.addAll(packages);
        }
        ns = opDesc.getResponseWrapperTargetNamespace();
        if (ns != null && ns.length() > 0) {
            if (log.isDebugEnabled()) {
                log.debug("TargetNamespace from Response Wrapper annotation = " + ns);
            }
            List packages = makePackages(ns);
            set.addAll(packages);
        }
        
        // See if the Method is available.  If so, additional reflection
        // can be performed to obtain the generic references
        Method m = getMethod(opDesc, msrd);
        if (log.isDebugEnabled()) {
            log.debug("Method obtained:" + m);
        }

        
        // Examine the parameters
        if (log.isDebugEnabled()) {
            log.debug("Collect the packages of the parameters");
        }
        addPackagesFromParameters(set, opDesc) ; 
        if (m != null) {
            addPackagesFromParameters(set, m);
        }

        // Finally consider the result type
        Class cls = opDesc.getResultActualType();
        if (cls != null && cls != void.class && cls != Void.class) {
            String pkg = getPackageFromClass(cls);
            if (log.isDebugEnabled()) {
                log.debug("Package from Return Type = " + pkg);
            }
            if (pkg != null) {
                set.add(pkg);
                set.add("@" + pkg);  // Indicates a package from an actual class reference (versus namespace)
                set.add("[" + cls.getCanonicalName() + "]");  // Indicates a actual class reference
            }
            if (m != null) {
                addPackagesFromReturn(set, m);
            }

        }
    }
    
    /**
     * Get Method associated with this OperationDesc
     * @param opDesc
     * @param msrd
     * @return Method or null
     */
    private static Method getMethod(OperationDescription opDesc, 
            MarshalServiceRuntimeDescription msrd) {
        Method m = null;
        try {
            m = msrd.getMethod(opDesc);
        } catch (Throwable t) {
            if (log.isDebugEnabled()) {
                log.debug("Method could not be obtained due to " + t);
            }
        }
        return m;
    }

    /**
     * add package information by reflecting the parameters on Method m
     * @param set
     * @param m
     */
    private static void addPackagesFromParameters(TreeSet<String> set, Method m) {
        if (log.isDebugEnabled()) {
            log.debug("enter addPackagesFromParameters for " + m);
        }
        try {
            if (m != null) {
                Set<Class> classes = new HashSet<Class>();
                // Build a set of all of the classes referenced in the parameters (including
                // generic argument references
                for(Type type : m.getGenericParameterTypes()) {
                    classes = ClassUtils.getClasses(type, classes);
                }
                addClassesToPackageSet(classes, set);
            }
        } catch (Throwable t) {
            if (log.isDebugEnabled()) {
                log.debug("Could not reflect the information on method " + m + " due to " + t);
                log.debug("Processing continues");
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("exit addPackagesFromParameters");
        }
    }

    /**
     * add package information by reflecting the return type on Method m
     * @param set
     * @param m
     */
    private static void addPackagesFromReturn(TreeSet<String> set, Method m) {
        if (log.isDebugEnabled()) {
            log.debug("enter addPackagesFromReturn for " + m);
        }
        try {
            if (m != null) {
                Set<Class> classes = new HashSet<Class>();
                // Build a set of all of the classes referenced in the return(including
                // generic argument references
                classes = ClassUtils.getClasses(m.getGenericReturnType(), classes);
                addClassesToPackageSet(classes, set);
            }
        } catch (Throwable t) {
            if (log.isDebugEnabled()) {
                log.debug("Could not reflect the information on method " + m + " due to " + t);
                log.debug("Processing continues");
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("exit addPackagesFromReturn");
        }
    }

    /**
     * Add all of the packages/names in classSet to our package set collection
     * @param classSet
     * @param set
     */
    private static void addClassesToPackageSet(Set<Class> classSet, TreeSet<String> set) {
        if (log.isDebugEnabled()) {
            log.debug("enter addClassesToPackageSet");
        }
        if (classSet != null) {
            for (Class clz : classSet) {
                if (clz != null &&
                        clz != Holder.class &&
                        clz != Future.class &&
                        clz != Response.class) {
                    Package pkg = clz.getPackage();
                    //For primitive types there is no package
                    String pkgText = (pkg != null) ? pkg.getName() : null;
                    if (pkg != null) {
                        String name = clz.getCanonicalName();
                        if (log.isDebugEnabled()) {
                            log.debug(" adding class " + name);
                        }   
                        set.add("@" + pkgText);  // Indicates a package from an actual class reference
                        set.add("[" + name + "]");  // Indicates a actual class referenced
                    }
                }
            }
        }
        if (log.isDebugEnabled()) {
            log.debug("exit addClassesToPackageSet");
        }
    }
    
    /**
     * addPackagesFromParameters
     * @param set  Set of packages
     * @param opDesc OperationDesc containing ParameterDescs
     */
    private static void addPackagesFromParameters(TreeSet<String> set, OperationDescription opDesc) {
        ParameterDescription[] pDescs = opDesc.getParameterDescriptions();
        if (pDescs != null) {
            for (int i=0; i<pDescs.length; i++) {
                ParameterDescription pDesc = pDescs[i];
                if (pDesc != null) {
                    // Get the actual type of the parameter.
                    // For example if the parameter is Holder<A>, the A.class is
                    // returned.
                    // NOTE Generics are handled by the addPackagesFromParameters(Method..) method
                    Class paramClass = pDesc.getParameterActualType();
                    String pkg = getPackageFromClass(paramClass);
                    if (log.isDebugEnabled()) {
                        log.debug("Package from Parameter (" + paramClass + ") = " + pkg);
                    }
                    if (pkg != null) {
                        set.add(pkg);
                        set.add("@" + pkg);  // Indicates a package from an actual class reference (versus namespace)
                        set.add("[" + paramClass.getCanonicalName() + "]");  // Indicates a actual class reference
                    }
                }
            }
        }    
    }

    /**
     * Update the package set with the packages referenced by this ParameterDescription
     *
     * @param paramDesc ParameterDesc
     * @param set       Set<Package> that is updated
     */
    private static void getPackagesFromAnnotations(ParameterDescription paramDesc,
                                                   TreeSet<String> set,
                                                   MarshalServiceRuntimeDescription msrd) {

        // Get the type that defines the actual data.  (this is never a holder )
        Class paramClass = paramDesc.getParameterActualType();

        if (paramClass != null) {
            setTypeAndElementPackages(paramClass, paramDesc.getTargetNamespace(),
                                      paramDesc.getPartName(), set, msrd);
        }

    }

    /**
     * Update the package set with the packages referenced by this FaultDescription
     *
     * @param faultDesc FaultDescription
     * @param set       Set<Package> that is updated
     */
    private static void getPackagesFromAnnotations(EndpointDescription ed, 
                                                   FaultDescription faultDesc, TreeSet<String> set,
                                                   MarshalServiceRuntimeDescription msrd) {

        FaultBeanDesc faultBeanDesc = msrd.getFaultBeanDesc(faultDesc);
        if(faultBeanDesc == null){
            if(log.isDebugEnabled()){
                log.debug("faultBeanDesc from MarshallServiceRuntimeDescription is null");
            }
            //NO FaultBeanDesc found nothing we can do.
            return;
        }
        String faultBeanName = faultBeanDesc.getFaultBeanClassName();
        if(faultBeanName == null){
            if(log.isDebugEnabled()){
                log.debug("FaultBeanName is null");
            }
            //We cannot load the faultBeanName
            return;
        }
        Class faultBean = loadClass(faultBeanName);
        if(faultBean == null){
            faultBean = loadClass(faultBeanName, ed.getAxisService().getClassLoader());
        }
        if (faultBean != null) {
            setTypeAndElementPackages(faultBean, faultBeanDesc.getFaultBeanNamespace(),
                                      faultBeanDesc.getFaultBeanLocalName(), set, msrd);
        }
    }

    /**
     * For each data element, we need the package for both the element and its type.
     *
     * @param cls       Class representing element, type or both
     * @param namespace of the element
     * @param localPart of the element
     * @param set       with both type and element packages set
     */
    private static void setTypeAndElementPackages(Class cls, String namespace, String localPart,
                                                  TreeSet<String> set,
                                                  MarshalServiceRuntimeDescription msrd) {

        // Get the element and type classes
        Class eClass = getElement(cls, msrd);
        Class tClass = getType(cls);

        // Set the package for the type
        if (tClass != null) {
            Package typePkg = tClass.getPackage();
            //For primitive types there is no package
            String pkg = (typePkg != null) ? typePkg.getName() : null;

            if (pkg != null) {
                set.add(pkg);
                set.add("@" + pkg);  // Indicates a package from an actual class reference (versus namespace)
                set.add("[" + tClass.getCanonicalName() + "]");  // Indicates a actual class reference
            }
            // If there is an xmlType, and it maps to a package then add
            // an override if the package is different.
            if (pkg != null) {
                AnnotationDesc ad = msrd.getAnnotationDesc(tClass);
                if (ad != null && ad.hasXmlType()) {
                    String ns = ad.getXmlTypeNamespace();
                    if (ns != null && ns.length() > 0) {
                        List pkgs = makePackages(ns);
                        if (pkgs != null) {
                            for (int i=0; i<pkgs.size(); i++) {
                                String pkg2 = (String) pkgs.get(i);
                                if (!pkg.equals(pkg2)) {
                                    String override = pkg + " > " + pkg2;
                                    if (!set.contains(override)) {
                                        set.add(override);
                                        if (log.isDebugEnabled()) {
                                            log.debug("Adding override=" + override);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            addXmlSeeAlsoPackages(tClass, msrd, set);
        }

        // Set the package for the element
        if (tClass != eClass) {
            if (eClass == null) {
                // A null or empty namespace indicates that the element is
                // unqualified.  This can occur if the parameter is represented as a child element 
                // in doc/lit wrapped.  The package is determined from the wrapper element in such casses.
                if (namespace != null && namespace.length() > 0) {
                    // Use default namespace to package algorithm
                    List pkgs = makePackages(namespace);
                    if (pkgs != null) {
                        set.addAll(pkgs);
                    }
                }
            } else {
                Package elementPkg = eClass.getPackage();
                String pkg = (elementPkg != null) ? elementPkg.getName() : null;
                if (pkg != null) {
                    set.add(pkg);
                    set.add("@" + pkg);  // Indicates a package from an actual class reference (versus namespace)
                    set.add("[" + eClass.getCanonicalName() + "]");  // Indicates a actual class reference
                }
                
                if (pkg != null) {
                    AnnotationDesc ad = msrd.getAnnotationDesc(tClass);
                    if (ad != null && ad.hasXmlRootElement()) {
                        String ns = ad.getXmlRootElementNamespace();
                        if (ns != null && ns.length() > 0) {
                            List pkgs = makePackages(ns);
                            if (pkgs != null) {
                                for (int i=0; i<pkgs.size(); i++) {
                                    String pkg2 = (String) pkgs.get(i);
                                    if (!pkg.equals(pkg2)) {
                                        String override = pkg + " > " + pkg2;
                                        if (!set.contains(override)) {
                                            set.add(override);
                                            if (log.isDebugEnabled()) {
                                                log.debug("Adding override=" + override);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
                addXmlSeeAlsoPackages(tClass, msrd, set);
            }
        }
    }

    /**
     * If cls represents an xml element then cls is returned. Otherwise null is returned
     *
     * @param cls Class
     * @return Class or null
     */
    private static Class getElement(Class cls, MarshalServiceRuntimeDescription msrd) {
        AnnotationDesc annotationDesc = msrd.getAnnotationDesc(cls);
        if (annotationDesc == null) {
            // This shouldn't happen
            annotationDesc = AnnotationDescImpl.create(cls);
        }
        if (annotationDesc.hasXmlRootElement()) {
            return cls;
        }
        return null;
    }

    private final static Class[] noClass = new Class[] { };

    /**
     * Returns the class that defines the type.
     *
     * @param cls
     * @return
     */
    private static Class getType(Class cls) {
        if (JAXBElement.class.isAssignableFrom(cls)) {
            try {
                Method m = cls.getMethod("getValue", noClass);
                return m.getReturnType();
            } catch (Exception e) {
                // We should never get here
                if (log.isDebugEnabled()) {
                    log.debug("Cannot find JAXBElement.getValue method.");
                }
                return null;
            }
        } else {
            return cls;
        }
    }

    /**
     * Default Namespace to Package algorithm
     *
     * @param ns
     * @return List of one or more packages
     */
    private static List makePackages(String ns) {
        List packages = JavaUtils.getPackagesFromNamespace(ns);
        return packages;
     }

    /**
     * Return the package associated with the class name.  The className may not be specified (in
     * which case a null Package is returned). 
     * If class has anunnamed package return ""
     *
     * @param className String (may be null or empty)
     * @return String or null if problems occur
     */
    public static String getPackageFromClassName(String className) {
        Class clz = loadClass(className);
        String pkg = getPackageFromClass(clz);
        return pkg;
    }
    
    /**
     * Return the package associated with the class name.  The className may not be specified (in
     * which case a null Package is returned). if class has unnamed package return ""
     *
     * @param cls Class
     * @return String or null 
     */
    public static String getPackageFromClass(Class cls) {
        String pkgName = null;
        if (cls == null) {
            pkgName = null;
        } else if (cls.isArray()) {
            pkgName = getPackageFromClass(cls.getComponentType());
        } else if (cls.isPrimitive()) {
            pkgName = null;
        } else {
            pkgName =
                (cls.getPackage() == null) ? "" : cls.getPackage().getName(); 
        }
        return pkgName;
    }

    private static void addXmlSeeAlsoPackages(Class clz, 
                                              MarshalServiceRuntimeDescription msrd, 
                                              TreeSet<String> set) {
    	if (log.isDebugEnabled()) {
    		log.debug("start addXmlSeeAlsoPackages for " + clz);
    	}
        if (clz != null) {
            AnnotationDesc aDesc = msrd.getAnnotationDesc(clz);
            if (aDesc != null) {
                Class[] seeAlso = aDesc.getXmlSeeAlsoClasses();
                if (seeAlso != null) {
                    for (int i=0; i<seeAlso.length; i++) {
                        String pkg =
                            (seeAlso[i] == null) ? null : 
                                (seeAlso[i].getPackage() == null) ? "" : 
                                    seeAlso[i].getPackage().getName();
                        if (pkg != null) {
                        	if (log.isDebugEnabled()) {
                        		log.debug(" adding package = " + pkg);
                        	}
                            set.add(pkg);
                            set.add("@" + pkg);  // Indicates a package from an actual class reference (versus namespace)
                            set.add("[" + seeAlso[i].getCanonicalName() + "]");  // Indicates a actual class reference
                        }
                    }
                }
            }
            
            Class[] interfaces = clz.getInterfaces();
            if (interfaces != null) {
                for (int i=0; i<interfaces.length; i++) {
                    addXmlSeeAlsoPackages(interfaces[i], msrd, set);
                }
            }
        }
        if (log.isDebugEnabled()) {
    		log.debug("end addXmlSeeAlsoPackages for " + clz);
    	}
    }
    /**
     * Loads the class
     *
     * @param className
     * @return Class (or null if the class cannot be loaded)
     */
    private static Class loadClass(String className) {
        // Don't make this public, its a security exposure
        if (className == null || className.length() == 0) {
            return null;
        }
        try {
            // Class.forName does not support primitives
            Class cls = ClassUtils.getPrimitiveClass(className);
            if (cls == null) {
                cls = Class.forName(className, true, getContextClassLoader());
            }
            return cls;
            //Catch Throwable as ClassLoader can throw an NoClassDefFoundError that
            //does not extend Exception, so lets catch everything that extends Throwable
            //rather than just Exception.
        } catch (Throwable e) {
            // TODO Should the exception be swallowed ?
            if (log.isDebugEnabled()) {
                log.debug("PackageSetBuilder cannot load the following class:" + className);
            }
        }
        return null;
    }

    /**
     * Loads the class
     *
     * @param className
     * @return Class (or null if the class cannot be loaded)
     */
    private static Class loadClass(String className, ClassLoader loader) {
        // Don't make this public, its a security exposure
        if (className == null || className.length() == 0) {
            return null;
        }
        try {
            // Class.forName does not support primitives
            Class cls = ClassUtils.getPrimitiveClass(className);
            if (cls == null) {
                cls = forName(className, true, loader);
            }
            return cls;
            //Catch Throwable as ClassLoader can throw an NoClassDefFoundError that
            //does not extend Exception, so lets catch everything that extends Throwable
            //rather than just Exception.
        } catch (Throwable e) {
            // TODO Should the exception be swallowed ?
            if (log.isDebugEnabled()) {
                log.debug("PackageSetBuilder cannot load the following class:" + className);
            }
        }
        return null;
    }

    private static Definition getWSDLDefinition(String wsdlLoc){
        Definition wsdlDefinition = null;
        final String wsdlLocation = wsdlLoc;
        if (wsdlLocation != null && wsdlLocation.trim().length() > 0) {
            try {
                wsdlDefinition = (Definition) AccessController.doPrivileged(
                        new PrivilegedExceptionAction() {
                            public Object run() throws MalformedURLException, IOException, WSDLException {
                                String baseDir = new File(System.getProperty("basedir",".")).getCanonicalPath();
                                String wsdlLocationPath = new File(baseDir +File.separator+ wsdlLocation).getAbsolutePath();
                                File file = new File(wsdlLocationPath);
                                URL url = file.toURI().toURL();
                                if(log.isDebugEnabled()){
                                    log.debug("Reading WSDL from URL:" +url.toString());
                                }
                                // This is a temporary wsdl and we use it to dig into the schemas,
                                // Thus the memory limit is set to false.  It will be discarded after it is used
                                // by the PackageSetBuilder implementation.
                                WSDLWrapper wsdlWrapper = new WSDL4JWrapper(url, false, 0);
                                return wsdlWrapper.getDefinition();
                            }
                        });
            } catch (PrivilegedActionException e) {
                // Swallow and continue
                if (log.isDebugEnabled()) {
                    log.debug("Exception getting wsdlLocation: " +e.getException());
                }
            }
        }

        return wsdlDefinition;
    }

    /**
     * Return the class for this name
     *
     * @return Class
     */
    static Class forName(final String className, final boolean initialize,
                         final ClassLoader classloader) throws ClassNotFoundException {
        // NOTE: This method must remain protected because it uses AccessController
        Class cl = null;
        try {
            cl = (Class)AccessController.doPrivileged(
                    new PrivilegedExceptionAction() {
                        public Object run() throws ClassNotFoundException {
                            // Class.forName does not support primitives
                            Class cls = ClassUtils.getPrimitiveClass(className);
                            if (cls == null) {
                                cls = Class.forName(className, initialize, classloader);
                            }
                            return cls;
                        }
                    }
            );
        } catch (PrivilegedActionException e) {
            if (log.isDebugEnabled()) {
                log.debug("Exception thrown from AccessController: " + e);
            }
            throw (ClassNotFoundException)e.getException();
        }

        return cl;
    }

    /** @return ClassLoader */
    static ClassLoader getContextClassLoader() {
        // NOTE: This method must remain private because it uses AccessController
        ClassLoader cl = null;
        try {
            cl = (ClassLoader)AccessController.doPrivileged(
                    new PrivilegedExceptionAction() {
                        public Object run() throws ClassNotFoundException {
                            return Thread.currentThread().getContextClassLoader();
                        }
                    }
            );
        } catch (PrivilegedActionException e) {
            if (log.isDebugEnabled()) {
                log.debug("Exception thrown from AccessController: " + e);
            }
            throw (RuntimeException)e.getException();
        }

        return cl;
    }
    /**
     * Get the Serivce Impl Class by looking at the AxisService
     * @param endpointDescription
     * @return class name or null
     */
    static private String getServiceImplClassName(EndpointDescription endpointDescription) {
        String result = null;
        if (endpointDescription != null) {
            AxisService as = endpointDescription.getAxisService();
            if (as != null) {
                Parameter param = as.getParameter(org.apache.axis2.Constants.SERVICE_CLASS);

                // If there was no implementation class, we should not go any further
                if (param != null) {
                    result = ((String)param.getValue()).trim();
                }
            }
        }
        return result;
    }
    
    /**
     * Get an annotation.  This is wrappered to avoid a Java2Security violation.
     * @param cls Class that contains annotation 
     * @param annotation Class of requrested Annotation
     * @return annotation or null
     */
    private static Annotation getAnnotation(final AnnotatedElement element, final Class annotation) {
        return (Annotation) AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                return element.getAnnotation(annotation);
            }
        });
    }
}
