/*
 * Copyright 2000-2006 Sun Microsystems, Inc.  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.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package com.sun.corba.se.impl.ior;

import java.util.ListIterator ;
import java.util.Iterator ;
import java.util.Map ;
import java.util.HashMap ;

import java.io.StringWriter;
import java.io.IOException;

import javax.rmi.CORBA.Util;

import org.omg.CORBA_2_3.portable.InputStream ;
import org.omg.CORBA_2_3.portable.OutputStream ;

import org.omg.IOP.TAG_INTERNET_IOP ;

import com.sun.corba.se.spi.ior.ObjectId ;
import com.sun.corba.se.spi.ior.TaggedProfileTemplate ;
import com.sun.corba.se.spi.ior.TaggedProfile ;
import com.sun.corba.se.spi.ior.IOR ;
import com.sun.corba.se.spi.ior.IORTemplate ;
import com.sun.corba.se.spi.ior.IORTemplateList ;
import com.sun.corba.se.spi.ior.IdentifiableFactoryFinder ;
import com.sun.corba.se.spi.ior.IdentifiableContainerBase ;
import com.sun.corba.se.spi.ior.ObjectKeyTemplate ;
import com.sun.corba.se.spi.ior.IORFactories ;

import com.sun.corba.se.spi.ior.iiop.GIOPVersion;

import com.sun.corba.se.spi.protocol.RequestDispatcherRegistry;

import com.sun.corba.se.spi.orb.ORB;

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

import com.sun.corba.se.impl.encoding.MarshalOutputStream;

import com.sun.corba.se.impl.encoding.EncapsOutputStream;

import com.sun.corba.se.impl.orbutil.HexOutputStream;
import com.sun.corba.se.impl.orbutil.ORBConstants;

import com.sun.corba.se.impl.logging.IORSystemException ;

// XXX remove this once getProfile is gone
import com.sun.corba.se.spi.ior.iiop.IIOPProfile ;

/** An IOR is represented as a list of profiles.
* Only objects that extend TaggedProfile should be added to an IOR.
* However, enforcing this restriction requires overriding all
* of the addXXX methods inherited from List, so no check
* is included here.
* @author Ken Cavanaugh
*/
public class IORImpl extends IdentifiableContainerBase implements IOR
{
    private String typeId;
    private ORB factory = null ;
    private boolean isCachedHashValue = false;
    private int cachedHashValue;
    IORSystemException wrapper ;

    public ORB getORB()
    {
        return factory ;
    }

    /* This variable is set directly from the constructors that take
     * an IORTemplate or IORTemplateList as arguments; otherwise it
     * is derived from the list of TaggedProfile instances on the first
     * call to getIORTemplates.  Note that we assume that an IOR with
     * mutiple TaggedProfile instances has the same ObjectId in each
     * TaggedProfile, as otherwise the IOR could never be created through
     * an ObjectReferenceFactory.
     */
    private IORTemplateList iortemps = null ;

    public boolean equals( Object obj )
    {
        if (obj == null)
            return false ;

        if (!(obj instanceof IOR))
            return false ;

        IOR other = (IOR)obj ;

        return super.equals( obj ) && typeId.equals( other.getTypeId() ) ;
    }

    public synchronized int hashCode()
    {
        if (! isCachedHashValue) {
              cachedHashValue =  (super.hashCode() ^ typeId.hashCode());
              isCachedHashValue = true;
        }
        return cachedHashValue;
    }

    /** Construct an empty IOR.  This is needed for null object references.
    */
    public IORImpl( ORB orb )
    {
        this( orb, "" ) ;
    }

    public IORImpl( ORB orb, String typeid )
    {
        factory = orb ;
        wrapper = IORSystemException.get( orb,
            CORBALogDomains.OA_IOR ) ;
        this.typeId = typeid ;
    }

    /** Construct an IOR from an IORTemplate by applying the same
    * object id to each TaggedProfileTemplate in the IORTemplate.
    */
    public IORImpl( ORB orb, String typeId, IORTemplate iortemp, ObjectId id)
    {
        this( orb, typeId ) ;

        this.iortemps = IORFactories.makeIORTemplateList() ;
        this.iortemps.add( iortemp ) ;

        addTaggedProfiles( iortemp, id ) ;

        makeImmutable() ;
    }

    private void addTaggedProfiles( IORTemplate iortemp, ObjectId id )
    {
        ObjectKeyTemplate oktemp = iortemp.getObjectKeyTemplate() ;
        Iterator templateIterator = iortemp.iterator() ;

        while (templateIterator.hasNext()) {
            TaggedProfileTemplate ptemp =
                (TaggedProfileTemplate)(templateIterator.next()) ;

            TaggedProfile profile = ptemp.create( oktemp, id ) ;

            add( profile ) ;
        }
    }

