public void merge(final Binding b, final boolean removeObject) {
      for (final PsiTypeVariable var : b.getBoundVariables()) {
        final int index = var.getIndex();

        if (myBindings.get(index) != null) {
          LOG.error("Oops... Binding conflict...");
        } else {
          final PsiType type = b.apply(var);
          final PsiClassType javaLangObject =
              PsiType.getJavaLangObject(
                  PsiManager.getInstance(myProject), GlobalSearchScope.allScope(myProject));

          if (removeObject && javaLangObject.equals(type)) {
            final HashSet<PsiTypeVariable> cluster = myFactory.getClusterOf(var.getIndex());

            if (cluster != null) {
              for (final PsiTypeVariable war : cluster) {
                final PsiType wtype = b.apply(war);

                if (!javaLangObject.equals(wtype)) {
                  myBindings.put(index, type);
                  break;
                }
              }
            }
          } else {
            myBindings.put(index, type);
          }
        }
      }
    }
  private Binding unify(final PsiType x, final PsiType y, final Unifier unifier) {
    final int indicator =
        (x instanceof PsiTypeVariable ? 1 : 0) + (y instanceof PsiTypeVariable ? 2 : 0);

    switch (indicator) {
      case 0:
        if (x instanceof PsiWildcardType || y instanceof PsiWildcardType) {
          return unifier.unify(x, y);
        } else if (x instanceof PsiArrayType || y instanceof PsiArrayType) {
          final PsiType xType =
              x instanceof PsiArrayType ? ((PsiArrayType) x).getComponentType() : x;
          final PsiType yType =
              y instanceof PsiArrayType ? ((PsiArrayType) y).getComponentType() : y;

          return unify(xType, yType, unifier);
        } else if (x instanceof PsiClassType && y instanceof PsiClassType) {
          final PsiClassType.ClassResolveResult resultX = Util.resolveType(x);
          final PsiClassType.ClassResolveResult resultY = Util.resolveType(y);

          final PsiClass xClass = resultX.getElement();
          final PsiClass yClass = resultY.getElement();

          if (xClass != null && yClass != null) {
            final PsiSubstitutor ySubst = resultY.getSubstitutor();

            final PsiSubstitutor xSubst = resultX.getSubstitutor();

            if (!xClass.equals(yClass)) {
              return null;
            }

            Binding b = create();

            for (final PsiTypeParameter aParm : xSubst.getSubstitutionMap().keySet()) {
              final PsiType xType = xSubst.substitute(aParm);
              final PsiType yType = ySubst.substitute(aParm);

              final Binding b1 = unify(xType, yType, unifier);

              if (b1 == null) {
                return null;
              }

              b = b.compose(b1);
            }

            return b;
          }
        } else if (y instanceof Bottom) {
          return create();
        } else {
          return null;
        }

      default:
        return unifier.unify(x, y);
    }
  }
  public Binding rise(final PsiType x, final PsiType y, final HashSet<Constraint> constraints) {
    final Binding binding =
        balance(
            x,
            y,
            new Balancer() {
              public Binding varType(final PsiTypeVariable x, final PsiType y) {
                if (y instanceof Bottom || y instanceof PsiWildcardType) {
                  return create();
                }

                return create(x, y);
              }

              public Binding varVar(final PsiTypeVariable x, final PsiTypeVariable y) {
                final int xi = x.getIndex();
                final int yi = y.getIndex();

                if (xi < yi) {
                  return create(x, y);
                } else if (yi < xi) {
                  return create(y, x);
                } else {
                  return create();
                }
              }

              public Binding typeVar(final PsiType x, final PsiTypeVariable y) {
                if (x == null) return create(y, Bottom.BOTTOM);
                if (x instanceof PsiWildcardType) return create();

                return create(y, x);
              }
            },
            constraints);

    return binding != null ? binding.reduceRecursive() : null;
  }
  public Binding riseWithWildcard(
      final PsiType x, final PsiType y, final HashSet<Constraint> constraints) {
    final Binding binding =
        balance(
            x,
            y,
            new Balancer() {
              public Binding varType(final PsiTypeVariable x, final PsiType y) {
                if (y instanceof Bottom) {
                  return create();
                }

                if (y == null || y instanceof PsiWildcardType) {
                  return null;
                }

                final PsiTypeVariable var = myFactory.create();
                final Binding binding =
                    create(x, PsiWildcardType.createSuper(PsiManager.getInstance(myProject), var));

                binding.addTypeVariable(var);
                constraints.add(new Subtype(var, y));

                return binding;
              }

              public Binding varVar(final PsiTypeVariable x, final PsiTypeVariable y) {
                final int xi = x.getIndex();
                final int yi = y.getIndex();

                if (xi == yi) return create();

                return create(
                    y, PsiWildcardType.createExtends(PsiManager.getInstance(myProject), x));
                /* if (xi < yi) {
                  return create(x, y);
                }
                else if (yi < xi) {
                  return create(y, x);
                }
                else {
                  return create();
                } */
              }

              public Binding typeVar(final PsiType x, final PsiTypeVariable y) {
                if (x == null) {
                  return create(y, Bottom.BOTTOM);
                }

                if (x instanceof PsiWildcardType) {
                  return null;
                }

                final PsiTypeVariable var = myFactory.create();
                final Binding binding =
                    create(
                        y, PsiWildcardType.createExtends(PsiManager.getInstance(myProject), var));

                binding.addTypeVariable(var);
                constraints.add(new Subtype(x, var));

                return binding;
              }
            },
            constraints);

    return binding != null ? binding.reduceRecursive() : null;
  }
  public Binding balance(
      final PsiType x,
      final PsiType y,
      final Balancer balancer,
      final HashSet<Constraint> constraints) {
    final int indicator =
        (x instanceof PsiTypeVariable ? 1 : 0) + (y instanceof PsiTypeVariable ? 2 : 0);

    switch (indicator) {
      case 0:
        if (x instanceof PsiWildcardType || y instanceof PsiWildcardType) {
          final PsiType xType = x instanceof PsiWildcardType ? ((PsiWildcardType) x).getBound() : x;
          final PsiType yType = y instanceof PsiWildcardType ? ((PsiWildcardType) y).getBound() : y;

          switch ((x instanceof PsiWildcardType ? 1 : 0) + (y instanceof PsiWildcardType ? 2 : 0)) {
            case 1:
              if (((PsiWildcardType) x).isExtends()) {
                /* ? extends T1, T2 */
                return null;
              } else {
                /* ? super T1, T2 */
                if (xType != null && !"java.lang.Object".equals(xType.getCanonicalText())) {
                  return null;
                }
                return create();
              }

            case 2:
              if (((PsiWildcardType) y).isExtends()) {
                /* T1, ? extends T2 */
                if (yType instanceof PsiTypeVariable) {
                  final PsiTypeVariable beta = myFactory.create();

                  if (constraints != null) {
                    constraints.add(new Subtype(beta, yType));
                    if (x != null) {
                      constraints.add(new Subtype(x, yType));
                    }
                  }

                  return create();
                } else {
                  if (constraints != null && xType != null && yType != null) {
                    constraints.add(new Subtype(xType, yType));
                  }

                  return balance(xType, yType, balancer, constraints);
                }
              } else {
                /* T1, ? super T2 */
                if (yType instanceof PsiTypeVariable) {
                  final PsiTypeVariable beta = myFactory.create();

                  if (constraints != null) {
                    if (x != null) constraints.add(new Subtype(x, beta));
                    constraints.add(new Subtype(yType, beta));
                  }

                  return create();
                } else {
                  if (constraints != null && yType != null && xType != null) {
                    constraints.add(new Subtype(yType, xType));
                  }

                  return balance(xType, yType, balancer, constraints);
                }
              }

            case 3:
              switch ((((PsiWildcardType) x).isExtends() ? 0 : 1)
                  + (((PsiWildcardType) y).isExtends() ? 0 : 2)) {
                case 0: /* ? super T1, ? super T2 */
                  if (constraints != null && xType != null && yType != null) {
                    constraints.add(new Subtype(yType, xType));
                  }
                  return balance(xType, yType, balancer, constraints);

                case 1: /* ? extends T1, ? super T2 */
                  if (constraints != null && xType != null && yType != null) {
                    constraints.add(new Subtype(xType, yType));
                  }
                  return balance(xType, yType, balancer, constraints);

                case 2: /* ? super T1, ? extends T2*/
                  return null;

                case 3: /* ? extends T1, ? extends T2*/
                  if (constraints != null && xType != null && yType != null) {
                    constraints.add(new Subtype(xType, yType));
                  }
                  return balance(xType, yType, balancer, constraints);
              }
          }

          return create();
        } else if (x instanceof PsiArrayType || y instanceof PsiArrayType) {
          final PsiType xType =
              x instanceof PsiArrayType ? ((PsiArrayType) x).getComponentType() : x;
          final PsiType yType =
              y instanceof PsiArrayType ? ((PsiArrayType) y).getComponentType() : y;

          return balance(xType, yType, balancer, constraints);
        } else if (x instanceof PsiClassType && y instanceof PsiClassType) {
          final PsiClassType.ClassResolveResult resultX = Util.resolveType(x);
          final PsiClassType.ClassResolveResult resultY = Util.resolveType(y);

          final PsiClass xClass = resultX.getElement();
          final PsiClass yClass = resultY.getElement();

          if (xClass != null && yClass != null) {
            final PsiSubstitutor ySubst = resultY.getSubstitutor();

            PsiSubstitutor xSubst =
                TypeConversionUtil.getClassSubstitutor(yClass, xClass, resultX.getSubstitutor());
            if (xSubst == null) return null;

            Binding b = create();

            for (final PsiTypeParameter aParm : xSubst.getSubstitutionMap().keySet()) {
              final PsiType xType = xSubst.substitute(aParm);
              final PsiType yType = ySubst.substitute(aParm);

              final Binding b1 =
                  unify(
                      xType,
                      yType,
                      new Unifier() {
                        public Binding unify(final PsiType x, final PsiType y) {
                          return balance(x, y, balancer, constraints);
                        }
                      });

              if (b1 == null) {
                return null;
              }

              b = b.compose(b1);
            }

            return b;
          }
        } else if (y instanceof Bottom) {
          return create();
        } else {
          return null;
        }
        break;

      case 1:
        return balancer.varType((PsiTypeVariable) x, y);

      case 2:
        return balancer.typeVar(x, (PsiTypeVariable) y);

      case 3:
        return balancer.varVar((PsiTypeVariable) x, (PsiTypeVariable) y);
    }

    return null;
  }
    public Binding compose(final Binding b) {
      LOG.assertTrue(b instanceof BindingImpl);

      final BindingImpl b1 = this;
      final BindingImpl b2 = (BindingImpl) b;

      final BindingImpl b3 = new BindingImpl();

      for (PsiTypeVariable boundVariable : myBoundVariables) {
        final int i = boundVariable.getIndex();

        final PsiType b1i = b1.myBindings.get(i);
        final PsiType b2i = b2.myBindings.get(i);

        final int flag = (b1i == null ? 0 : 1) + (b2i == null ? 0 : 2);

        switch (flag) {
          case 0:
            break;

          case 1: /* b1(i)\b2(i) */
            {
              final PsiType type = b2.apply(b1i);

              if (type == null) {
                return null;
              }

              b3.myBindings.put(i, type);
              b3.myCyclic = type instanceof PsiTypeVariable;
            }
            break;

          case 2: /* b2(i)\b1(i) */
            {
              final PsiType type = b1.apply(b2i);

              if (type == null) {
                return null;
              }

              b3.myBindings.put(i, type);
              b3.myCyclic = type instanceof PsiTypeVariable;
            }
            break;

          case 3: /* b2(i) \cap b1(i) */
            {
              final Binding common = rise(b1i, b2i, null);

              if (common == null) {
                return null;
              }

              final PsiType type = common.apply(b1i);
              if (type == null) {
                return null;
              }

              if (type == null) {
                return null;
              }

              b3.myBindings.put(i, type);
              b3.myCyclic = type instanceof PsiTypeVariable;
            }
        }
      }

      return b3;
    }