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

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;

import org.apache.axis2.AxisFault;
import org.apache.axis2.description.AxisMessage;
import org.apache.axis2.description.AxisOperation;
import org.apache.axis2.description.AxisService;
import org.apache.axis2.description.AxisServiceGroup;
import org.apache.axis2.description.Parameter;
import org.apache.axis2.description.TransportInDescription;
import org.apache.axis2.description.WSDL11ToAllAxisServicesBuilder;
import org.apache.axis2.engine.AxisConfiguration;
import org.apache.axis2.engine.Handler;
import org.apache.axis2.kernel.TransportListener;
import org.apache.axis2.util.MetaDataEntry;
import org.apache.axis2.wsdl.WSDLConstants;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * Some Externalize objects must be "activated" after they are read.
 * Activation normally involves associating the object with objects in the current
 * runtime.
 * 
 * ActivateUtils provides activation related utilities
 */
public class ActivateUtils {
    
    private static final Log log = LogFactory.getLog(ActivateUtils.class);
    
    /**
     * Private Constructor
     * All methods in ActivateUtils are static.
     */
    private ActivateUtils() {}

    /**
     * Find the AxisServiceGroup object that matches the criteria
     * <p/>
     * <B>Note<B> the saved service group meta information may not
     * match up with any of the serviceGroups that
     * are in the current AxisConfiguration object.
     * 
     * @param axisConfig The AxisConfiguration object
     * @param serviceGrpClassName the class name string for the target object
     *                   (could be a derived class)
     * @param serviceGrpName      the name associated with the service group
     * @return the AxisServiceGroup object that matches the criteria
     */
    public static AxisServiceGroup findServiceGroup(AxisConfiguration axisConfig,
                                                    String serviceGrpClassName,
                                                    String serviceGrpName) {
        Iterator its = axisConfig.getServiceGroups();

        while (its.hasNext()) {
            AxisServiceGroup checkServiceGroup = (AxisServiceGroup) its.next();

            String tmpSGClassName = checkServiceGroup.getClass().getName();
            String tmpSGName = checkServiceGroup.getServiceGroupName();

            if (tmpSGClassName.equals(serviceGrpClassName)) {
                boolean found = false;

                // the serviceGroupName can be null, so either both the 
                // service group names are null or they match
                if ((tmpSGName == null) && (serviceGrpName == null)) {
                    found = true;
                } else if ((tmpSGName != null) && (tmpSGName.equals(serviceGrpName))) {
                    found = true;
                } else if (containsExternalizedAxisServiceName(checkServiceGroup, serviceGrpName)) {
                    found = true;
                }

                if (found) {
                    // trace point
                    if (log.isTraceEnabled()) {
                        log.trace("ObjectStateUtils:findServiceGroup(): returning  ["
                                + serviceGrpClassName + "]   [" + serviceGrpName + "]");
                    }

                    return checkServiceGroup;
                }
            }
        }

        // trace point
        if (log.isTraceEnabled()) {
            log.trace("ObjectStateUtils:findServiceGroup(): [" + serviceGrpClassName + "]   ["
                    + serviceGrpName + "]  returning  [null]");
        }

        return null;
    }
    
    /**
     * Answer if there are any AxisServices in the specified ServiceGroup that have an externalized
     * name that matches the service group name.    
     * 
     * @param checkServiceGroup The AxisServiceGroup containing the AxisServies to check
     * @param serviceGrpName The name to check as the externalized name of the AxisService
     * @return true if the group contains an AxisService with that name; false otherwise.
     */
    private static boolean containsExternalizedAxisServiceName(
            AxisServiceGroup checkServiceGroup, String serviceGrpName) {
        boolean containsAxisService = false;
        if (serviceGrpName != null && checkServiceGroup != null) {
            // Get a list of AxisServices on the group
            // Iterate over them to see if any have the Externalized Name Parameter
            // If so and it mathces, then this service group name then use this service group
            Iterator axisServicesInGroup = checkServiceGroup.getServices();
            while (axisServicesInGroup.hasNext()) {
                AxisService checkService = (AxisService) axisServicesInGroup.next();
                String externalizedServiceName = 
                    (String) checkService.getParameterValue(EXTERNALIZED_AXIS_SERVICE_NAME);
                if (externalizedServiceName != null && 
                        externalizedServiceName.equals(serviceGrpName)) {
                    containsAxisService = true;
                    break;
                }
            }
        }
        return containsAxisService;
    }

