package jacorb.orb.domain;


import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;
import org.omg.CORBA.*;
import jacorb.util.Debug;

/**
 * DomainImpl.java
 * implementation of IDL-interface jacorb.orb.domain.Domain <br>
 * 
 * @author Herbert Kiefer
 * @version $Revision: 1.1 $
 */
public class DomainImpl extends DomainPOA {
  

  /** the factory this domain  uses to create other domain services */
  private DomainFactory _factory; 

  /** an informal description of the usage of the managed domain */
  String _description;  

  // Hashtables with the identity function are used to implement sets

  /** set of members */
  private Hashtable _members     = new Hashtable(); 


  /** the list of domain policies */
  private Hashtable _policies    = new Hashtable();

  /** indicates the type of policy this domain is mainly used for */
  private Integer _defaultPolicyType= null; // used as index into hashtable

 /** the child domain  registered at this domain  */
  private Hashtable _child_domains= new Hashtable();

  /** the list of parent domains of this domain */
  protected Hashtable _parents   = new Hashtable();
  
  public final static int UNDEF = -1;

  // ***** constructors *****


  /** creates a domain.
   *  The domain is initially empty, contains no policies, has a
   *  undefined default policy type and the description is the empty 
   *  string.
   */
  public DomainImpl()
  {
    this(null, null, UNDEF, null);
  }
  
  /** creates a domain.
   *  The domain gets the specified list as initial members, contains no policies,
   *  has a undefined default policy type and the description is the empty 
   *  string.
   *  @param initialMembers a list of objects which shall become 
   *                        members of the domain
   */
  public DomainImpl(org.omg.CORBA.Object[] initialMembers)
  {
    this(initialMembers, null, UNDEF, null);
  }
 
 
  public DomainImpl(org.omg.CORBA.Object[] initialMembers, 
			   int   mainPolicyType)

  { 
    this(initialMembers, null, mainPolicyType, "");
  }
  
  /** creates a domain.
   *  The newly created domain contains the list of initial members,
   *  has the policies from the list of initial policies set and the
   *  the parameter mainPolicyType specifies the default policy type.
   *  string.
   */
  public DomainImpl(org.omg.CORBA.Object[] initialMembers, 
		    org.omg.CORBA.Policy[] initialPolicies,
		    int mainPolicyType)

  { 
    this(initialMembers, initialPolicies, mainPolicyType, "");
  }

  /** Creates a domain object. A set of initial members, a
      default policy type, a set of initial minor policies and an informal 
      description are specified by the parameters.
  */
  public DomainImpl(org.omg.CORBA.Object[] initialMembers, 
		    org.omg.CORBA.Policy[] initialPolicies,
		    int                    mainPolicyType,
		    String                 description) 
  {
    _description= description;

    // set members
    if (initialMembers != null)
      for (int i=0; i<initialMembers.length; i++)  
	_members.put(initialMembers[i], initialMembers[i]);
    else Debug.output(2, "DMImpl.init: list of initial members is empty");

    // set policies
    // assuming Integer.hashCode() == Integer.intValue() !
    if (initialPolicies != null)
      {
	for (int i=0; i<initialPolicies.length; i++) 
	  { // set policies
	    _policies.put(new Integer ( initialPolicies[i].policy_type() )
			  , initialPolicies[i] );
	  }
      } 
    else Debug.output(2,"DMImpl.init: list of initial policies is empty");
    
    // set main policy type
    if (mainPolicyType >= 0) 
      {
	_defaultPolicyType= new Integer(mainPolicyType);
      }
    else Debug.output(1, "DomainImpl.init(WARNING): no default policy type specified");
  }


  // ******************* factory operations *******************************

 /** creates a new domain.
   *  @return - an object reference to a newly created domain  */
  public Domain createDomain
    (org.omg.CORBA.Object[] initialMembers,
     org.omg.CORBA.Policy[] initialPolicies,
     int                    defaultPolicyType,
     String                 description)
  {
    // use factory 
    if (_factory == null) 
      {
	DomainFactoryImpl factoryImpl= new DomainFactoryImpl();
	// use the poa of this domain to register servant
	try 
	  {
	    _factory= DomainFactoryHelper.narrow( _poa().servant_to_reference(factoryImpl) );
	  } 
	catch (org.omg.PortableServer.POAPackage.WrongPolicy wp) 
	  {
	    jacorb.util.Debug.output(1, "the poa of this domain(" + this 
	         +") has the wrong policies for \"servant_to_reference\".");
	  }
	catch (Exception e)
	  {
	    jacorb.util.Debug.output(1, e);
	  } 
      }

    return _factory.createDomain(initialMembers, initialPolicies, 
				 defaultPolicyType, description);
  }