    /** Construct an IOR from an IORTemplate by applying the same
    * object id to each TaggedProfileTemplate in the IORTemplate.
    */
    public IORImpl( ORB orb, String typeId, IORTemplateList iortemps, ObjectId id)
    {
        this( orb, typeId ) ;

        this.iortemps = iortemps ;

        Iterator iter = iortemps.iterator() ;
        while (iter.hasNext()) {
            IORTemplate iortemp = (IORTemplate)(iter.next()) ;
            addTaggedProfiles( iortemp, id ) ;
        }

        makeImmutable() ;
    }

    public IORImpl(InputStream is)
    {
        this( (ORB)(is.orb()), is.read_string() ) ;

        IdentifiableFactoryFinder finder =
            factory.getTaggedProfileFactoryFinder() ;

        EncapsulationUtility.readIdentifiableSequence( this, finder, is ) ;

        makeImmutable() ;
    }

    public String getTypeId()
    {
        return typeId ;
    }

    public void write(OutputStream os)
    {
        os.write_string( typeId ) ;
        EncapsulationUtility.writeIdentifiableSequence( this, os ) ;
    }

    public String stringify()
    {
        StringWriter bs;

        MarshalOutputStream s = new EncapsOutputStream(factory);
        s.putEndian();
        write( (OutputStream)s );
        bs = new StringWriter();
        try {
            s.writeTo(new HexOutputStream(bs));
        } catch (IOException ex) {
            throw wrapper.stringifyWriteError( ex ) ;
        }

        return ORBConstants.STRINGIFY_PREFIX + bs;
    }

    public synchronized void makeImmutable()
    {
        makeElementsImmutable() ;

        if (iortemps != null)
            iortemps.makeImmutable() ;

        super.makeImmutable() ;
    }

    public org.omg.IOP.IOR getIOPIOR() {
        EncapsOutputStream os = new EncapsOutputStream(factory);
        write(os);
        InputStream is = (InputStream) (os.create_input_stream());
        return org.omg.IOP.IORHelper.read(is);
    }

    public boolean isNil()
    {
        //
        // The check for typeId length of 0 below is commented out
        // as a workaround for a bug in ORBs which send a
        // null objref with a non-empty typeId string.
        //
        return ((size() == 0) /* && (typeId.length() == 0) */);
    }

    public boolean isEquivalent(IOR ior)
    {
        Iterator myIterator = iterator() ;
        Iterator otherIterator = ior.iterator() ;
        while (myIterator.hasNext() && otherIterator.hasNext()) {
            TaggedProfile myProfile = (TaggedProfile)(myIterator.next()) ;
            TaggedProfile otherProfile = (TaggedProfile)(otherIterator.next()) ;
            if (!myProfile.isEquivalent( otherProfile ))
                return false ;
        }

        return myIterator.hasNext() == otherIterator.hasNext() ;
    }

    private void initializeIORTemplateList()
    {
        // Maps ObjectKeyTemplate to IORTemplate
        Map oktempToIORTemplate = new HashMap() ;

        iortemps = IORFactories.makeIORTemplateList() ;
        Iterator iter = iterator() ;
        ObjectId oid = null ; // used to check that all profiles have the same oid.
        while (iter.hasNext()) {
            TaggedProfile prof = (TaggedProfile)(iter.next()) ;
            TaggedProfileTemplate ptemp = prof.getTaggedProfileTemplate() ;
            ObjectKeyTemplate oktemp = prof.getObjectKeyTemplate() ;

            // Check that all oids for all profiles are the same: if they are not,
            // throw exception.
            if (oid == null)
                oid = prof.getObjectId() ;
            else if (!oid.equals( prof.getObjectId() ))
                throw wrapper.badOidInIorTemplateList() ;

            // Find or create the IORTemplate for oktemp.
            IORTemplate iortemp = (IORTemplate)(oktempToIORTemplate.get( oktemp )) ;
            if (iortemp == null) {
                iortemp = IORFactories.makeIORTemplate( oktemp ) ;
                oktempToIORTemplate.put( oktemp, iortemp ) ;
                iortemps.add( iortemp ) ;
            }

            iortemp.add( ptemp ) ;
        }

        iortemps.makeImmutable() ;
    }

    /** Return the IORTemplateList for this IOR.  Will throw
     * exception if it is not possible to generate an IOR
     * from the IORTemplateList that is equal to this IOR,
     * which can only happen if not every TaggedProfile in the
     * IOR has the same ObjectId.
     */
    public synchronized IORTemplateList getIORTemplates()
    {
        if (iortemps == null)
            initializeIORTemplateList() ;

        return iortemps ;
    }

    /** Return the first IIOPProfile in this IOR.
     * XXX THIS IS TEMPORARY FOR BACKWARDS COMPATIBILITY AND WILL BE REMOVED
     * SOON!
     */
    public IIOPProfile getProfile()
    {
        IIOPProfile iop = null ;
        Iterator iter = iteratorById( TAG_INTERNET_IOP.value ) ;
        if (iter.hasNext())
            iop = (IIOPProfile)(iter.next()) ;

        if (iop != null)
            return iop ;

        // if we come to this point then no IIOP Profile
        // is present.  Therefore, throw an exception.
        throw wrapper.iorMustHaveIiopProfile() ;
    }
}
