/*
 * The Apache Software License, Version 1.1
 *
 *
 * Copyright (c) 2001-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgment:  
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowledgment may appear in the software itself,
 *    if and wherever such third-party acknowledgments normally appear.
 *
 * 4. The names "Axis" and "Apache Software Foundation" must
 *    not be used to endorse or promote products derived from this
 *    software without prior written permission. For written 
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache",
 *    nor may "Apache" appear in their name, without prior written
 *    permission of the Apache Software Foundation.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 */

package org.jboss.axis.encoding;

import org.jboss.axis.Constants;
import org.jboss.axis.utils.Messages;

import java.util.HashMap;

/**
 * <p/>
 * The TypeMappingRegistry keeps track of the individual TypeMappings.
 * </p>
 * <p/>
 * The TypeMappingRegistry for axis contains a default type mapping
 * that is set for either SOAP 1.1 or SOAP 1.2
 * The default type mapping is a singleton used for the entire
 * runtime and should not have anything new registered in it.
 * </p>
 * <p/>
 * Instead the new TypeMappings for the deploy and service are
 * made in a separate TypeMapping which is identified by
 * the soap encoding.  These new TypeMappings delegate back to
 * the default type mapping when information is not found.
 * </p>
 * <p/>
 * So logically we have:
 * <pre>
 *         TMR
 *         | |
 *         | +---------------> DefaultTM
 *         |                      ^
 *         |                      |
 *         +----> TM --delegate---+
 * </pre>
 * <p/>
 * But in the implementation, the TMR references
 * "delegate" TypeMappings (TM') which then reference the actual TM's
 * </p>
 * <p/>
 * So the picture is really:
 * <pre>
 *         TMR
 *         | |
 *         | +-----------TM'------> DefaultTM
 *         |              ^
 *         |              |
 *         +-TM'-> TM ----+
 * </pre>
 * <p/>
 * This extra indirection is necessary because the user may want to
 * change the default type mapping.  In such cases, the TMR
 * just needs to adjust the TM' for the DefaultTM, and all of the
 * other TMs will properly delegate to the new one.  Here's the picture:
 * <pre>
 *         TMR
 *         | |
 *         | +-----------TM'--+     DefaultTM
 *         |              ^   |
 *         |              |   +---> New User Defined Default TM
 *         +-TM'-> TM ----+
 * </pre>
 * <p/>
 * The other reason that it is necessary is when a deploy
 * has a TMR, and then TMR's are defined for the individual services
 * in such cases the delegate() method is invoked on the service
 * to delegate to the deploy TMR
 * <pre>
 *       Deploy TMR
 *         | |
 *         | +-----------TM'------> DefaultTM
 *         |              ^
 *         |              |
 *         +-TM'-> TM ----+
 * <p/>
 *       Service TMR
 *         | |
 *         | +-----------TM'------> DefaultTM
 *         |              ^
 *         |              |
 *         +-TM'-> TM ----+
 * <p/>
 *    ServiceTMR.delegate(DeployTMR)
 * <p/>
 *       Deploy TMR
 *         | |
 *         | +------------TM'------> DefaultTM
 *         |              ^ ^
 *         |              | |
 *         +-TM'-> TM ----+ |
 *           ^              |
 *   +-------+              |
 *   |                      |
 *   |   Service TMR        |
 *   |     | |              |
 *   |     | +----------TM'-+
 *   |     |
 *   |     |
 *   |     +-TM'-> TM +
 *   |                |
 *   +----------------+
 * </pre>
 * <p/>
 * So now the service uses the DefaultTM of the Deploy TMR, and
 * the Service TM properly delegates to the deploy's TM.  And
 * if either the deploy defaultTM or TMs change, the links are not broken.
 * </p>
 *
 * @author James Snell (jasnell@us.ibm.com)
 * @author Sam Ruby (rubys@us.ibm.com)
 *         Re-written for JAX-RPC Compliance by
 * @author Rich Scheuerle (scheu@us.ibm.com
 */
public class TypeMappingRegistryImpl implements TypeMappingRegistry
{

   private HashMap mapTM;          // Type Mappings keyed with Namespace URI
   private TypeMapping defaultDelTM;  // Delegate to default Type Mapping