    /**
     * Find the AxisService object that matches the criteria
     * 
     * @param axisConfig The AxisConfiguration object
     * @param serviceClassName the class name string for the target object
     *                   (could be a derived class)
     * @param serviceName      the name associated with the service
     * @return the AxisService object that matches the criteria
     */
    public static AxisService findService(AxisConfiguration axisConfig, String serviceClassName,
            String serviceName) {
        return findService(axisConfig, serviceClassName, serviceName, null);
    }
    private static final String EXTERNALIZED_AXIS_SERVICE_NAME 
        = "org.apache.axis2.context.externalize.AxisServiceName";
    public static AxisService findService(AxisConfiguration axisConfig, String serviceClassName,
                                          String serviceName, String extraName) {
        if (log.isDebugEnabled()) {
            log.debug("ActivateUtils.findService serviceName: " + serviceName +", extraName: "
                + extraName);
        }
        HashMap services = axisConfig.getServices();

        Iterator its = services.values().iterator();

        // We loop through all the axis services looking for an exact match of the name, and if 
        // it exists, the extra information of the fully qualified Service QName and the port
        // name.  If we find an exact match, including the name of the service, we stop looking.
        // If no exact match is found after searching the entire list, then we use the first 
        // match of the extra information we found.  Note that picking the first one found is arbitrary.
        boolean exactServiceNameMatch = false;
        AxisService foundService = null;
        while (its.hasNext() && !exactServiceNameMatch) {
            AxisService service = (AxisService) its.next();
            switch(checkAxisService(service, serviceClassName, serviceName, extraName)) {
            case NAME_MATCH:
                foundService = service;
                exactServiceNameMatch = true;
                break;
            case SERVICE_PORT_MATCH:
                if (foundService == null) {
                    foundService = service;
                }
                break;
            case NONE:
                break;
            }
        }
        if (foundService != null) {
            // Store the original serviceName on the service for use in findServiceGroup
            // This is the name from when the service was originally externalized.
            try {
                foundService.addParameter(EXTERNALIZED_AXIS_SERVICE_NAME, serviceName);
            } catch (AxisFault e) {
                // I don't think this can actually ever happen.  The exception occurs if the
                // parameter is locked, but this is the only code that references that parameter
                if (log.isDebugEnabled()) {
                    log.debug("Got fault trying to add parameter " + EXTERNALIZED_AXIS_SERVICE_NAME +
                            " for service name " + serviceName + " to AxisService " + foundService, e); 
                }
            }
            if (log.isTraceEnabled()) {
                log.trace("ObjectStateUtils:findService(): returning  [" + serviceClassName
                        + "]   [" + serviceName + "] AxisService name [" + foundService.getName()
                        + "]");
            }

            return foundService;
        }

        // trace point
        if (log.isTraceEnabled()) {
            log.trace("ObjectStateUtils:findService(): [" + serviceClassName + "]   ["
                    + serviceName + "]  returning  [null]");
        }
        return null;
    }
    // NONE means no match at all, NAME_MATCH means the service names match and the service and
    // port names match if they exist, and SERVICE_PORT_MATCH means only the service and port names
    // match (not the service names).
    private enum MatchType {NONE, NAME_MATCH, SERVICE_PORT_MATCH};
    