  /** sets the description.
   *  @param description an informal description of the usage of 
   *   the managed domain 
   */
  public void description(String description) 
  { _description= description; }

  /** gets the description.
   *  @return an informal description of the usage of the managed domain 
   */
  public String description() 
  { return _description; }

  // policy operations


  /** retrieves a policy.
   *  @param policy_type the type of policy to be retrieved
   *  @exception INV_POLICY if this domain currently doesn't have 
   *             a policy of the type "policy type"
   */
  public org.omg.CORBA.Policy get_domain_policy(int policy_type) {

    Policy result= null;

    java.lang.Object hashResult= _policies.get(new Integer (policy_type) );
    if (hashResult != null)  
      result= (org.omg.CORBA.Policy) hashResult; // main case
    else if ( !isRoot() )
      {  // check parents if this domain does not have the wanted policy
	result= getParentPolicy(policy_type);
      }
    else // policy not found in this domain
      throw (new INV_POLICY("DM.get_domain_policy: this domain " + this
			    +"doesn't have a policy of type " + policy_type));
	
    
    return result;
  } // get_domain_policy

  /** uses (one of) the parents to get the policy of the specified type.
   *  Pre: !isRoot() */
  protected org.omg.CORBA.Policy getParentPolicy(int policy_type)
  {
    jacorb.util.Debug.assert(2, !isRoot(), "DMImpl.getParentPolicy: precondition violated");

    Domain ds;
    Enumeration objectEnum= _parents.keys();

    if (_parents.size() == 1 )
      { // fine, only one parent, ask this one
	ds= DomainHelper.narrow
	  ( (org.omg.CORBA.Object) objectEnum.nextElement() );
	return ds.get_domain_policy(policy_type);
      }
    else
      { // potentially more than one parent incorporated, ask all
	Vector dsList= new Vector();
	while ( objectEnum.hasMoreElements() ) 
	  {
	    ds= DomainHelper.narrow
	      ( (org.omg.CORBA.Object) objectEnum.nextElement() );
	    try { ds.get_domain_policy(policy_type); }
	    // don't consider ds which does not have the right policy type
	    catch (INV_POLICY invalid) { continue; }    
	    dsList.addElement(ds);
	  }
	// now we have a list of ds with all overlap in the wanted policy type
	// first check if list is nonempty
	int n= dsList.size();
	if ( n < 1 ) throw (new INV_POLICY("DM.get_domain_policy: this domain " + this
					   +"doesn't have a policy of type " + policy_type));
	else if (n == 1) // only one element, use this one
	  return ((Domain) dsList.firstElement()).get_domain_policy(policy_type);
	else 
	  { // use conflict resolution policy to choose one
	    ConflictResolutionPolicy resolver;
	    if (policy_type == CONFLICT_RESOLUTION_POLICY_ID.value)
	      { // this is bad: we have more than one parent domain with a conflicting
		// resolution policy; to resolve this conflict, we need a resolution policy ...
		// if we do it the usual way, we will end up in a non-ending recursion ...
		// the hardcoded ! (re)solution is to simply use the first one
	        return ((Domain) dsList.firstElement()).get_domain_policy(policy_type);
	      }
	    try { 
	      resolver= ConflictResolutionPolicyHelper.narrow 
		( get_domain_policy(CONFLICT_RESOLUTION_POLICY_ID.value));
	    } catch (INV_POLICY invalid) { 
	      // no conflict resolution policy defined, but we need one, 
	      // so define a default one and insert it into policy list
	      resolver= (new ConflictResolutionPolicyPOATie
			 (new SimpleConflictResolutionPolicy() ))._this();
	      this.set_domain_policy(resolver);
	    }
	    Domain overlappingDS[]= new Domain[dsList.size()];
	    dsList.copyInto(overlappingDS);
	    Domain dm= resolver.resolveConflict(overlappingDS, policy_type);
	    return dm.get_domain_policy(policy_type);
	  }
      }
    
  } // getParentPolicy

