/* Inner emulation consists in either recording a dependency
   * link only, or performing one level of propagation.
   *
   * Dependency mechanism is used whenever dealing with source target
   * types, since by the time we reach them, we might not yet know their
   * exact need.
   */
  public void manageEnclosingInstanceAccessIfNecessary(BlockScope currentScope, FlowInfo flowInfo) {
    if ((flowInfo.tagBits & FlowInfo.UNREACHABLE_OR_DEAD) != 0) return;
    ReferenceBinding allocatedTypeErasure =
        (ReferenceBinding) this.binding.declaringClass.erasure();

    // perform some emulation work in case there is some and we are inside a local type only
    if (allocatedTypeErasure.isNestedType() && currentScope.enclosingSourceType().isLocalType()) {

      if (allocatedTypeErasure.isLocalType()) {
        ((LocalTypeBinding) allocatedTypeErasure).addInnerEmulationDependent(currentScope, false);
        // request cascade of accesses
      } else {
        // locally propagate, since we already now the desired shape for sure
        currentScope.propagateInnerEmulation(allocatedTypeErasure, false);
        // request cascade of accesses
      }
    }
  }
  /*
   * For all type bindings that have hierarchy problems, artificially fix their superclass/superInterfaces so that the connection
   * can be made.
   */
  private void fixSupertypeBindings() {
    for (int current = this.typeIndex; current >= 0; current--) {
      ReferenceBinding typeBinding = this.typeBindings[current];
      if ((typeBinding.tagBits & TagBits.HierarchyHasProblems) == 0) continue;

      if (typeBinding instanceof SourceTypeBinding) {
        if (typeBinding instanceof LocalTypeBinding) {
          LocalTypeBinding localTypeBinding = (LocalTypeBinding) typeBinding;
          QualifiedAllocationExpression allocationExpression =
              localTypeBinding.scope.referenceContext.allocation;
          TypeReference type;
          if (allocationExpression != null
              && (type = allocationExpression.type) != null
              && type.resolvedType != null) {
            localTypeBinding.setSuperClass((ReferenceBinding) type.resolvedType);
            continue;
          }
        }
        ClassScope scope = ((SourceTypeBinding) typeBinding).scope;
        if (scope != null) {
          TypeDeclaration typeDeclaration = scope.referenceContext;
          TypeReference superclassRef = typeDeclaration == null ? null : typeDeclaration.superclass;
          TypeBinding superclass = superclassRef == null ? null : superclassRef.resolvedType;
          if (superclass != null) {
            superclass = superclass.closestMatch();
          }
          if (superclass instanceof ReferenceBinding) {
            // ensure we are not creating a cycle (see
            // https://bugs.eclipse.org/bugs/show_bug.cgi?id=215681 )
            if (!(subTypeOfType((ReferenceBinding) superclass, typeBinding))) {
              ((SourceTypeBinding) typeBinding).setSuperClass((ReferenceBinding) superclass);
            }
          }

          TypeReference[] superInterfaces =
              typeDeclaration == null ? null : typeDeclaration.superInterfaces;
          int length;
          ReferenceBinding[] interfaceBindings = typeBinding.superInterfaces();
          if (superInterfaces != null
              && (length = superInterfaces.length)
                  > (interfaceBindings == null
                      ? 0
                      : interfaceBindings
                          .length)) { // check for interfaceBindings being null (see
                                      // https://bugs.eclipse.org/bugs/show_bug.cgi?id=139689)
            interfaceBindings = new ReferenceBinding[length];
            int index = 0;
            for (int i = 0; i < length; i++) {
              TypeBinding superInterface = superInterfaces[i].resolvedType;
              if (superInterface != null) {
                superInterface = superInterface.closestMatch();
              }
              if (superInterface instanceof ReferenceBinding) {
                // ensure we are not creating a cycle (see
                // https://bugs.eclipse.org/bugs/show_bug.cgi?id=215681 )
                if (!(subTypeOfType((ReferenceBinding) superInterface, typeBinding))) {
                  interfaceBindings[index++] = (ReferenceBinding) superInterface;
                }
              }
            }
            if (index < length)
              System.arraycopy(
                  interfaceBindings, 0, interfaceBindings = new ReferenceBinding[index], 0, index);
            ((SourceTypeBinding) typeBinding).setSuperInterfaces(interfaceBindings);
          }
        }
      } else if (typeBinding instanceof BinaryTypeBinding) {
        try {
          typeBinding.superclass();
        } catch (AbortCompilation e) {
          // allow subsequent call to superclass() to succeed so that we don't have to catch
          // AbortCompilation everywhere
          ((BinaryTypeBinding) typeBinding).tagBits &= ~TagBits.HasUnresolvedSuperclass;
          this.builder.hierarchy.missingTypes.add(
              new String(typeBinding.superclass().sourceName()));
          this.hasMissingSuperClass = true;
        }
        try {
          typeBinding.superInterfaces();
        } catch (AbortCompilation e) {
          // allow subsequent call to superInterfaces() to succeed so that we don't have to catch
          // AbortCompilation everywhere
          ((BinaryTypeBinding) typeBinding).tagBits &= ~TagBits.HasUnresolvedSuperinterfaces;
        }
      }
    }
  }
  /*
   * INTERNAL USE-ONLY
   * Innerclasses get their name computed as they are generated, since some may not
   * be actually outputed if sitting inside unreachable code.
   */
  public char[] computeConstantPoolName(LocalTypeBinding localType) {
    if (localType.constantPoolName != null) {
      return localType.constantPoolName;
    }
    // delegates to the outermost enclosing classfile, since it is the only one with a global vision
    // of its innertypes.

    if (this.constantPoolNameUsage == null) this.constantPoolNameUsage = new HashtableOfType();

    ReferenceBinding outerMostEnclosingType =
        localType.scope.outerMostClassScope().enclosingSourceType();

    // ensure there is not already such a local type name defined by the user
    int index = 0;
    char[] candidateName;
    boolean isCompliant15 = compilerOptions().complianceLevel >= ClassFileConstants.JDK1_5;
    while (true) {
      if (localType.isMemberType()) {
        if (index == 0) {
          candidateName =
              CharOperation.concat(
                  localType.enclosingType().constantPoolName(), localType.sourceName, '$');
        } else {
          // in case of collision, then member name gets extra $1 inserted
          // e.g. class X { { class L{} new X(){ class L{} } } }
          candidateName =
              CharOperation.concat(
                  localType.enclosingType().constantPoolName(),
                  '$',
                  String.valueOf(index).toCharArray(),
                  '$',
                  localType.sourceName);
        }
      } else if (localType.isAnonymousType()) {
        if (isCompliant15) {
          // AspectJ Extension start
          char[] extraInsert = null;
          if (outerMostEnclosingType instanceof SourceTypeBinding) {
            SourceTypeBinding sourceTypeBinding = (SourceTypeBinding) outerMostEnclosingType;
            ClassScope classScope = sourceTypeBinding.scope;
            if (classScope != null) {
              TypeDeclaration typeDeclaration = classScope.referenceContext;
              if (typeDeclaration != null) {
                extraInsert = typeDeclaration.getLocalTypeNameSuffix();
              }
            }
          }
          if (extraInsert != null) { // AspectJ Extension end
            candidateName =
                CharOperation.concat(
                    localType.enclosingType.constantPoolName(),
                    '$',
                    extraInsert,
                    '$',
                    String.valueOf(index + 1).toCharArray());
          } else {
            // from 1.5 on, use immediately enclosing type name
            candidateName =
                CharOperation.concat(
                    localType.enclosingType.constantPoolName(),
                    String.valueOf(index + 1).toCharArray(),
                    '$');
          } // AspectJ extension, closing }
        } else {
          candidateName =
              CharOperation.concat(
                  outerMostEnclosingType.constantPoolName(),
                  String.valueOf(index + 1).toCharArray(),
                  '$');
        }
      } else {
        // local type
        if (isCompliant15) {
          candidateName =
              CharOperation.concat(
                  CharOperation.concat(
                      localType.enclosingType().constantPoolName(),
                      String.valueOf(index + 1).toCharArray(),
                      '$'),
                  localType.sourceName);
        } else {
          candidateName =
              CharOperation.concat(
                  outerMostEnclosingType.constantPoolName(),
                  '$',
                  String.valueOf(index + 1).toCharArray(),
                  '$',
                  localType.sourceName);
        }
      }
      if (this.constantPoolNameUsage.get(candidateName) != null) {
        index++;
      } else {
        this.constantPoolNameUsage.put(candidateName, localType);
        break;
      }
    }
    return candidateName;
  }