    /**
     * Determine if the AxisService passed in is a match for the name information passed in.
     * The type of match is returned as an enum.
     * 
     * @param serviceToCheck The AxisService to check against the other parameters
     * @param serviceClassName The name of class to look for
     * @param externalizedServiceName The name of the Service to look for.
     * @param externalizedExtraName Additional information beyond the name of the service to look 
     *  for
     * @return MatchType indication of the type of match for the passed in AxisService.
     */
    private static MatchType checkAxisService(AxisService serviceToCheck,
            String serviceClassName, String externalizedServiceName, String externalizedExtraName) {
        MatchType serviceIsSame = MatchType.NONE;

        String checkServiceClassName = serviceToCheck.getClass().getName();
        String checkServiceName = serviceToCheck.getName();
        String checkServiceExtraName = getAxisServiceExternalizeExtraName(serviceToCheck);
        if (checkServiceClassName.equals(serviceClassName)) {
            if ((externalizedExtraName == null || checkServiceExtraName == null)
                    && checkServiceName.equals(externalizedServiceName)) {
                // If we don't have an externalized extra name or there is no
                // externalized extra name information in the current Axis Service, then 
                // check the simple case where the AxisService names match
                serviceIsSame = MatchType.NAME_MATCH;
            } else if (externalizedExtraName != null && checkServiceExtraName != null
                    && checkServiceExtraName.equals(externalizedExtraName)){
                // If the service names also match each other, then consider this a name match
                // otherwise it is just a service & port match
                if (checkServiceName.equals(externalizedServiceName)) {
                    serviceIsSame = MatchType.NAME_MATCH;
                } else {
                    serviceIsSame = MatchType.SERVICE_PORT_MATCH;
                }
            } else {
                // This is not an error necessarily; just iterating through all of AxisServices
                // and some won't match.
                if (log.isDebugEnabled()) {
                    log.debug("No match: checking Externalized AxisService name: " + externalizedServiceName 
                            + " and extraName: " + externalizedExtraName 
                            + " against existing AxisService name: " + checkServiceName
                            + " and extraName: " + checkServiceExtraName);
                }
            }
        }

        return serviceIsSame;
        
    }

    // This String separates the ServiceQName and the Port LocalName in the extraName field
    // It is not a valid value in a QName so it can not accidently appear in a valid QName.
    private static String DELIMITER_SERVICE_PORT = " ";
    /**
     * Return a Sring that contains the service QName and port local name of an AxisService
     * seperated by a delimiter.  This value can be used as part of externalizing an AxisService
     * to provide additional information during deserialization in cases where the AxisService
     * name is not unique or does not match for whatever reasons.  
     * 
     * @param axisService The AxisService to create the externalized name
     * @return a String with the ServiceQName and port local name separated by DELIMITER_SERVICE_PORT
     * if both values exist as parameters on the service; null otherwise.
     */
    public static String getAxisServiceExternalizeExtraName(AxisService axisService) {
        String extraName = null;
        String serviceQName = null;
        String portName = null;
        
        Parameter serviceQNameParameter = 
            axisService.getParameter(WSDL11ToAllAxisServicesBuilder.WSDL_SERVICE_QNAME);
        if (serviceQNameParameter != null) {
            serviceQName = serviceQNameParameter.getValue().toString();
        }
        
        Parameter portNameParameter = 
            axisService.getParameter(WSDL11ToAllAxisServicesBuilder.WSDL_PORT); 
        if (portNameParameter != null) {
            portName = (String) portNameParameter.getValue();
        }
        
        if (serviceQName != null && portName != null) {
            extraName = serviceQName + DELIMITER_SERVICE_PORT +  portName;
        }
        
        return extraName;
    }
    


    /**
     * Find the AxisOperation object that matches the criteria
     * 
     * @param axisConfig The AxisConfiguration object
     * @param opClassName the class name string for the target object
     *                   (could be a derived class)
     * @param opQName    the name associated with the operation
     * @return the AxisOperation object that matches the given criteria
     */
    public static AxisOperation findOperation(AxisConfiguration axisConfig, String opClassName,
                                              QName opQName) {
        HashMap services = axisConfig.getServices();

        Iterator its = services.values().iterator();

        while (its.hasNext()) {
            AxisService service = (AxisService) its.next();

            Iterator ito = service.getOperations();

            while (ito.hasNext()) {
                AxisOperation operation = (AxisOperation) ito.next();

                String tmpOpName = operation.getClass().getName();
                QName tmpOpQName = operation.getName();

                if ((tmpOpName.equals(opClassName)) && (tmpOpQName.equals(opQName))) {
                    // trace point
                    if (log.isTraceEnabled()) {
                        log.trace("ObjectStateUtils:findOperation(axisCfg): returning  ["
                                + opClassName + "]   [" + opQName.toString() + "]");
                    }

                    return operation;
                }
            }
        }

        // trace point
        if (log.isTraceEnabled()) {
            log.trace("ObjectStateUtils:findOperation(axisCfg): [" + opClassName + "]   ["
                    + opQName.toString() + "]  returning  [null]");
        }

        return null;
    }


