/*
 * Copyright (c) 2002, 2003, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package com.sun.corba.se.impl.naming.namingutil;

import java.util.*;

import com.sun.corba.se.spi.logging.CORBALogDomains ;
import com.sun.corba.se.impl.logging.NamingSystemException ;

/**
 *  The corbaloc: URL definitions from the -ORBInitDef and -ORBDefaultInitDef's
 *  will be parsed and converted to  this object. This object is capable of
 *  storing multiple  Host profiles as defined in the CorbaLoc grammer.
 *
 *  @author  Hemanth
 *  @author <a href="mailto:tadamski@redhat.com">Tomasz Adamski</a>
 */
public class CorbalocURL extends INSURLBase
{
    static NamingSystemException wrapper = NamingSystemException.get(
        CORBALogDomains.NAMING_READ ) ;

    /**
     * This constructor parses the URL and initializes all the variables. Once
     * the URL Object is constructed it is immutable. URL parameter is a
     * corbaloc: URL string with 'corbaloc:' prefix stripped.
     */
    public CorbalocURL( String aURL ) {
        String url = aURL;

        if( url != null ) {
            try {
                // First Clean the URL Escapes if there are any
                url = Utility.cleanEscapes( url );
            } catch( Exception e ) {
                // There is something wrong with the URL escapes used
                // so throw an exception
                badAddress( e );
            }
            int endIndex = url.indexOf( '/' );
            if( endIndex == -1 ) {
                // If there is no '/' then the endIndex is at the end of the URL
                endIndex = url.length();
            }
            // _REVISIT_: Add a testcase to check 'corbaloc:/'
            if( endIndex == 0 )  {
                // The url starts with a '/', it's an error
                badAddress( null );
            }
            // Anything between corbaloc: and / is the host,port information
            // of the server where the Service Object is located
            StringTokenizer endpoints = new StringTokenizer(
                url.substring( 0, endIndex ), "," );
            // NOTE:
            // There should be atleast one token, because there are checks
            // to make sure that there is host information before the
            // delimiter '/'. So no need to explicitly check for number of
            // tokens != 0
            while( endpoints.hasMoreTokens( ) ) {
                String endpointInfo = endpoints.nextToken();
                IIOPEndpointInfo iiopEndpointInfo = null;
                if( endpointInfo.startsWith( "iiop:" ) ) {
                    endpointInfo = endpointInfo.substring( "iiop".length() );
                    iiopEndpointInfo = parseEndpointInfo( endpointInfo, false);
                } else if (endpointInfo.startsWith("ssliop:")){
                    endpointInfo = endpointInfo.substring( "ssliop".length() );
                    iiopEndpointInfo = parseEndpointInfo( endpointInfo, true );
                } else if (endpointInfo.startsWith( "rir:" )) {
                    handleRIRColon( endpointInfo );
                    rirFlag = true;
                } else if( endpointInfo.startsWith( ":" ) ) {
                    iiopEndpointInfo = parseEndpointInfo( endpointInfo, false );
                } else {
                    // Right now we are not allowing any other protocol
                    // other than iiop:, rir: so raise exception indicating
                    // that the URL is malformed
                    badAddress( null );
                }
                if ( rirFlag == false ) {
                    // Add the Host information if RIR flag is set,
                    // If RIR is set then it means use the internal Boot
                    // Strap protocol for Key String resolution
                    if( theEndpointInfo == null ) {
                        theEndpointInfo = new java.util.ArrayList( );
                    }
                    theEndpointInfo.add( iiopEndpointInfo );
                }
            }
            // If there is something after corbaloc:endpointInfo/
            // then that is the keyString
            if( url.length() > (endIndex + 1) ) {
                theKeyString = url.substring( endIndex + 1 );
            }
        }
    }


    /**
     *  A Utility method to throw BAD_PARAM exception to signal malformed
     *  INS URL.
     */
    private void badAddress( java.lang.Throwable e )
    {
        throw wrapper.insBadAddress( e ) ;
    }

    /**
     *  If there is 'iiop:' or 'ssliop:' token in the URL, this method will parses
     *  and  validates that host and port information.
     */