  /** sets the specified policy.
   *  invarian: at most one policy of every type may exist .
   *  an already existing policy of the same type of pol may be overwritten
   *  @param pol the policy to be set in this domain
   */
  public void set_domain_policy(org.omg.CORBA.Policy pol) {
    
    int key= pol.policy_type();
    if ( _policies.containsKey( new Integer (key) ) ) 
      Debug.output(1,"Domain.set_domain_policy:overwriting policy of type "+key);
      
    _policies.put( new Integer(key), pol );
    Debug.output(1,"Domain.set_domain_policy:setting policy of type "+key);

  } // set_domain_policy

  /** sets the specified policy as the default policy */
  public void defaultPolicyType(int type) 
  {
    _defaultPolicyType= new Integer(type);
  } // defaultPolicyType



  /** returns the default policy */
  public int defaultPolicyType() 
  {
    return _defaultPolicyType.intValue();
  } // defaultPolicyType



  // ****** service operations *********

  
  /** returns the domains  the object is associated with */
  public Domain[] getDomains(org.omg.CORBA.Object obj)
  {
    Hashtable parents= new Hashtable();
    Hashtable childs = new Hashtable();
    Domain self= _this();

    int me= 0;
    if ( self.hasMember(obj) ) 
      {

	getParentsOfAnyLevel(self, parents);
	me= 1;
      }
    getChildsOfAnyLevelWithObject(obj, self, childs);

    // copy hashtable entries to result array
    Domain[] result= new Domain[parents.size()+me+childs.size()];
    int i= 0;
  
  
    if (me == 1) // include self and parents
      {
	result[0]= self;
	i= 1; // adjust index
	Enumeration parentsEnum= parents.keys();
	while ( parentsEnum.hasMoreElements() ) 
	  {
	    result[i]= (Domain) parentsEnum.nextElement() ;
	    i++;
	  }
      }
    
    Enumeration childsEnum =  childs.keys();
    while ( childsEnum.hasMoreElements() ) 
      {
	result[i]= (Domain) childsEnum.nextElement() ;
	i++;
      }
    jacorb.util.Debug.assert(2, result != null, "Domain.getDomains result is null");	    
    return result;

  } // getDomains


    /** returns the policy of the specified type for an object.
     *	@return Policy the policy of type "type" the object "obj" has
     *  @exception INV_POLICY if the object does not have a policy of that type
     */
    public org.omg.CORBA.Policy getPolicy(org.omg.CORBA.Object obj,int type) 
    {
	org.omg.CORBA.Policy pol= null;
	Vector targetDomains= new Vector();

	// first main step
	Domain domains[]= getDomains(obj);
	jacorb.util.Debug.assert(2, domains != null," Domain.getPolicy: "
				 +"result of getDomains is null");

	for (int i= 0; i < domains.length; i++) 
	  {
	    try 
	      { 
		// try to get policy from domain: second main step
		jacorb.util.Debug.assert(2, domains[i] != null,"Domain.getPolicy:"
					 +" array entry is null");
		pol= domains[i].get_domain_policy(type);
  
		// if successful add to found list
		targetDomains.addElement(domains[i]);     
	      }
	    // if no success skip and try next one
	    catch (INV_POLICY invalid) { continue; }    
	  }

	int n= targetDomains.size(); 
	if (n == 1) // fine, one domain has had the needed policy type,
	            // so pol is set to the policy of this domain 
	  return pol;
	else if (n > 1)
	  {
	    jacorb.util.Debug.output(2, "Domain.getPolicy: there is more than one domain (n= " +n
	      +") with the wanted policy, using conflict resolution for overlapping domains...");
	    return doConflictResolution(targetDomains, type);
	  }
	// else ( n < 1) <=> not found
	else throw (new INV_POLICY("obj " + obj + " does not have a policy of type " + type));

    } // getPolicy

  public org.omg.CORBA.Object set_policy_override(org.omg.CORBA.Object obj, 
						  org.omg.CORBA.Policy[] ps, 
						  boolean add)
						  