    /**
     * Find the AxisOperation object that matches the criteria
     * 
     * @param service    The AxisService object
     * @param opClassName The class name string for the target object
     *                   (could be a derived class)
     * @param opQName    the name associated with the operation
     * @return the AxisOperation object that matches the given criteria
     */
    public static AxisOperation findOperation(AxisService service, String opClassName,
                                              QName opQName) {
        if (service == null) {
            return null;
        }

        Iterator ito = service.getOperations();

        // Previous versions of Axis2 didn't use a namespace on the operation name, so they wouldn't
        // have externalized a namespace.  If that's the case, only compare the localPart of the
        // operation name
        String namespace = opQName.getNamespaceURI();
        boolean ignoreNamespace = false;
        if (namespace == null || "".equals(namespace)) {
            ignoreNamespace = true;
        }
        
        while (ito.hasNext()) {
            AxisOperation operation = (AxisOperation) ito.next();

            String tmpOpName = operation.getClass().getName();
            QName tmpOpQName = operation.getName();
            
            if ((tmpOpName.equals(opClassName)) && 
                ((ignoreNamespace && (tmpOpQName.getLocalPart().equals(opQName.getLocalPart())) || (tmpOpQName.equals(opQName))))) {

                if (log.isTraceEnabled()) {
                    log.trace("ObjectStateUtils:findOperation(service): ignoreNamespace [" + ignoreNamespace
                    		+ "] returning  ["
                            + opClassName + "]   [" + opQName.toString() + "]");
                }

                return operation;
            }
        }

        // trace point
        if (log.isTraceEnabled()) {
            log.trace("ObjectStateUtils:findOperation(service): ignoreNamespace [" + ignoreNamespace
                    + " classname [" + opClassName + "]  QName ["
                    + opQName.toString() + "]  returning  [null]");
        }

        return null;
    }

    /**
     * Find the AxisMessage object that matches the criteria
     * 
     * @param op             The AxisOperation object
     * @param msgName        The name associated with the message
     * @param msgElementName The name associated with the message element
     * @return the AxisMessage object that matches the given criteria
     */
    public static AxisMessage findMessage(AxisOperation op, String msgName, String msgElementName) {
        // Several kinds of AxisMessages can be associated with a particular 
        // AxisOperation.  The kinds of AxisMessages that are typically
        // accessible are associated with "in" and "out".  
        // There are also different kinds of AxisOperations, and each
        // type of AxisOperation can have its own mix of AxisMessages
        // depending on the style of message exchange pattern (mep)

        if (op == null) {
            // trace point
            if (log.isTraceEnabled()) {
                log.trace("ObjectStateUtils:findMessage(): [" + msgName + "]  [" + msgElementName
                        + "] returning  [null] - no AxisOperation");
            }

            return null;
        }

        if (msgName == null) {
            // nothing to match with, expect to match against a name
            // trace point
            if (log.isTraceEnabled()) {
                log.trace("ObjectStateUtils:findMessage(): [" + msgName + "]  [" + msgElementName
                        + "] returning  [null] - message name is not set");
            }

            return null;
        }


        String tmpName = null;
        String tmpElementName = null;

        //-------------------------------------
        // first try the "out" message
        //-------------------------------------
        AxisMessage out = null;
        try {
            out = op.getMessage(WSDLConstants.MESSAGE_LABEL_OUT_VALUE);
        } catch (Exception ex) {
            // just absorb the exception
        }

        if (out != null) {
            tmpName = out.getName();

            QName tmpQout = out.getElementQName();
            if (tmpQout != null) {
                tmpElementName = tmpQout.toString();
            }
        }

        // check the criteria for a match

        boolean matching = matchMessageNames(tmpName, tmpElementName, msgName, msgElementName);

        if (matching) {
            // trace point
            if (log.isTraceEnabled()) {
                log.trace("ObjectStateUtils:findMessage(): returning OUT message  [" + msgName
                        + "]  [" + msgElementName + "] ");
            }

            return out;
        }

        //-------------------------------------
        // next, try the "in" message 
        //-------------------------------------
        AxisMessage in = null;
        try {
            in = op.getMessage(WSDLConstants.MESSAGE_LABEL_IN_VALUE);
        } catch (Exception ex) {
            // just absorb the exception
        }

        if (in != null) {
            tmpName = in.getName();

            QName tmpQin = in.getElementQName();
            if (tmpQin != null) {
                tmpElementName = tmpQin.toString();
            }
        } else {
            tmpName = null;
            tmpElementName = null;
        }

        // check the criteria for a match

        matching = matchMessageNames(tmpName, tmpElementName, msgName, msgElementName);

        if (matching) {
            // trace point
            if (log.isTraceEnabled()) {
                log.trace("ObjectStateUtils:findMessage(): returning IN message [" + msgName
                        + "]  [" + msgElementName + "] ");
            }

            return in;
        }

        // if we got here, then no match was found

        // trace point
        if (log.isTraceEnabled()) {
            log.trace("ObjectStateUtils:findMessage(): [" + msgName + "]  [" + msgElementName
                    + "] returning  [null]");
        }

        return null;
    }
    