    /**
     * This is to handle the case of host information with no 'iiop:' prefix.
     * instead if ':' is specified then iiop is assumed.
     */
    private IIOPEndpointInfo parseEndpointInfo( String iiopInfo, boolean secured) {
        IIOPEndpointInfo iiopEndpointInfo = new IIOPEndpointInfo();
        if( secured) {
            iiopEndpointInfo.setSecured(true);
        }
        // String after ":"
         iiopInfo = iiopInfo.substring( 1 );
         String hostandport = iiopInfo;
         // The format can be 1.2@<host>:<port>
         StringTokenizer tokenizer = new StringTokenizer( iiopInfo, "@" );
         int tokenCount = tokenizer.countTokens( );
         // There can be 1 or 2 tokens with '@' as the delimiter
         //  - if there is only 1 token then there is no GIOP version
         //    information.  A Default GIOP version of 1.2 is used.
         //  - if there are 2 tokens then there is GIOP version is specified
         //  - if there are no tokens or more than 2 tokens, then that's an
         //    error
         if( ( tokenCount == 0 )
           ||( tokenCount > 2 ))
         {
             badAddress( null );
         }
         if( tokenCount == 2 ) {
            // There is VersionInformation after iiop:
            String version     = tokenizer.nextToken( );
            int dot = version.indexOf('.');
            // There is a version without ., which means
            // Malformed list
            if (dot == -1) {
                badAddress( null );
            }
            try {
                iiopEndpointInfo.setVersion(
                    Integer.parseInt( version.substring( 0, dot )),
                    Integer.parseInt( version.substring(dot+1)) );
                hostandport = tokenizer.nextToken( );//.substring(1);
            } catch( Throwable e ) {
                badAddress( e );
            }
         }
         try {
           // A Hack to differentiate IPV6 address
           // from IPV4 address, Current Resolution
           // is to use [ ] to differentiate ipv6 host
           int squareBracketBeginIndex = hostandport.indexOf ( '[' );
           if( squareBracketBeginIndex != -1 ) {
               // ipv6Host should be enclosed in
               // [ ], if not it will result in a
               // BAD_PARAM exception
               String ipv6Port = getIPV6Port( hostandport );
               if( ipv6Port != null ) {
                   iiopEndpointInfo.setPort( Integer.parseInt( ipv6Port ));
               }
               iiopEndpointInfo.setHost( getIPV6Host( hostandport ));
               return iiopEndpointInfo;
           }
           tokenizer = new StringTokenizer( hostandport, ":" );
           // There are three possible cases here
           // 1. Host and Port is explicitly specified by using ":" as a
           //    a separator
           // 2. Only Host is specified without the port
           // 3. HostAndPort info is null
           if( tokenizer.countTokens( ) == 2 ) {
               // Case 1: There is Host and Port Info
               iiopEndpointInfo.setHost( tokenizer.nextToken( ) );
               iiopEndpointInfo.setPort( Integer.parseInt(
                   tokenizer.nextToken( )));
           } else {
               if( ( hostandport != null )
                 &&( hostandport.length() != 0 ) )
               {
                   // Case 2: Only Host is specified. iiopEndpointInfo is
                   // initialized to use the default INS port, if no port is
                   // specified
                   iiopEndpointInfo.setHost( hostandport );
               }
               // Case 3: If no Host and Port info is provided then we use the
               // the default LocalHost and INSPort. iiopEndpointInfo is
               // already initialized with this info.
           }
       } catch( Throwable e ) {
           // Any kind of Exception is bad here.
           // Possible causes: A Number Format exception because port info is
           // malformed
           badAddress( e );
       }
       Utility.validateGIOPVersion( iiopEndpointInfo );
       return iiopEndpointInfo;
    }

    /**
     *  Validate 'rir:' case.
     */
    private void handleRIRColon( String rirInfo )
    {
        if( rirInfo.length() != NamingConstants.RIRCOLON_LENGTH ) {
            badAddress( null );
        }
    }

    /**
      * Returns an IPV6 Port that is after [<ipv6>]:. There is no validation
      * done here, if it is an incorrect port then the request through
      * this URL results in a COMM_FAILURE, otherwise malformed list will
      * result in BAD_PARAM exception thrown in checkcorbalocGrammer.
      */
     private String getIPV6Port( String endpointInfo )
     {
         int squareBracketEndIndex = endpointInfo.indexOf ( ']' );
         // If there is port information, then it has to be after ] bracket
         // indexOf returns the count from the index of zero as the base, so
         // equality check requires squareBracketEndIndex + 1.
         if( (squareBracketEndIndex + 1) != (endpointInfo.length( )) ) {
             if( endpointInfo.charAt( squareBracketEndIndex + 1 ) != ':' ) {
                  throw new RuntimeException(
                      "Host and Port is not separated by ':'" );
             }
             // PortInformation  should be after ']:' delimiter
             // If there is an exception then it will be caught in
             // checkcorbaGrammer method and rethrown as BAD_PARAM
             return endpointInfo.substring( squareBracketEndIndex + 2 );
         }
         return null;
     }


     /**
      * Returns an IPV6 Host that is inside [ ] tokens. There is no validation
      * done here, if it is an incorrect IPV6 address then the request through
      * this URL results in a COMM_FAILURE, otherwise malformed list will
      * result in BAD_PARAM exception thrown in checkcorbalocGrammer.
      */
     private String getIPV6Host( String endpointInfo ) {
          // ipv6Host should be enclosed in
          // [ ], if not it will result in a
          // BAD_PARAM exception
          int squareBracketEndIndex = endpointInfo.indexOf ( ']' );
          // get the host between [ ]
          String ipv6Host = endpointInfo.substring( 1, squareBracketEndIndex  );
          return ipv6Host;
    }

    /**
     * Will be true only in CorbanameURL class.
     */
    public boolean isCorbanameURL( ) {
        return false;
    }


}