   /**
    * Construct TypeMappingRegistry
    */
   public TypeMappingRegistryImpl()
   {
      mapTM = new HashMap();
      if (Constants.URI_DEFAULT_SOAP_ENC.equals(Constants.URI_SOAP11_ENC))
      {
         defaultDelTM =
                 new TypeMappingImpl(DefaultTypeMappingImpl.getSingleton());
      }
      else
      {
         defaultDelTM =
                 new TypeMappingImpl(DefaultSOAPEncodingTypeMappingImpl.create());
      }
   }

   /**
    * delegate
    * <p/>
    * Changes the contained type mappings to delegate to
    * their corresponding types in the secondary TMR.
    */
   public void delegate(TypeMappingRegistry secondaryTMR)
   {

      if (secondaryTMR == null || secondaryTMR == this)
      {
         return;
      }
      String[] keys = secondaryTMR.getRegisteredEncodingStyleURIs();
//        String[]  keys = null;
      if (keys != null)
      {
         for (int i = 0; i < keys.length; i++)
         {
            try
            {
               String nsURI = keys[i];
               TypeMapping tm = (TypeMapping)getTypeMapping(nsURI);
               if (tm == null || tm == getDefaultTypeMapping())
               {
                  tm = (TypeMapping)createTypeMapping();
                  tm.setSupportedEncodings(new String[]{nsURI});
                  register(nsURI, tm);
               }

               if (tm != null)
               {
                  // Get the secondaryTMR's TM'
                  TypeMapping del = (TypeMapping)
                          ((TypeMappingRegistryImpl)
                          secondaryTMR).mapTM.get(nsURI);
                  tm.setDelegate(del);
               }

            }
            catch (Exception e)
            {
            }
         }
      }
      // Change our defaultDelTM to delegate to the one in
      // the secondaryTMR
      if (defaultDelTM != null)
      {
         defaultDelTM.setDelegate(((TypeMappingRegistryImpl)secondaryTMR).defaultDelTM);
      }

   }



   /********* JAX-RPC Compliant Method Definitions *****************/

   /**
    * The method register adds a TypeMapping instance for a specific
    * namespace
    *
    * @param namespaceURI
    * @param mapping      - TypeMapping for specific namespaces
    * @return Previous TypeMapping associated with the specified namespaceURI,
    *         or null if there was no TypeMapping associated with the specified namespaceURI
    * @throws JAXRPCException - If there is any error in the registration
    *                         of the TypeMapping for the specified namespace URI
    */
   public javax.xml.rpc.encoding.TypeMapping register(String namespaceURI,
                                                      javax.xml.rpc.encoding.TypeMapping mapping)
   {
//        namespaceURI = "";
      if (mapping == null ||
              !(mapping instanceof TypeMapping))
      {
         throw new IllegalArgumentException(Messages.getMessage("badTypeMapping"));
      }
      if (namespaceURI == null)
      {
         throw new java.lang.IllegalArgumentException(Messages.getMessage("nullNamespaceURI"));
      }
      // Get or create a TypeMappingDelegate and set it to
      // delegate to the new mapping.
      TypeMappingDelegate del = (TypeMappingDelegate)
              mapTM.get(namespaceURI);
      if (del == null)
      {
         del = new TypeMappingDelegate((TypeMapping)mapping);
         mapTM.put(namespaceURI, del);
      }
      else
      {
         del.setDelegate((TypeMapping)mapping);
      }
      return null; // Needs works
   }

   /**
    * The method register adds a default TypeMapping instance.  If a specific
    * TypeMapping is not found, the default TypeMapping is used.
    *
    * @param mapping - TypeMapping for specific type namespaces
    *                <p/>
    *                java.lang.IllegalArgumentException -
    *                if an invalid type mapping is specified or the delegate is already set
    */
   public void registerDefault(javax.xml.rpc.encoding.TypeMapping mapping)
   {
      if (mapping == null ||
              !(mapping instanceof TypeMapping))
      {
         throw new IllegalArgumentException(Messages.getMessage("badTypeMapping"));
      }

      /* Don't allow this call after the delegate() method since
       * the TMR's TypeMappings will be using the default type mapping
       * of the secondary TMR.
       */
      if (defaultDelTM.getDelegate() instanceof TypeMappingDelegate)
      {
         throw new IllegalArgumentException(Messages.getMessage("defaultTypeMappingSet"));
      }

      defaultDelTM.setDelegate((TypeMapping)mapping);
   }