  { // TODO
    return obj;
  }

  // ***** object member operations *****

  /** inserts a member object into this domain */
  public void insertMember(org.omg.CORBA.Object obj)
  { _members.put(obj, obj); }
  
  /** deletes a member object from this domain */
  public void deleteMember(org.omg.CORBA.Object obj)
  { _members.remove(obj); }

  /** checks whether this domain has the specified member */
  public boolean hasMember(org.omg.CORBA.Object obj)
  { return _members.containsKey(obj); }

  /** lists the object members of this domain */
  public org.omg.CORBA.Object[] getMembers(){ 
    Enumeration objectEnum= _members.keys();
    // convert Enumeration to array
    org.omg.CORBA.Object[] result= new org.omg.CORBA.Object[_members.size()];
    int i= 0;
    while ( objectEnum.hasMoreElements() ) {
      result[i]= (org.omg.CORBA.Object) objectEnum.nextElement();
      i++;
    }
    return result;
  } // members

  // parent operations


  /** checks whether this domain is a root domain in a potientallly
   *  domain hierarchy.
   *  @return True iff this domain has no parents */
  public boolean isRoot()
  { return _parents.size() == 0; }
    
  /** gets the root domain.
      @return the root domain in the scope of this domain  */
  public jacorb.orb.domain.Domain getRootDomain() 
  { 
    if ( isRoot() ) return _this();

    // else search the graph upwards

     Enumeration domainList= _parents.keys();
    // convert Enumeration to array
    Domain ds, oldfoundRoot= null, foundRoot;    
    while ( domainList.hasMoreElements() ) 
      {
	ds= (Domain) domainList.nextElement();
	jacorb.util.Debug.output(2, "Domain(" + _description +  ").getRootDomain: calling getRootDomain"
				 +", parent is " + ds.description());
	foundRoot= ds.getRootDomain();
	jacorb.util.Debug.assert(1, foundRoot != null,"DMImpl.getRootDomain:"
				 + " found root is null");
	if (oldfoundRoot == null) oldfoundRoot= foundRoot;       // first step
	else 
	  { // check equality
	    if (oldfoundRoot._is_equivalent(foundRoot)) ; // that's ok
	    else throw new jacorb.util.AssertionViolation
		   ("DMImpl.getRootDomain: invariant of "
		    +" unique root violated");
	  }

      }
    return oldfoundRoot;
  }


  /************************ child operations ***********************/


  /** inserts a child domain into this domain.
   *  Pre: NOT child.containsDomain(this)  (to prevent cycles)
   *       the precondition of Domain.addParent also needs to be satisfied
   Post: this.hasChild(child) AND child.hasParent(this)
  */
  public void insertChild(Domain child)
  {
    
    Domain self= _this();
    jacorb.util.Debug.assert(2, child != null, "child is null");
    jacorb.util.Debug.assert(2, self != null, "self is null");

    // check precondition
    jacorb.util.Debug.assert(2, child != null && ! child.containsDomain(self),
			     "precondition of Domain.insertChild violated");
    jacorb.util.Debug.output(2,"Domain("+_description+"): insert child "+child.description());

    _child_domains.put(child, child);

    if ( child.hasParent( self ) );
    else child.insertParent(self); // only insert, if not already there

    // check postcondition
    jacorb.util.Debug.assert(2, self.hasChild(child) && child.hasParent(self), 
			     "post condition of Domain.insertChild violated");
	    
  } // insertChild
    
  /** removes a child domain from the domain.
   */
  public void deleteChild(Domain dm) 
  {
    Domain self= _this();

    _child_domains.remove(dm);

    if ( dm.hasParent( self ) )
      dm.deleteParent( self );

    jacorb.util.Debug.assert(2, ! dm.hasParent(self) && ! self.hasChild(dm),
			     "DSImpl.deleteChild: post condition violated");

  } // deleteChild

  /** checks whether this domain has the specified domain as
   *  child domain.
   *  @return true iff domain_manager is a direct child of this domain 
   */
  public boolean hasChild(Domain aDomain)
  {
    jacorb.util.Debug.assert(2, aDomain != null," Domain.hasChild: parameter \"aDomain\" is null.");
    return _child_domains.containsKey(aDomain);
  } 

