/*@ also
  @  public normal_behavior
  @    requires obj != null && obj instanceof JMLEqualsToEqualsRelation;
  @    ensures \result ==
  @            this.theRelation.equals(
  @                    ((JMLEqualsToEqualsRelation)obj).theRelation);
  @ also
  @  public normal_behavior
  @    requires obj == null
  @          || !(obj instanceof JMLEqualsToEqualsRelation);
  @    ensures !\result;
  @*/
  public boolean equals(/*@ nullable @*/ Object obj) {
    if (obj == null || !(obj instanceof JMLEqualsToEqualsRelation)) {
      return false;
    }

    JMLEqualsToEqualsRelation rel = (JMLEqualsToEqualsRelation) obj;

    if (size_ != rel.int_size()) {
      return false;
    }

    JMLEqualsToEqualsRelationImageEnumerator imagePairEnum = this.imagePairs();
    JMLEqualsValuePair imagePair;
    JMLEqualsSet img;
    while (imagePairEnum.hasMoreElements()) {
      imagePair = imagePairEnum.nextImagePair();
      // @ assume imagePair.value != null;
      // @ assume imagePair.value instanceof JMLEqualsSet;
      img = (JMLEqualsSet) imagePair.value;
      if (!img.equals(rel.elementImage(imagePair.key))) {
        return false;
      }
    }
    return true;
  }
  /*@  public normal_behavior
  @    requires othRel != null;
  @    ensures (\forall JMLValueEqualsPair pair; ;
  @                 \result.theRelation.has(pair)
  @                    == (\exists Object val;
  @                            othRel.elementImage(pair.key).has(val);
  @                            this.elementImage(val).has(pair.value)
  @                            )
  @                );
  @*/
  public /*@ non_null @*/ JMLObjectToEqualsRelation compose(
      /*@ non_null @*/ JMLObjectToEqualsRelation othRel) {
    JMLValueSet newImagePairSet = new JMLValueSet();
    JMLObjectSet newDom = new JMLObjectSet();
    int newSize = 0;

    JMLObjectToEqualsRelationImageEnumerator imagePairEnum = othRel.imagePairs();
    JMLObjectValuePair imagePair;
    JMLEqualsSet img1;
    JMLEqualsSet img2;
    int imgSize;
    while (imagePairEnum.hasMoreElements()) {
      imagePair = imagePairEnum.nextImagePair();
      // @ assume imagePair.value != null;
      // @ assume imagePair.value instanceof JMLEqualsSet;
      img1 = (JMLEqualsSet) imagePair.value;
      img2 = this.image(img1);
      imgSize = img2.int_size();
      if (imgSize > 0) {
        newImagePairSet = newImagePairSet.insert(new JMLObjectValuePair(imagePair.key, img2));
        newSize = newSize + imgSize;
        newDom = newDom.insert(imagePair.key);
      }
    }
    return new JMLObjectToEqualsRelation(newImagePairSet, newDom, newSize);
  } // @ nowarn Exception;
  /*@  public normal_behavior
  @    ensures (\forall Object dv; dv != null;
  @             (this.isDefinedAt(dv) == \result.isDefinedAt(dv))
  @             && \result.elementImage(dv).isSubset(this.elementImage(dv))
  @             && \result.elementImage(dv).int_size() == 1);
  @*/
  public /*@ non_null @*/ JMLEqualsToEqualsMap toFunction() {
    JMLEqualsSet newDom = domain_;
    int newSize = domain_.int_size();

    JMLValueSet newImagePairSet = imagePairSet_;

    if (newSize != size_) {
      // Have to restrict the result to be a function
      JMLEqualsToEqualsRelationImageEnumerator imagePairEnum = this.imagePairs();
      newImagePairSet = new JMLValueSet();
      JMLEqualsValuePair imagePair;
      JMLEqualsSet img;
      while (imagePairEnum.hasMoreElements()) {
        imagePair = imagePairEnum.nextImagePair();
        // @ assume imagePair.value != null;
        // @ assume imagePair.value instanceof JMLEqualsSet;
        img = (JMLEqualsSet) imagePair.value;
        Enumeration imgEnum = img.elements();
        // @ assume imgEnum.moreElements;
        Object o = imgEnum.nextElement();
        if (o == null) {
          img = new JMLEqualsSet(null);
        } else {
          // @ assume o != null && o instanceof Object;
          Object rv = (Object) o;
          img = new JMLEqualsSet(rv);
        }
        newImagePairSet = newImagePairSet.insert(new JMLEqualsValuePair(imagePair.key, img));
      }
    }
    return new JMLEqualsToEqualsMap(newImagePairSet, newDom, newSize);
  } // @ nowarn Exception;
  /*@  public normal_behavior
  @    ensures (\forall Object rv; ;
  @                 \result.has(rv)
  @                    == (\exists Object dv; ;
  @                            elementImage(dv).has(rv))
  @                );
  @*/
  public /*@ non_null @*/ JMLEqualsSet range() {
    JMLEqualsSet rangeSet = new JMLEqualsSet();

    JMLEqualsToEqualsRelationImageEnumerator imagePairEnum = this.imagePairs();
    JMLEqualsValuePair imagePair;
    JMLEqualsSet img;
    while (imagePairEnum.hasMoreElements()) {
      imagePair = imagePairEnum.nextImagePair();
      // @ assume imagePair.value != null;
      // @ assume imagePair.value instanceof JMLEqualsSet;
      img = (JMLEqualsSet) imagePair.value;
      rangeSet = rangeSet.union(img);
    }
    return rangeSet;
  } // @ nowarn Exception;
 /*@  public normal_behavior
 @    requires rng != null;
 @    ensures \result.equals(inverse().image(rng));
 @
 @ implies_that
 @    ensures \result != null && !\result.containsNull;
 @*/
 public /*@ non_null @*/ JMLEqualsSet inverseImage(/*@ non_null @*/ JMLEqualsSet rng) {
   JMLEqualsSet invImg = new JMLEqualsSet();
   JMLEqualsToEqualsRelationImageEnumerator imagePairEnum = this.imagePairs();
   JMLEqualsValuePair imagePair;
   // @ loop_invariant !invImg.containsNull;
   while (imagePairEnum.hasMoreElements()) { // @ nowarn LoopInv;
     imagePair = imagePairEnum.nextImagePair();
     // @ assume imagePair.value != null && imagePair.key != null;
     // @ assume imagePair.value instanceof JMLEqualsSet;
     JMLEqualsSet img = (JMLEqualsSet) imagePair.value;
     if (!img.intersection(rng).isEmpty()) {
       invImg = invImg.insert(imagePair.key);
     }
   }
   return invImg;
 } // @ nowarn Exception;
  /*@  public normal_behavior
  @    requires dv != null && rv != null;
  @    requires int_size() < Integer.MAX_VALUE || elementImage(dv).has(rv);
  @    ensures \result.theRelation.equals(
  @           this.theRelation.insert(new JMLEqualsEqualsPair(dv, rv)));
  @*/
  public /*@ pure @*/ /*@ non_null @*/ JMLEqualsToEqualsRelation add(
      /*@ non_null @*/ Object dv, /*@ non_null @*/ Object rv)
      throws NullPointerException, IllegalStateException {
    if (rv == null) {
      throw new NullPointerException();
    }

    JMLValueSet newImagePairSet;
    JMLEqualsSet newDom;
    int newSize;
    JMLEqualsSet img;

    if (!domain_.has(dv)) {
      if (size_ == Integer.MAX_VALUE) {
        throw new IllegalStateException(TOO_BIG_TO_INSERT);
      }
      newDom = domain_.insert(dv);
      newSize = size_ + 1;
      img = new JMLEqualsSet(rv);
      newImagePairSet = imagePairSet_.insert(new JMLEqualsValuePair(dv, img));
    } else {
      newImagePairSet = new JMLValueSet();
      newDom = domain_;
      newSize = 0;

      JMLEqualsToEqualsRelationImageEnumerator imagePairEnum = this.imagePairs();
      JMLEqualsValuePair imagePair;
      while (imagePairEnum.hasMoreElements()) {
        imagePair = imagePairEnum.nextImagePair();
        // @ assume imagePair.value != null;
        // @ assume imagePair.value instanceof JMLEqualsSet;
        img = (JMLEqualsSet) imagePair.value;
        if (imagePair.keyEquals(dv)) {
          img = img.insert(rv);
        }
        int size_inc = img.int_size();
        if (newSize <= Integer.MAX_VALUE - size_inc) {
          newSize = newSize + size_inc;
        } else {
          throw new IllegalStateException(TOO_BIG_TO_INSERT);
        }
        newImagePairSet = newImagePairSet.insert(new JMLEqualsValuePair(imagePair.key, img));
      }
    }
    return new JMLEqualsToEqualsRelation(newImagePairSet, newDom, newSize);
  }
 /*@  public normal_behavior
 @    requires dom != null;
 @    ensures (\forall Object o; \result.has(o)
 @              <==> (\exists JMLEqualsEqualsPair pair;
 @                      theRelation.has(pair);
 @                      dom.has(pair.key) && pair.valueEquals(o)));
 @    ensures_redundantly
 @              (\forall JMLEqualsEqualsPair pair;
 @                      theRelation.has(pair);
 @                      dom.has(pair.key) ==> \result.has(pair.value));
 @
 @ implies_that
 @    ensures \result != null && !\result.containsNull;
 @*/
 public /*@ non_null @*/ JMLEqualsSet image(/*@ non_null @*/ JMLEqualsSet dom) {
   JMLEqualsSet img = new JMLEqualsSet();
   JMLEqualsToEqualsRelationImageEnumerator imagePairEnum = this.imagePairs();
   JMLEqualsValuePair imagePair;
   // @ loop_invariant !img.containsNull;
   while (imagePairEnum.hasMoreElements()) {
     imagePair = imagePairEnum.nextImagePair();
     // @ assume imagePair.value != null;
     // @ assume imagePair.value instanceof JMLEqualsSet;
     if (dom.has(imagePair.key)) {
       JMLEqualsSet ipv = (JMLEqualsSet) imagePair.value;
       // @ assume !ipv.containsNull;
       img = img.union(ipv);
     }
   }
   return img;
 } // @ nowarn Exception;
  /*@  public normal_behavior
  @    requires rng != null;
  @    ensures (\forall JMLEqualsEqualsPair pair; ;
  @                       \result.theRelation.has(pair)
  @                        == rng.has(pair.value)
  @                    && elementImage(pair.key).has(pair.value)
  @                );
  @*/
  public /*@ non_null @*/ JMLEqualsToEqualsRelation restrictRangeTo(
      /*@ non_null @*/ JMLEqualsSet rng) {
    JMLValueSet newImagePairSet = new JMLValueSet();
    JMLEqualsSet newDom = new JMLEqualsSet();
    int newSize = 0;

    JMLEqualsToEqualsRelationImageEnumerator imagePairEnum = this.imagePairs();
    JMLEqualsValuePair imagePair;
    JMLEqualsSet img;
    while (imagePairEnum.hasMoreElements()) {
      imagePair = imagePairEnum.nextImagePair();
      // @ assume imagePair.value != null;
      // @ assume imagePair.value instanceof JMLEqualsSet;
      img = ((JMLEqualsSet) imagePair.value).intersection(rng);
      if (!img.isEmpty()) {
        newImagePairSet = newImagePairSet.insert(new JMLEqualsValuePair(imagePair.key, img));
        newDom = newDom.insert(imagePair.key);
        newSize = newSize + img.int_size();
      }
    }
    return new JMLEqualsToEqualsRelation(newImagePairSet, newDom, newSize);
  } // @ nowarn Exception;
  /*@  public normal_behavior
  @    requires dom != null;
  @    ensures (\forall JMLEqualsEqualsPair pair; pair != null;
  @                      \result.theRelation.has(pair) == dom.has(pair.key)
  @                    && elementImage(pair.key).has(pair.value)
  @                );
  @*/
  public /*@ non_null @*/ JMLEqualsToEqualsRelation restrictDomainTo(
      /*@ non_null @*/ JMLEqualsSet dom) {
    JMLValueSet newImagePairSet = new JMLValueSet();
    JMLEqualsSet newDom = domain_.intersection(dom);
    // @ assume (\forall Object dv; newDom.has(dv); dv != null);
    int newSize = 0;

    JMLEqualsToEqualsRelationImageEnumerator imagePairEnum = this.imagePairs();
    JMLEqualsValuePair imagePair;
    JMLEqualsSet img;
    while (imagePairEnum.hasMoreElements()) {
      imagePair = imagePairEnum.nextImagePair();
      // @ assume imagePair.value != null;
      // @ assume imagePair.value instanceof JMLEqualsSet;
      if (newDom.has(imagePair.key)) {
        newImagePairSet = newImagePairSet.insert(imagePair);
        img = (JMLEqualsSet) imagePair.value;
        newSize = newSize + img.int_size();
      }
    }
    return new JMLEqualsToEqualsRelation(newImagePairSet, newDom, newSize);
  } // @ nowarn Exception;
  /*@  public normal_behavior
  @    ensures \result != null
  @         && (\forall Object val; domain().has(val);
  @             (\forall Object r; r != null;
  @                   (elementImage(val).has(r)
  @                      <==> \result.theRelation
  @                            .has(new JMLEqualsEqualsPair(val,r))
  @                          && !val.equals(dv))));
  @ implies_that
  @   public normal_behavior
  @    requires dv == null;
  @    ensures \result != null && \result.equals(this);
  @*/
  public /*@ non_null @*/ JMLEqualsToEqualsRelation removeFromDomain(/*@ nullable @*/ Object dv) {
    if (!domain_.has(dv)) {
      return (this);
    }

    JMLValueSet newImagePairSet = new JMLValueSet();
    JMLEqualsSet newDom = domain_.remove(dv);
    int newSize = 0;

    JMLEqualsToEqualsRelationImageEnumerator imagePairEnum = this.imagePairs();
    JMLEqualsValuePair imagePair;
    while (imagePairEnum.hasMoreElements()) {
      imagePair = imagePairEnum.nextImagePair();
      // @ assume imagePair.value != null;
      // @ assume imagePair.value instanceof JMLEqualsSet;
      if (!imagePair.keyEquals(dv)) {
        newImagePairSet = newImagePairSet.insert(imagePair);
        JMLEqualsSet img = (JMLEqualsSet) imagePair.value;
        newSize = newSize + img.int_size();
      }
    }
    return new JMLEqualsToEqualsRelation(newImagePairSet, newDom, newSize);
  } // @ nowarn Exception;
  /*@  public normal_behavior
  @    requires othRel != null;
  @    requires int_size()
  @             < Integer.MAX_VALUE - othRel.difference(this).int_size();
  @    ensures \result.theRelation.equals(
  @                    this.theRelation.union(othRel.theRelation));
  @*/
  public /*@ non_null @*/ JMLEqualsToEqualsRelation union(
      /*@ non_null @*/ JMLEqualsToEqualsRelation othRel) throws IllegalStateException {
    JMLValueSet newImagePairSet = new JMLValueSet();
    JMLEqualsSet newDom = domain_;
    int newSize = 0;

    JMLEqualsToEqualsRelationImageEnumerator imagePairEnum = this.imagePairs();
    JMLEqualsValuePair imagePair;
    JMLEqualsSet img;
    while (imagePairEnum.hasMoreElements()) {
      imagePair = imagePairEnum.nextImagePair();
      // @ assume imagePair.value != null;
      // @ assume imagePair.value instanceof JMLEqualsSet;
      img = (JMLEqualsSet) imagePair.value;
      img = img.union(othRel.elementImage(imagePair.key));
      newImagePairSet = newImagePairSet.insert(new JMLEqualsValuePair(imagePair.key, img));
      int size_inc = img.int_size();
      if (newSize <= Integer.MAX_VALUE - size_inc) {
        newSize = newSize + size_inc;
      } else {
        throw new IllegalStateException(TOO_BIG_TO_UNION);
      }
    }
    JMLEqualsSet diffDom = othRel.domain().difference(this.domain_);
    imagePairEnum = othRel.imagePairs();
    while (imagePairEnum.hasMoreElements()) {
      imagePair = imagePairEnum.nextImagePair();
      // @ assume imagePair.value != null;
      // @ assume imagePair.value instanceof JMLEqualsSet;
      if (diffDom.has(imagePair.key)) {
        newImagePairSet = newImagePairSet.insert(imagePair);
        newDom = newDom.insert(imagePair.key);
        // @ assume imagePair.value != null;
        // @ assume imagePair.value instanceof JMLEqualsSet;
        img = (JMLEqualsSet) imagePair.value;
        int size_inc = img.int_size();
        if (newSize <= Integer.MAX_VALUE - size_inc) {
          newSize = newSize + size_inc;
        } else {
          throw new IllegalStateException(TOO_BIG_TO_UNION);
        }
      }
    }
    return new JMLEqualsToEqualsRelation(newImagePairSet, newDom, newSize);
  }
  /*@  public normal_behavior
  @    requires dv != null && rv != null;
  @    ensures \result.theRelation.equals(
  @                theRelation.remove(new JMLEqualsEqualsPair(dv, rv)));
  @   also
  @    requires dv == null || rv == null;
  @    ensures \result != null && \result.equals(this);
  @*/
  public /*@ non_null @*/ JMLEqualsToEqualsRelation remove(Object dv, Object rv) {
    if (!domain_.has(dv)) {
      return (this);
    }
    // @ assume dv != null;

    JMLValueSet newImagePairSet = new JMLValueSet();
    JMLEqualsSet newDom = domain_;
    int newSize = 0;

    JMLEqualsToEqualsRelationImageEnumerator imagePairEnum = this.imagePairs();
    JMLEqualsValuePair imagePair;
    JMLEqualsSet img;
    while (imagePairEnum.hasMoreElements()) {
      imagePair = imagePairEnum.nextImagePair();
      // @ assume imagePair.value != null;
      // @ assume imagePair.value instanceof JMLEqualsSet;
      img = (JMLEqualsSet) imagePair.value;
      int imgSize = img.int_size();
      if (imagePair.keyEquals(dv)) {
        img = img.remove(rv);
        imgSize = img.int_size();
        if (imgSize > 0) {
          newImagePairSet = newImagePairSet.insert(new JMLEqualsValuePair(dv, img));
          newSize = newSize + imgSize;
        } else {
          // @ assert imgSize == 0;
          newDom = newDom.remove(dv);
        }
      } else {
        newImagePairSet = newImagePairSet.insert(imagePair);
        newSize = newSize + imgSize;
      }
    }
    return new JMLEqualsToEqualsRelation(newImagePairSet, newDom, newSize);
  } // @ nowarn Exception;
 /*@  public normal_behavior
 @     ensures \result <==> domain().has(dv) && elementImage(dv).has(rv);
 @     ensures_redundantly dv == null || rv == null ==> !\result;
 @*/
 public /*@ pure @*/ boolean has(/*@ nullable @*/ Object dv, /*@ nullable @*/ Object rv) {
   // @ assume domain_.has(dv) ==> dv != null;
   return domain_.has(dv) && elementImage(dv).has(rv);
 }
 /*@  public normal_behavior
 @     ensures \result == (elementImage(dv).int_size() > 0);
 @     ensures_redundantly dv == null ==> !\result;
 @*/
 public boolean isDefinedAt(Object dv) {
   return domain_.has(dv);
 }
 /*@   public normal_behavior
 @     ensures \result == (\forall Object dv; isDefinedAt(dv);
 @                                  elementImage(dv).int_size() == 1);
 @*/
 public boolean isaFunction() {
   return size_ == domain_.int_size();
 }
 /*@  public normal_behavior
 @    ensures \result.equals(domain().elements());
 @*/
 public /*@ non_null @*/ JMLEqualsSetEnumerator domainElements() {
   return domain_.elements();
 }