    /**
     * Find the Handler object that matches the criteria
     * 
     * @param existingHandlers The list of existing handlers and phases
     * @param handlerClassName the class name string for the target object
     *                   (could be a derived class)
     * @return the Handler object that matches the criteria
     */
    public static Object findHandler(List<Handler> existingHandlers, MetaDataEntry metaDataEntry) //String handlerClassName)
    {

        String title = "ObjectStateUtils:findHandler(): ";

        String handlerClassName = metaDataEntry.getClassName();
        String qNameAsString = metaDataEntry.getQNameAsString();

        for (int i = 0; i < existingHandlers.size(); i++) {
            if (existingHandlers.get(i) != null) {
                String tmpClassName = existingHandlers.get(i).getClass().getName();
                String tmpName = ((Handler) existingHandlers.get(i)).getName().toString();

                if ((tmpClassName.equals(handlerClassName)) && (tmpName.equals(qNameAsString))) {
                    // trace point
                    if (log.isTraceEnabled()) {
                        log.trace(title + " [" + handlerClassName + "]  name [" + qNameAsString
                                + "]  returned");
                    }

                    return (Handler) (existingHandlers.get(i));
                }
            }
        }

        // trace point
        if (log.isTraceEnabled()) {
            log.trace(title + " [" + handlerClassName + "]  name [" + qNameAsString
                    + "] was not found in the existingHandlers list");
        }

        return null;
    }
    
    /**
     * Find the TransportListener object that matches the criteria
     * <p/>
     * <B>Note<B> the saved meta information may not
     * match up with any of the objects that
     * are in the current AxisConfiguration object.
     * 
     * @param axisConfig The AxisConfiguration object
     * @param listenerClassName the class name string for the target object
     *                   (could be a derived class)
     * @return the TransportListener object that matches the criteria
     */
    public static TransportListener findTransportListener(AxisConfiguration axisConfig,
                                                          String listenerClassName) {
        // TODO: investigate a better technique to match up with a TransportListener

        HashMap transportsIn = axisConfig.getTransportsIn();

        // get a collection of the values in the map
        Collection values = transportsIn.values();

        Iterator i = values.iterator();

        while (i.hasNext()) {
            TransportInDescription ti = (TransportInDescription) i.next();

            TransportListener tl = ti.getReceiver();
            String tlClassName = tl.getClass().getName();

            if (tlClassName.equals(listenerClassName)) {
                // trace point
                if (log.isTraceEnabled()) {
                    log.trace("ObjectStateUtils:findTransportListener():  [" + listenerClassName
                            + "]  returned");
                }

                return tl;
            }
        }

        // trace point
        if (log.isTraceEnabled()) {
            log.trace("ObjectStateUtils:findTransportListener(): returning  [null]");
        }

        return null;
    }