   /** gets the child domains of this domain service.
    *  @return an array of all child domains of this domain. There is no order in the
    *          returned list.
    */
  public Domain[] getChilds()
  {
    Domain result[]= new Domain[_child_domains.size()];
    Enumeration objectEnum= _child_domains.keys();
    // convert enumeration to array
    int i= 0;
    while ( objectEnum.hasMoreElements() ) {
      result[i]= (Domain) objectEnum.nextElement() ;
      i++;
    }
    return result;
  } // getChilds
  

  /** checks whether this domain has a INDIRECT child domain.
   *  This is equivalent to check whether there exists a directed path from 
   *  this domain to the specified child domain. This method
   *  is mainly used for detecting and preventing cycles in a domain graph.
   *  @return true iff there exists a directed path from this domain 
   *          downwards to the node "aDomain".
   */
  public boolean containsDomain(Domain aDomain)
  { 
    // firstly, check self
    if ( _this()._is_equivalent(aDomain) ) return true;

    // secondly, check direct childs
    if ( this.hasChild(aDomain) ) return true;
    
    // thirdly, check all indirect childs by depth-first search
    Domain child;
    Enumeration objectEnum= _child_domains.keys();
    // convert enumeration to array
    while ( objectEnum.hasMoreElements() ) 
      {
	child= (Domain) objectEnum.nextElement() ;
	if ( child.containsDomain(aDomain) ) 
	  return true;
      }
    // no matching child found
    return false;
  } // containsDomain


  /** adds a new parent domain to this domain. <br>
   * Pre :         this.isRoot()
   *       OR (NOT this.isRoot()  
   *           AND parentDS.getRootDomain == this.getRootDomain)
   *       the precondition of Domain.addChild must also be satisfied <br>
   * Post: this.hasParent(parentDS) AND parentDS.hasChild(this)
   *       AND NOT this.isRoot() 
   *       AND parentDS.getRootDomain == this.getRootDomain
   *       (this is now a child domain of the parent domain parentDS and
   *        they have the same root domain)
   * @param parentDS the domain to be added as parent domain
   */
  public void insertParent(jacorb.orb.domain.Domain parentDS)
  { 
    // check precondition
    Domain self= _this();
    jacorb.util.Debug.assert(2, self.isRoot() 
			   || (!self.isRoot() && parentDS.getRootDomain()._is_equivalent
			       (self.getRootDomain()) ),
			     "DMImpl.insertParent: precondition violated");
    jacorb.util.Debug.output(2,"Domain("+_description+"insertParent: adding " 
			     + parentDS.description()+ " as parent domain.");
    _parents.put(parentDS, parentDS); // put into parent table

    // set at parent
    if ( ! parentDS.hasChild(self) )
      parentDS.insertChild(self);

    // check post condition
    jacorb.util.Debug.assert(2, self.hasParent(parentDS) && parentDS.hasChild(self),
			     "DMImpl.insertParent: child <-> parent postcond. violated");
    jacorb.util.Debug.assert(2, ! self.isRoot(),
			     "DMImpl.insertParent: root postcondition violated");

    if ( !parentDS.getRootDomain()._is_equivalent(self.getRootDomain() ) ) 
	 {
	   jacorb.util.Debug.output(2, self.description() + ": parent " + parentDS.description() 
				    +"has root " + parentDS.getRootDomain().description()
				    +"I have "+ self.getRootDomain().description() + " as root.");
	 }
    jacorb.util.Debug.assert(2,parentDS.getRootDomain()._is_equivalent
			         (self.getRootDomain() ),
			     "DMImpl.insertParent: unique root postcondition violated");
  }

  /** removes a parent domain from the list of parent domains.
   *  Post: NOT this.hasParent(parentDS) AND NOT parentDS.hasChild(this) */
  public void deleteParent(jacorb.orb.domain.Domain parentDS)
  {
    Domain self;
    // TODO: check precondition

    _parents.remove(parentDS);
    if (parentDS.hasChild( self= _this() ) )
      parentDS.deleteChild( self );
    
    // check post condition
    jacorb.util.Debug.assert(2, !parentDS.hasChild(self) && !self.hasParent(parentDS),
			     "DMImpl.insertParent: child <-> parent postcondition violated");
  }
  