   /**
    * Gets the TypeMapping for the namespace.  If not found, the default
    * TypeMapping is returned.
    *
    * @param namespaceURI - The namespace URI of a Web Service
    * @return The registered TypeMapping
    *         (which may be the default TypeMapping) or null.
    */
   public javax.xml.rpc.encoding.TypeMapping
           getTypeMapping(String namespaceURI)
   {
//        namespaceURI = "";
      TypeMapping del = (TypeMapping)mapTM.get(namespaceURI);
      TypeMapping tm = null;
      if (del != null)
      {
         tm = del.getDelegate();
      }
      if (tm == null)
      {
         tm = (TypeMapping)getDefaultTypeMapping();
      }
      return tm;
   }

   /**
    * Obtain a type mapping for the given encodingStyle.  If no specific
    * mapping exists for this encodingStyle, we will create and register
    * one before returning it.
    *
    * @param encodingStyle
    * @return a registered TypeMapping for the given encodingStyle
    */
   public TypeMapping getOrMakeTypeMapping(String encodingStyle)
   {
      TypeMapping del = (TypeMapping)mapTM.get(encodingStyle);
      TypeMapping tm = null;
      if (del != null)
      {
         tm = del.getDelegate();
      }
      if (tm == null)
      {
         tm = (TypeMapping)createTypeMapping();
         tm.setSupportedEncodings(new String[]{encodingStyle});
         register(encodingStyle, tm);
      }
      return tm;
   }

   /**
    * Unregisters the TypeMapping for the namespace.
    *
    * @param namespaceURI - The namespace URI
    * @return The registered TypeMapping .
    */
   public javax.xml.rpc.encoding.TypeMapping
           unregisterTypeMapping(String namespaceURI)
   {
      TypeMapping del = (TypeMapping)mapTM.get(namespaceURI);
      TypeMapping tm = null;
      if (del != null)
      {
         tm = del.getDelegate();
         del.setDelegate(null);
      }
      return tm;
   }

   /**
    * Removes the TypeMapping for the namespace.
    *
    * @param mapping The type mapping to remove
    * @return true if found and removed
    */
   public boolean removeTypeMapping(javax.xml.rpc.encoding.TypeMapping mapping)
   {
      String[] ns = getRegisteredEncodingStyleURIs();
      boolean rc = false;
      for (int i = 0; i < ns.length; i++)
      {
         if (getTypeMapping(ns[i]) == mapping)
         {
            rc = true;
            unregisterTypeMapping(ns[i]);
         }
      }
      return rc;
   }

   /**
    * Creates a new empty TypeMapping object for the specified
    * encoding style or XML schema namespace.
    *
    * @return An empty generic TypeMapping object
    */
   public javax.xml.rpc.encoding.TypeMapping createTypeMapping()
   {
      return new TypeMappingImpl(defaultDelTM);
   }


   /**
    * Gets a list of namespace URIs registered with this TypeMappingRegistry.
    *
    * @return String[] containing names of all registered namespace URIs
    */
   public String[] getRegisteredEncodingStyleURIs()
   {
      java.util.Set s = mapTM.keySet();
      if (s != null)
      {
         String[] rc = new String[s.size()];
         int i = 0;
         java.util.Iterator it = s.iterator();
         while (it.hasNext())
         {
            rc[i++] = (String)it.next();
         }
         return rc;
      }
      return null;
   }


   /**
    * Removes all TypeMappings and namespaceURIs from this TypeMappingRegistry.
    */
   public void clear()
   {
      mapTM.clear();
   }

   /**
    * Return the default TypeMapping
    *
    * @return TypeMapping or null
    */
   public javax.xml.rpc.encoding.TypeMapping getDefaultTypeMapping()
   {
      TypeMapping defaultTM = defaultDelTM;
      while (defaultTM != null && defaultTM instanceof TypeMappingDelegate)
      {
         defaultTM = defaultTM.getDelegate();
      }
      return defaultTM;
   }

}