    /**
     * Compares the two collections to see if they are equivalent.
     * 
     * @param a1  The first collection
     * @param a2  The second collection
     * @param strict  Indicates whether strict checking is required.  Strict
     *                checking means that the two collections must have the
     *                same elements in the same order.  Non-strict checking
     *                means that the two collections must have the same 
     *                elements, but the order is not significant.
     * @return TRUE if the two collections are equivalent
     *         FALSE, otherwise
     */
    public static boolean isEquivalent(ArrayList a1, ArrayList a2, boolean strict) {
        if ((a1 != null) && (a2 != null)) {
            // check number of elements in lists
            int size1 = a1.size();
            int size2 = a2.size();

            if (size1 != size2) {
                // trace point
                if (log.isTraceEnabled()) {
                    log.trace("ObjectStateUtils:isEquivalent(ArrayList,ArrayList): FALSE - size mismatch ["
                            + size1 + "] != [" + size2 + "]");
                }
                return false;
            }

            if (strict) {
                // Strict checking
                // The lists must contain the same elements in the same order.
                return (a1.equals(a2));
            } else {
                // Non-strict checking
                // The lists must contain the same elements but the order is not required.
                Iterator i1 = a1.iterator();

                while (i1.hasNext()) {
                    Object obj1 = i1.next();

                    if (!a2.contains(obj1)) {
                        // trace point
                        if (log.isTraceEnabled()) {
                            log.trace("ObjectStateUtils:isEquivalent(ArrayList,ArrayList): FALSE - mismatch with element ["
                                    + obj1.getClass().getName() + "] ");
                        }
                        return false;
                    }
                }

                return true;
            }

        } else if ((a1 == null) && (a2 == null)) {
            return true;
        } else if ((a1 != null) && (a2 == null)) {
            if (a1.size() == 0) {
                return true;
            }
            return false;
        } else if ((a1 == null) && (a2 != null)) {
            if (a2.size() == 0) {
                return true;
            }
            return false;
        } else {
            // mismatch

            // trace point
            if (log.isTraceEnabled()) {
                log.trace("ObjectStateUtils:isEquivalent(ArrayList,ArrayList): FALSE - mismatch in lists");
            }
            return false;
        }
    }

    /**
     * Compares the two collections to see if they are equivalent.
     * 
     * @param m1  The first collection
     * @param m2  The second collection
     * @param strict  Indicates whether strict checking is required.  Strict
     *                checking means that the two collections must have the
     *                same mappings.  Non-strict checking means that the 
     *                two collections must have the same keys.  In both
     *                cases, the order is not significant.
     * @return TRUE if the two collections are equivalent
     *         FALSE, otherwise
     */
    public static boolean isEquivalent(Map m1, Map m2, boolean strict) {
        if ((m1 != null) && (m2 != null)) {
            if (strict) {
                // This is a strict test.
                // Returns true if the given object is also a map and the two Maps 
                // represent the same mappings. 
                return (m1.equals(m2));
            } else {
                int size1 = m1.size();
                int size2 = m2.size();

                if (size1 != size2) {
                    return false;
                }

                // check the keys, ordering is not important between the two maps
                Iterator it1 = m1.keySet().iterator();

                while (it1.hasNext()) {
                    Object key1 = it1.next();

                    if (m2.containsKey(key1) == false) {
                        return false;
                    }
                }

                return true;
            }
        } else if ((m1 == null) && (m2 == null)) {
            return true;
        } else {
            // mismatch
            return false;
        }
    }


    /**
     * Compares the two collections to see if they are equivalent.
     * 
     * @param l1  The first collection
     * @param l2  The second collection
     * @return TRUE if the two collections are equivalent
     *         FALSE, otherwise
     */
    public static boolean isEquivalent(LinkedList l1, LinkedList l2) {
        if ((l1 != null) && (l2 != null)) {
            // This is a strict test.
            // Returns true if the specified object is also a list, 
            // both lists have the same size, and all corresponding pairs 
            // of elements in the two lists are equal where
            // they contain the same elements in the same order.
            return (l1.equals(l2));
        } else if ((l1 == null) && (l2 == null)) {
            return true;
        } else {
            // mismatch
            return false;
        }
    }
    
    /**
     * Check the first set of names for a match against
     * the second set of names.  These names are 
     * associated with AxisMessage objects. Message names
     * are expected to be non-null.  Element names could
     * be either null or non-null.
     * 
     * @param name1  The name for the first message
     * @param elementName1 The element name for the first message
     * @param name2  The name for the second message
     * @param elementName2 The element name for the second message
     * @return TRUE if there's a match,
     *         FALSE otherwise
     */
    private static boolean matchMessageNames(String name1, String elementName1, String name2,
                                             String elementName2) {
        // the name for the message must exist
        if ((name1 != null) && (name2 != null) && (name1.equals(name2))) {
            // there's a match on the name associated with the message object

            // element names need to match, including being null
            if ((elementName1 == null) && (elementName2 == null)) {
                // there's a match for the nulls
                return true;
            } else if ((elementName1 != null) && (elementName2 != null)
                    && (elementName1.equals(elementName2))) {
                // there's a match for the element names
                return true;
            } else {
                // there's some mismatch
                return false;
            }
        } else {
            // either a message name is null or the names don't match
            return false;
        }
    }

}