  /** checks whether this domain has the specified parent domain
   * @return true iff parentDS is a parent domain of this
      domain
  */
  public boolean hasParent(jacorb.orb.domain.Domain parentDS)
  { 
    return _parents.containsKey(parentDS);
  }
	
  /** lists all parent domains. */
  public jacorb.orb.domain.Domain[] getParents()
  {
    Enumeration objectEnum= _parents.keys();
    // convert Enumeration to array
    Domain[] result= new Domain[_parents.size()];
    int i= 0;
    while ( objectEnum.hasMoreElements() ) {
      result[i]= DomainHelper.narrow
	( (org.omg.CORBA.Object) objectEnum.nextElement() );
      i++;
    }
    return result;

  }

 // ********** ** private functions used to compute some temporary results **********

  /** does the conflict resolution in the case of overlapping domains. */
  private org.omg.CORBA.Policy doConflictResolution(Vector targetDomains, int overlapType)
  {
    ConflictResolutionPolicy resolver;
    try 
      { 
	resolver= ConflictResolutionPolicyHelper.narrow 
	  ( get_domain_policy(CONFLICT_RESOLUTION_POLICY_ID.value));
	jacorb.util.Debug.output(4,"DSImpl.doConflictResolution: using policy "
         + resolver.short_description()+" which states: \"" + resolver.long_description() +"\"");
      } 
    catch (INV_POLICY invalid) 
      {
	// no conflict resolution policy defined, but we need one, 
	// so define a default one and insert it into policy list
	resolver= (new ConflictResolutionPolicyPOATie
		   (new SimpleConflictResolutionPolicy() ))._this();
	jacorb.util.Debug.output(2, "DSImpl.doConflictResolution: no conflict policy found. "
           + "Creating a simple conflict resolution policy and inserting it into this domain."); 
				 
	this.set_domain_policy(resolver);
      }
    Domain overlappingDM[]= new Domain[targetDomains.size()];
    targetDomains.copyInto(overlappingDM);
    Domain dm= resolver.resolveConflict(overlappingDM, overlapType);
    return dm.get_domain_policy(overlapType);
  } // doConflictResolution

  /** does a depth-frist search in the domain structure do obtain all child domains 
   *  of the domain "ds"
   *  @param ds the domain where to start the search 
   *  @param foundChilds a hashtable which is used to store the result 
   */
  private static void getChildsOfAnyLevel(Domain ds, Hashtable foundChilds)
  {
    Domain child[]= ds.getChilds();

    for (int i= 0; i < child.length; i++)
      {
	foundChilds.put(child[i], child[i]);
	getChildsOfAnyLevel(child[i], foundChilds);
      }
  } // getChildsOfAnyLevel


 /** does a depth-frist search in the domain structure do obtain all child domains 
   *  of the domain "ds" which contain the member "obj"
   *  @param obj the object which the found child domains shall contain
   *  @param ds the domain where to start the search 
   *  @param foundChilds a hashtable which is used to store the result 
   *  @see   getChildsOfAnyLevel
   */
  private final static void getChildsOfAnyLevelWithObject(org.omg.CORBA.Object obj,
						    Domain ds, Hashtable foundChilds)
  {
    Domain child[]= ds.getChilds();
    jacorb.util.Debug.output(2,"DS.getChildsOfAnyLevelWithObject called.");
    for (int i= 0; i < child.length; i++)
      {
	if ( child[i].hasMember(obj) )        // only this line differs from above
	    foundChilds.put(child[i], child[i]);

	getChildsOfAnyLevel(child[i], foundChilds);
      }
  } // getChildsOfAnyLevelWithObject

 /** does a depth-frist search in the domain structure do obtain all parent domains 
   *  of the domain "ds"
   *  @param ds the domain where to start the search 
   *  @param foundParents a hashtable which is used to store the result 
   */
  private final static void getParentsOfAnyLevel(Domain ds, Hashtable foundParents)
  {
    Domain parent[]= ds.getParents();
    for (int i= 0; i < parent.length; i++)
      {
	foundParents.put(parent[i], parent[i]);
	getParentsOfAnyLevel(parent[i], foundParents);
      }
  } // getParentsOfAnyLevel

} // DomainImpl
















