Package org.eclipse.jdt.core.dom.rewrite

Source Code of org.eclipse.jdt.core.dom.rewrite.ImportRewrite$ImportRewriteContext

/*******************************************************************************
* Copyright (c) 2000, 2014 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
*     IBM Corporation - initial API and implementation
*******************************************************************************/

package org.eclipse.jdt.core.dom.rewrite;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IImportDeclaration;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.dom.*;
import org.eclipse.jdt.internal.core.dom.rewrite.ImportRewriteAnalyzer;
import org.eclipse.jdt.internal.core.util.Messages;
import org.eclipse.jdt.internal.core.util.Util;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;


/**
* The {@link ImportRewrite} helps updating imports following a import order and on-demand imports threshold as configured by a project.
* <p>
* The import rewrite is created on a compilation unit and collects references to types that are added or removed. When adding imports, e.g. using
* {@link #addImport(String)}, the import rewrite evaluates if the type can be imported and returns the a reference to the type that can be used in code.
* This reference is either unqualified if the import could be added, or fully qualified if the import failed due to a conflict with another element of the same name.
* </p>
* <p>
* On {@link #rewriteImports(IProgressMonitor)} the rewrite translates these descriptions into
* text edits that can then be applied to the original source. The rewrite infrastructure tries to generate minimal text changes and only
* works on the import statements. It is possible to combine the result of an import rewrite with the result of a {@link org.eclipse.jdt.core.dom.rewrite.ASTRewrite}
* as long as no import statements are modified by the AST rewrite.
* </p>
* <p>The options controlling the import order and on-demand thresholds are:
* <ul><li>{@link #setImportOrder(String[])} specifies the import groups and their preferred order</li>
* <li>{@link #setOnDemandImportThreshold(int)} specifies the number of imports in a group needed for a on-demand import statement (star import)</li>
* <li>{@link #setStaticOnDemandImportThreshold(int)} specifies the number of static imports in a group needed for a on-demand import statement (star import)</li>
*</ul>
* This class is not intended to be subclassed.
* </p>
* @since 3.2
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public final class ImportRewrite {

  /**
   * A {@link ImportRewrite.ImportRewriteContext} can optionally be used in e.g. {@link ImportRewrite#addImport(String, ImportRewrite.ImportRewriteContext)} to
   * give more information about the types visible in the scope. These types can be for example inherited inner types where it is
   * unnecessary to add import statements for.
   *
   * </p>
   * <p>
   * This class can be implemented by clients.
   * </p>
   */
  public static abstract class ImportRewriteContext {

    /**
     * Result constant signaling that the given element is know in the context.
     */
    public final static int RES_NAME_FOUND= 1;

    /**
     * Result constant signaling that the given element is not know in the context.
     */
    public final static int RES_NAME_UNKNOWN= 2;

    /**
     * Result constant signaling that the given element is conflicting with an other element in the context.
     */
    public final static int RES_NAME_CONFLICT= 3;

    /**
     * Kind constant specifying that the element is a type import.
     */
    public final static int KIND_TYPE= 1;

    /**
     * Kind constant specifying that the element is a static field import.
     */
    public final static int KIND_STATIC_FIELD= 2;

    /**
     * Kind constant specifying that the element is a static method import.
     */
    public final static int KIND_STATIC_METHOD= 3;

    /**
     * Searches for the given element in the context and reports if the element is known ({@link #RES_NAME_FOUND}),
     * unknown ({@link #RES_NAME_UNKNOWN}) or if its name conflicts ({@link #RES_NAME_CONFLICT}) with an other element.
     * @param qualifier The qualifier of the element, can be package or the qualified name of a type
     * @param name The simple name of the element; either a type, method or field name or * for on-demand imports.
     * @param kind The kind of the element. Can be either {@link #KIND_TYPE}, {@link #KIND_STATIC_FIELD} or
     * {@link #KIND_STATIC_METHOD}. Implementors should be prepared for new, currently unspecified kinds and return
     * {@link #RES_NAME_UNKNOWN} by default.
     * @return Returns the result of the lookup. Can be either {@link #RES_NAME_FOUND}, {@link #RES_NAME_UNKNOWN} or
     * {@link #RES_NAME_CONFLICT}.
     */
    public abstract int findInContext(String qualifier, String name, int kind);
  }

  private static final char STATIC_PREFIX= 's';
  private static final char NORMAL_PREFIX= 'n';

  private final ImportRewriteContext defaultContext;

  private final ICompilationUnit compilationUnit;
  private final CompilationUnit astRoot;

  private final boolean restoreExistingImports;
  private final List existingImports;
  private final Map importsKindMap;

  private String[] importOrder;
  private int importOnDemandThreshold;
  private int staticImportOnDemandThreshold;

  private List addedImports;
  private List removedImports;

  private String[] createdImports;
  private String[] createdStaticImports;

  private boolean filterImplicitImports;
  private boolean useContextToFilterImplicitImports;

  /**
   * Creates an {@link ImportRewrite} from an {@link ICompilationUnit}. If <code>restoreExistingImports</code>
   * is <code>true</code>, all existing imports are kept, and new imports will be inserted at best matching locations. If
   * <code>restoreExistingImports</code> is <code>false</code>, the existing imports will be removed and only the
   * newly added imports will be created.
   * <p>
   * Note that {@link #create(CompilationUnit, boolean)} is more efficient than this method if an AST for
   * the compilation unit is already available.
   * </p>
   * @param cu the compilation unit to create the imports for
   * @param restoreExistingImports specifies if the existing imports should be kept or removed.
   * @return the created import rewriter.
   * @throws JavaModelException thrown when the compilation unit could not be accessed.
   */
  public static ImportRewrite create(ICompilationUnit cu, boolean restoreExistingImports) throws JavaModelException {
    if (cu == null) {
      throw new IllegalArgumentException("Compilation unit must not be null"); //$NON-NLS-1$
    }
    List existingImport= null;
    if (restoreExistingImports) {
      existingImport= new ArrayList();
      IImportDeclaration[] imports= cu.getImports();
      for (int i= 0; i < imports.length; i++) {
        IImportDeclaration curr= imports[i];
        char prefix= Flags.isStatic(curr.getFlags()) ? STATIC_PREFIX : NORMAL_PREFIX;
        existingImport.add(prefix + curr.getElementName());
      }
    }
    return new ImportRewrite(cu, null, existingImport);
  }

  /**
   * Creates an {@link ImportRewrite} from an AST ({@link CompilationUnit}). The AST has to be created from an
   * {@link ICompilationUnit}, that means {@link ASTParser#setSource(ICompilationUnit)} has been used when creating the
   * AST. If <code>restoreExistingImports</code> is <code>true</code>, all existing imports are kept, and new imports
   * will be inserted at best matching locations. If <code>restoreExistingImports</code> is <code>false</code>, the
   * existing imports will be removed and only the newly added imports will be created.
   * <p>
   * Note that this method is more efficient than using {@link #create(ICompilationUnit, boolean)} if an AST is already available.
   * </p>
   * @param astRoot the AST root node to create the imports for
   * @param restoreExistingImports specifies if the existing imports should be kept or removed.
   * @return the created import rewriter.
   * @throws IllegalArgumentException thrown when the passed AST is null or was not created from a compilation unit.
   */
  public static ImportRewrite create(CompilationUnit astRoot, boolean restoreExistingImports) {
    if (astRoot == null) {
      throw new IllegalArgumentException("AST must not be null"); //$NON-NLS-1$
    }
    ITypeRoot typeRoot = astRoot.getTypeRoot();
    if (!(typeRoot instanceof ICompilationUnit)) {
      throw new IllegalArgumentException("AST must have been constructed from a Java element"); //$NON-NLS-1$
    }
    List existingImport= null;
    if (restoreExistingImports) {
      existingImport= new ArrayList();
      List imports= astRoot.imports();
      for (int i= 0; i < imports.size(); i++) {
        ImportDeclaration curr= (ImportDeclaration) imports.get(i);
        StringBuffer buf= new StringBuffer();
        buf.append(curr.isStatic() ? STATIC_PREFIX : NORMAL_PREFIX).append(curr.getName().getFullyQualifiedName());
        if (curr.isOnDemand()) {
          if (buf.length() > 1)
            buf.append('.');
          buf.append('*');
        }
        existingImport.add(buf.toString());
      }
    }
    return new ImportRewrite((ICompilationUnit) typeRoot, astRoot, existingImport);
  }

  private ImportRewrite(ICompilationUnit cu, CompilationUnit astRoot, List existingImports) {
    this.compilationUnit= cu;
    this.astRoot= astRoot; // might be null
    if (existingImports != null) {
      this.existingImports= existingImports;
      this.restoreExistingImports= !existingImports.isEmpty();
    } else {
      this.existingImports= new ArrayList();
      this.restoreExistingImports= false;
    }
    this.filterImplicitImports= true;
    // consider that no contexts are used
    this.useContextToFilterImplicitImports = false;

    this.defaultContext= new ImportRewriteContext() {
      public int findInContext(String qualifier, String name, int kind) {
        return findInImports(qualifier, name, kind);
      }
    };
    this.addedImports= null; // Initialized on use
    this.removedImports= null; // Initialized on use
    this.createdImports= null;
    this.createdStaticImports= null;

    this.importOrder= CharOperation.NO_STRINGS;
    this.importOnDemandThreshold= 99;
    this.staticImportOnDemandThreshold= 99;
   
    this.importsKindMap = new HashMap();
  }


   /**
   * Defines the import groups and order to be used by the {@link ImportRewrite}.
   * Imports are added to the group matching their qualified name most. The empty group name groups all imports not matching
   * any other group. Static imports are managed in separate groups. Static import group names are prefixed with a '#' character.
   * @param order A list of strings defining the import groups. A group name must be a valid package name or empty. If can be
   * prefixed by the '#' character for static import groups
   */
  public void setImportOrder(String[] order) {
    if (order == null)
      throw new IllegalArgumentException("Order must not be null"); //$NON-NLS-1$
    this.importOrder= order;
  }

   /**
   *  Sets the on-demand import threshold for normal (non-static) imports.
   *  This threshold defines the number of imports that need to be in a group to use
   * a on-demand (star) import declaration instead.
   *
   * @param threshold a positive number defining the on-demand import threshold
   * for normal (non-static) imports.
   * @throws IllegalArgumentException a {@link IllegalArgumentException} is thrown
   * if the number is not positive.
     */
  public void setOnDemandImportThreshold(int threshold) {
    if (threshold <= 0)
      throw new IllegalArgumentException("Threshold must be positive."); //$NON-NLS-1$
    this.importOnDemandThreshold= threshold;
  }

   /**
   *  Sets the on-demand import threshold for static imports.
   *  This threshold defines the number of imports that need to be in a group to use
   * a on-demand (star) import declaration instead.
   *
   * @param threshold a positive number defining the on-demand import threshold
   * for normal (non-static) imports.
   * @throws IllegalArgumentException a {@link IllegalArgumentException} is thrown
   * if the number is not positive.
     */
  public void setStaticOnDemandImportThreshold(int threshold) {
    if (threshold <= 0)
      throw new IllegalArgumentException("Threshold must be positive."); //$NON-NLS-1$
    this.staticImportOnDemandThreshold= threshold;
  }

  /**
   * The compilation unit for which this import rewrite was created for.
   * @return the compilation unit for which this import rewrite was created for.
   */
  public ICompilationUnit getCompilationUnit() {
    return this.compilationUnit;
  }

  /**
   * Returns the default rewrite context that only knows about the imported types. Clients
   * can write their own context and use the default context for the default behavior.
   * @return the default import rewrite context.
   */
  public ImportRewriteContext getDefaultImportRewriteContext() {
    return this.defaultContext;
  }

  /**
   * Specifies that implicit imports (for types in <code>java.lang</code>, types in the same package as the rewrite
   * compilation unit, and types in the compilation unit's main type) should not be created, except if necessary to
   * resolve an on-demand import conflict.
   * <p>
   * The filter is enabled by default.
   * </p>
   * <p>
   * Note: {@link #setUseContextToFilterImplicitImports(boolean)} can be used to filter implicit imports
   * when a context is used.
   * </p>
   *
   * @param filterImplicitImports
   *            if <code>true</code>, implicit imports will be filtered
   *
   * @see #setUseContextToFilterImplicitImports(boolean)
   */
  public void setFilterImplicitImports(boolean filterImplicitImports) {
    this.filterImplicitImports= filterImplicitImports;
  }

  /**
  * Sets whether a context should be used to properly filter implicit imports.
  * <p>
  * By default, the option is disabled to preserve pre-3.6 behavior.
  * </p>
  * <p>
  * When this option is set, the context passed to the <code>addImport*(...)</code> methods is used to determine
  * whether an import can be filtered because the type is implicitly visible. Note that too many imports
  * may be kept if this option is set and <code>addImport*(...)</code> methods are called without a context.
  * </p>
  *
  * @param useContextToFilterImplicitImports the given setting
  *
  * @see #setFilterImplicitImports(boolean)
  * @since 3.6
  */
  public void setUseContextToFilterImplicitImports(boolean useContextToFilterImplicitImports) {
    this.useContextToFilterImplicitImports = useContextToFilterImplicitImports;
  }
 
  private static int compareImport(char prefix, String qualifier, String name, String curr) {
    if (curr.charAt(0) != prefix || !curr.endsWith(name)) {
      return ImportRewriteContext.RES_NAME_UNKNOWN;
    }

    curr= curr.substring(1); // remove the prefix

    if (curr.length() == name.length()) {
      if (qualifier.length() == 0) {
        return ImportRewriteContext.RES_NAME_FOUND;
      }
      return ImportRewriteContext.RES_NAME_CONFLICT;
    }
    // at this place: curr.length > name.length

    int dotPos= curr.length() - name.length() - 1;
    if (curr.charAt(dotPos) != '.') {
      return ImportRewriteContext.RES_NAME_UNKNOWN;
    }
    if (qualifier.length() != dotPos || !curr.startsWith(qualifier)) {
      return ImportRewriteContext.RES_NAME_CONFLICT;
    }
    return ImportRewriteContext.RES_NAME_FOUND;
  }

  /**
   * Not API, package visibility as accessed from an anonymous type
   */
  /* package */ final int findInImports(String qualifier, String name, int kind) {
    boolean allowAmbiguity=  (kind == ImportRewriteContext.KIND_STATIC_METHOD) || (name.length() == 1 && name.charAt(0) == '*');
    List imports= this.existingImports;
    char prefix= (kind == ImportRewriteContext.KIND_TYPE) ? NORMAL_PREFIX : STATIC_PREFIX;

    for (int i= imports.size() - 1; i >= 0 ; i--) {
      String curr= (String) imports.get(i);
      int res= compareImport(prefix, qualifier, name, curr);
      if (res != ImportRewriteContext.RES_NAME_UNKNOWN) {
        if (!allowAmbiguity || res == ImportRewriteContext.RES_NAME_FOUND) {
          if (prefix != STATIC_PREFIX) {
            return res;
          }
          Object currKind = this.importsKindMap.get(curr.substring(1));
          if (currKind != null && currKind.equals(this.importsKindMap.get(qualifier + '.' + name))) {
            return res;
          }
        }
      }
    }
    if (this.filterImplicitImports && this.useContextToFilterImplicitImports) {
      String fPackageName= this.compilationUnit.getParent().getElementName();
      String mainTypeSimpleName= JavaCore.removeJavaLikeExtension(this.compilationUnit.getElementName());
      String fMainTypeName= Util.concatenateName(fPackageName, mainTypeSimpleName, '.');
      if (kind == ImportRewriteContext.KIND_TYPE
          && (qualifier.equals(fPackageName)
              || fMainTypeName.equals(Util.concatenateName(qualifier, name, '.'))))
        return ImportRewriteContext.RES_NAME_FOUND;
    }
    return ImportRewriteContext.RES_NAME_UNKNOWN;
  }

  /**
   * Adds the necessary imports for the given annotation binding to the rewriter's record
   * and returns an {@link Annotation} that can be used in the code.
   * <p>
   * No imports are added for types that are already known. If an import for a type is recorded to be removed, this record is discarded instead.
   * </p>
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records newly added imports.
   * </p>
   * @param annotation the annotation to be added
   * @param ast the AST to create the returned annotation for
   * @param context an optional context that knows about types visible in the current scope or <code>null</code>
   * to use the default context (only using the available imports)
   * @return an annotation node. The returned annotation contains unqualified type names where
   * an import could be added or was already known. Type names are fully qualified if an import conflict prevented an import.
   *
   * @since 3.10
   */
  public Annotation addAnnotation(IAnnotationBinding annotation, AST ast, ImportRewriteContext context) {
    Type type = addImport(annotation.getAnnotationType(), ast, context);
    Name name;
    if (type instanceof SimpleType) {
      SimpleType simpleType = (SimpleType) type;
      name = simpleType.getName();
      // cut 'name' loose from its parent, so that it can be reused
      simpleType.setName(ast.newName("a")); //$NON-NLS-1$
    } else {
      name = ast.newName("invalid"); //$NON-NLS-1$
    }

    IMemberValuePairBinding[] mvps= annotation.getDeclaredMemberValuePairs();
    if (mvps.length == 0) {
      MarkerAnnotation result = ast.newMarkerAnnotation();
      result.setTypeName(name);
      return result;
    } else if (mvps.length == 1 && "value".equals(mvps[0].getName())) { //$NON-NLS-1$
      SingleMemberAnnotation result= ast.newSingleMemberAnnotation();
      result.setTypeName(name);
      Object value = mvps[0].getValue();
      if (value != null)
        result.setValue(addAnnotation(ast, value, context));
      return result;
    } else {
      NormalAnnotation result = ast.newNormalAnnotation();
      result.setTypeName(name);
      for (int i= 0; i < mvps.length; i++) {
        IMemberValuePairBinding mvp = mvps[i];
        MemberValuePair mvpNode = ast.newMemberValuePair();
        mvpNode.setName(ast.newSimpleName(mvp.getName()));
        Object value = mvp.getValue();
        if (value != null)
          mvpNode.setValue(addAnnotation(ast, value, context));
        result.values().add(mvpNode);
      }
      return result;
    }
  }

  /**
   * Adds a new import to the rewriter's record and returns a {@link Type} node that can be used
   * in the code as a reference to the type. The type binding can be an array binding, type variable or wildcard.
   * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
   * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
   * of wildcards are ignored.
   *   <p>
    * No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
   * </p>
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records that a new import has been added.
   * </p>
   * @param typeSig the signature of the type to be added.
   * @param ast the AST to create the returned type for.
   * @return a type node for the given type signature. Type names are simple names if an import could be used,
   * or else qualified names if an import conflict prevented an import.
   */
  public Type addImportFromSignature(String typeSig, AST ast) {
    return addImportFromSignature(typeSig, ast, this.defaultContext);
  }

  /**
   * Adds a new import to the rewriter's record and returns a {@link Type} node that can be used
   * in the code as a reference to the type. The type binding can be an array binding, type variable or wildcard.
   * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
   * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
   * of wildcards are ignored.
   *   <p>
    * No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
   * </p>
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records that a new import has been added.
   * </p>
   * @param typeSig the signature of the type to be added.
   * @param ast the AST to create the returned type for.
   * @param context an optional context that knows about types visible in the current scope or <code>null</code>
   * to use the default context only using the available imports.
   * @return a type node for the given type signature. Type names are simple names if an import could be used,
   * or else qualified names if an import conflict prevented an import.
   */
  public Type addImportFromSignature(String typeSig, AST ast, ImportRewriteContext context) {
    if (typeSig == null || typeSig.length() == 0) {
      throw new IllegalArgumentException("Invalid type signature: empty or null"); //$NON-NLS-1$
    }
    int sigKind= Signature.getTypeSignatureKind(typeSig);
    switch (sigKind) {
      case Signature.BASE_TYPE_SIGNATURE:
        return ast.newPrimitiveType(PrimitiveType.toCode(Signature.toString(typeSig)));
      case Signature.ARRAY_TYPE_SIGNATURE:
        Type elementType= addImportFromSignature(Signature.getElementType(typeSig), ast, context);
        return ast.newArrayType(elementType, Signature.getArrayCount(typeSig));
      case Signature.CLASS_TYPE_SIGNATURE:
        String erasureSig= Signature.getTypeErasure(typeSig);

        String erasureName= Signature.toString(erasureSig);
        if (erasureSig.charAt(0) == Signature.C_RESOLVED) {
          erasureName= internalAddImport(erasureName, context);
        }
        Type baseType= ast.newSimpleType(ast.newName(erasureName));
        String[] typeArguments= Signature.getTypeArguments(typeSig);
        if (typeArguments.length > 0) {
          ParameterizedType type= ast.newParameterizedType(baseType);
          List argNodes= type.typeArguments();
          for (int i= 0; i < typeArguments.length; i++) {
            String curr= typeArguments[i];
            if (containsNestedCapture(curr)) { // see bug 103044
              argNodes.add(ast.newWildcardType());
            } else {
              argNodes.add(addImportFromSignature(curr, ast, context));
            }
          }
          return type;
        }
        return baseType;
      case Signature.TYPE_VARIABLE_SIGNATURE:
        return ast.newSimpleType(ast.newSimpleName(Signature.toString(typeSig)));
      case Signature.WILDCARD_TYPE_SIGNATURE:
        WildcardType wildcardType= ast.newWildcardType();
        char ch= typeSig.charAt(0);
        if (ch != Signature.C_STAR) {
          Type bound= addImportFromSignature(typeSig.substring(1), ast, context);
          wildcardType.setBound(bound, ch == Signature.C_EXTENDS);
        }
        return wildcardType;
      case Signature.CAPTURE_TYPE_SIGNATURE:
        return addImportFromSignature(typeSig.substring(1), ast, context);
      default:
        throw new IllegalArgumentException("Unknown type signature kind: " + typeSig); //$NON-NLS-1$
    }
  }



  /**
   * Adds a new import to the rewriter's record and returns a type reference that can be used
   * in the code. The type binding can be an array binding, type variable or wildcard.
   * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
   * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
   * of wildcards are ignored. Type annotations are ignored.
   *   <p>
    * No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
   * </p>
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records that a new import has been added.
   * </p>
   * @param binding the signature of the type to be added.
   * @return a type reference for the given type binding. Type names are simple names if an import could be used,
   * or else qualified names if an import conflict prevented an import.
   */
  public String addImport(ITypeBinding binding) {
    return addImport(binding, this.defaultContext);
  }

  /**
   * Adds a new import to the rewriter's record and returns a type reference that can be used
   * in the code. The type binding can be an array binding, type variable or wildcard.
   * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
   * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
   * of wildcards are ignored. Type annotations are ignored.
   *   <p>
    * No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
   * </p>
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records that a new import has been added.
   * </p>
   * @param binding the signature of the type to be added.
   * @param context an optional context that knows about types visible in the current scope or <code>null</code>
   * to use the default context only using the available imports.
   * @return a type reference for the given type binding. Type names are simple names if an import could be used,
   * or else qualified names if an import conflict prevented an import.
   */
  public String addImport(ITypeBinding binding, ImportRewriteContext context) {
    if (binding.isPrimitive() || binding.isTypeVariable() || binding.isRecovered()) {
      return binding.getName();
    }

    ITypeBinding normalizedBinding= normalizeTypeBinding(binding);
    if (normalizedBinding == null) {
      return "invalid"; //$NON-NLS-1$
    }
    if (normalizedBinding.isWildcardType()) {
      StringBuffer res= new StringBuffer("?"); //$NON-NLS-1$
      ITypeBinding bound= normalizedBinding.getBound();
      if (bound != null && !bound.isWildcardType() && !bound.isCapture()) { // bug 95942
        if (normalizedBinding.isUpperbound()) {
          res.append(" extends "); //$NON-NLS-1$
        } else {
          res.append(" super "); //$NON-NLS-1$
        }
        res.append(addImport(bound, context));
      }
      return res.toString();
    }

    if (normalizedBinding.isArray()) {
      StringBuffer res= new StringBuffer(addImport(normalizedBinding.getElementType(), context));
      for (int i= normalizedBinding.getDimensions(); i > 0; i--) {
        res.append("[]"); //$NON-NLS-1$
      }
      return res.toString();
    }

    String qualifiedName= getRawQualifiedName(normalizedBinding);
    if (qualifiedName.length() > 0) {
      String str= internalAddImport(qualifiedName, context);

      ITypeBinding[] typeArguments= normalizedBinding.getTypeArguments();
      if (typeArguments.length > 0) {
        StringBuffer res= new StringBuffer(str);
        res.append('<');
        for (int i= 0; i < typeArguments.length; i++) {
          if (i > 0) {
            res.append(',');
          }
          ITypeBinding curr= typeArguments[i];
          if (containsNestedCapture(curr, false)) { // see bug 103044
            res.append('?');
          } else {
            res.append(addImport(curr, context));
          }
        }
        res.append('>');
        return res.toString();
      }
      return str;
    }
    return getRawName(normalizedBinding);
  }

  private boolean containsNestedCapture(ITypeBinding binding, boolean isNested) {
    if (binding == null || binding.isPrimitive() || binding.isTypeVariable()) {
      return false;
    }
    if (binding.isCapture()) {
      if (isNested) {
        return true;
      }
      return containsNestedCapture(binding.getWildcard(), true);
    }
    if (binding.isWildcardType()) {
      return containsNestedCapture(binding.getBound(), true);
    }
    if (binding.isArray()) {
      return containsNestedCapture(binding.getElementType(), true);
    }
    ITypeBinding[] typeArguments= binding.getTypeArguments();
    for (int i= 0; i < typeArguments.length; i++) {
      if (containsNestedCapture(typeArguments[i], true)) {
        return true;
      }
    }
    return false;
  }

  private boolean containsNestedCapture(String signature) {
    return signature.length() > 1 && signature.indexOf(Signature.C_CAPTURE, 1) != -1;
  }

  private static ITypeBinding normalizeTypeBinding(ITypeBinding binding) {
    if (binding != null && !binding.isNullType() && !"void".equals(binding.getName())) { //$NON-NLS-1$
      if (binding.isAnonymous()) {
        ITypeBinding[] baseBindings= binding.getInterfaces();
        if (baseBindings.length > 0) {
          return baseBindings[0];
        }
        return binding.getSuperclass();
      }
      if (binding.isCapture()) {
        return binding.getWildcard();
      }
      return binding;
    }
    return null;
  }

  /**
   * Adds a new import to the rewriter's record and returns a {@link Type} that can be used
   * in the code. The type binding can be an array binding, type variable or wildcard.
   * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
   * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
   * of wildcards are ignored. If type annotations or type arguments are present at any point, the import is added up to that point and
   * the type is retained from that point with type annotations and type arguments.
   *   <p>
    * No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
   * </p>
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records that a new import has been added.
   * </p>
   * @param binding the signature of the type to be added.
   * @param ast the AST to create the returned type for.
   * @return a type node for the given type binding. Type names are simple names if an import could be used,
   * or else qualified names if an import conflict prevented an import.
   */
  public Type addImport(ITypeBinding binding, AST ast) {
    return addImport(binding, ast, this.defaultContext);
  }

  /**
   * Adds a new import to the rewriter's record and returns a {@link Type} that can be used
   * in the code. The type binding can be an array binding, type variable or wildcard.
   * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
   * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
   * of wildcards are ignored. If type annotations or type arguments are present at any point, the import is added up to that point and
   * the type is retained from that point with type annotations and type arguments
   *   <p>
    * No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
   * </p>
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records that a new import has been added.
   * </p>
   * @param binding the signature of the type to be added.
   * @param ast the AST to create the returned type for.
   * @param context an optional context that knows about types visible in the current scope or <code>null</code>
   * to use the default context only using the available imports.
   * @return a type node for the given type binding. Type names are simple names if an import could be used,
   * or else qualified names if an import conflict prevented an import.
   */
  public Type addImport(ITypeBinding binding, AST ast, ImportRewriteContext context) {
    ITypeBinding bindingPoint = checkAnnotationAndGenerics(binding);
    Type type = internalAddImport(bindingPoint == null ? binding : bindingPoint, ast, context, null, /* getBase */ true);
    if (bindingPoint != null && !bindingPoint.equals(binding)) {
      type = buildType(binding, bindingPoint, ast, context, type);
    }
    return type;
  }

  /**
   * Adds a new import to the rewriter's record and returns a type reference that can be used
   * in the code. The type binding can only be an array or non-generic type.
   *   <p>
    * No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
   * </p>
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records that a new import has been added.
   * </p>
   * @param qualifiedTypeName the qualified type name of the type to be added
   * @param context an optional context that knows about types visible in the current scope or <code>null</code>
   * to use the default context only using the available imports.
   * @return a type reference for the given qualified type name. The type name is a simple name if an import could be used,
   * or else a qualified name if an import conflict prevented an import.
   */
  public String addImport(String qualifiedTypeName, ImportRewriteContext context) {
    int angleBracketOffset= qualifiedTypeName.indexOf('<');
    if (angleBracketOffset != -1) {
      return internalAddImport(qualifiedTypeName.substring(0, angleBracketOffset), context) + qualifiedTypeName.substring(angleBracketOffset);
    }
    int bracketOffset= qualifiedTypeName.indexOf('[');
    if (bracketOffset != -1) {
      return internalAddImport(qualifiedTypeName.substring(0, bracketOffset), context) + qualifiedTypeName.substring(bracketOffset);
    }
    return internalAddImport(qualifiedTypeName, context);
  }

  /**
   * Adds a new import to the rewriter's record and returns a type reference that can be used
   * in the code. The type binding can only be an array or non-generic type.
   *   <p>
    * No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
   * </p>
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records that a new import has been added.
   * </p>
   * @param qualifiedTypeName the qualified type name of the type to be added
   * @return a type reference for the given qualified type name. The type name is a simple name if an import could be used,
   * or else a qualified name if an import conflict prevented an import.
   */
  public String addImport(String qualifiedTypeName) {
    return addImport(qualifiedTypeName, this.defaultContext);
  }

  /**
   * Adds a new static import to the rewriter's record and returns a name - single member name if
   * import is successful, else qualified name.
   *   <p>
    * No imports are added for members that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
   * </p>
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records that a new import has been added.
   * </p>
   * @param binding The binding of the static field or method to be added.
   * @return either the simple member name if the import was successful or else the qualified name if
   * an import conflict prevented the import
   * @throws IllegalArgumentException an {@link IllegalArgumentException} is thrown if the binding is not a static field
   * or method.
   */
  public String addStaticImport(IBinding binding) {
    return addStaticImport(binding, this.defaultContext);
  }

  /**
   * Adds a new static import to the rewriter's record and returns a name - single member name if
   * import is successful, else qualified name.
   *   <p>
    * No imports are added for members that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
   * </p>
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records that a new import has been added.
   * </p>
   * @param binding The binding of the static field or method to be added.
   * @param context an optional context that knows about members visible in the current scope or <code>null</code>
   * to use the default context only using the available imports.
   * @return either the simple member name if the import was successful or else the qualified name if
   * an import conflict prevented the import
   * @throws IllegalArgumentException an {@link IllegalArgumentException} is thrown if the binding is not a static field
   * or method.
   */
  public String addStaticImport(IBinding binding, ImportRewriteContext context) {
    if (Modifier.isStatic(binding.getModifiers())) {
      if (binding instanceof IVariableBinding) {
        IVariableBinding variableBinding= (IVariableBinding) binding;
        if (variableBinding.isField()) {
          ITypeBinding declaringType= variableBinding.getDeclaringClass();
          return addStaticImport(getRawQualifiedName(declaringType), binding.getName(), true, context);
        }
      } else if (binding instanceof IMethodBinding) {
        ITypeBinding declaringType= ((IMethodBinding) binding).getDeclaringClass();
        return addStaticImport(getRawQualifiedName(declaringType), binding.getName(), false, context);
      }
    }
    throw new IllegalArgumentException("Binding must be a static field or method."); //$NON-NLS-1$
  }

  /**
   * Adds a new static import to the rewriter's record and returns a name - single member name if
   * import is successful, else qualified name.
   *   <p>
    * No imports are added for members that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
   * </p>
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records that a new import has been added.
   * </p>
   * @param declaringTypeName The qualified name of the static's member declaring type
   * @param simpleName the simple name of the member; either a field or a method name.
   * @param isField <code>true</code> specifies that the member is a field, <code>false</code> if it is a
   * method.
   * @return either the simple member name if the import was successful or else the qualified name if
   * an import conflict prevented the import
   */
  public String addStaticImport(String declaringTypeName, String simpleName, boolean isField) {
    return addStaticImport(declaringTypeName, simpleName, isField, this.defaultContext);
  }

  /**
   * Adds a new static import to the rewriter's record and returns a name - single member name if
   * import is successful, else qualified name.
   *   <p>
    * No imports are added for members that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
   * </p>
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records that a new import has been added.
   * </p>
   * @param declaringTypeName The qualified name of the static's member declaring type
   * @param simpleName the simple name of the member; either a field or a method name.
   * @param isField <code>true</code> specifies that the member is a field, <code>false</code> if it is a
   * method.
   * @param context an optional context that knows about members visible in the current scope or <code>null</code>
   * to use the default context only using the available imports.
   * @return either the simple member name if the import was successful or else the qualified name if
   * an import conflict prevented the import
   */
  public String addStaticImport(String declaringTypeName, String simpleName, boolean isField, ImportRewriteContext context) {
    String key = declaringTypeName + '.' + simpleName;
    if (declaringTypeName.indexOf('.') == -1) {
      return key;
    }
    if (context == null) {
      context= this.defaultContext;
    }
    int kind= isField ? ImportRewriteContext.KIND_STATIC_FIELD : ImportRewriteContext.KIND_STATIC_METHOD;
    this.importsKindMap.put(key, new Integer(kind));
    int res= context.findInContext(declaringTypeName, simpleName, kind);
    if (res == ImportRewriteContext.RES_NAME_CONFLICT) {
      return key;
    }
    if (res == ImportRewriteContext.RES_NAME_UNKNOWN) {
      addEntry(STATIC_PREFIX + key);
    }
    return simpleName;
  }

  private String internalAddImport(String fullTypeName, ImportRewriteContext context) {
    int idx= fullTypeName.lastIndexOf('.');
    String typeContainerName, typeName;
    if (idx != -1) {
      typeContainerName= fullTypeName.substring(0, idx);
      typeName= fullTypeName.substring(idx + 1);
    } else {
      typeContainerName= ""; //$NON-NLS-1$
      typeName= fullTypeName;
    }

    if (typeContainerName.length() == 0 && PrimitiveType.toCode(typeName) != null) {
      return fullTypeName;
    }

    if (context == null)
      context= this.defaultContext;

    int res= context.findInContext(typeContainerName, typeName, ImportRewriteContext.KIND_TYPE);
    if (res == ImportRewriteContext.RES_NAME_CONFLICT) {
      return fullTypeName;
    }
    if (res == ImportRewriteContext.RES_NAME_UNKNOWN) {
      addEntry(NORMAL_PREFIX + fullTypeName);
    }
    return typeName;
  }

  private void addEntry(String entry) {
    this.existingImports.add(entry);

    if (this.removedImports != null) {
      if (this.removedImports.remove(entry)) {
        return;
      }
    }

    if (this.addedImports == null) {
      this.addedImports= new ArrayList();
    }
    this.addedImports.add(entry);
  }

  private boolean removeEntry(String entry) {
    if (this.existingImports.remove(entry)) {
      if (this.addedImports != null) {
        if (this.addedImports.remove(entry)) {
          return true;
        }
      }
      if (this.removedImports == null) {
        this.removedImports= new ArrayList();
      }
      this.removedImports.add(entry);
      return true;
    }
    return false;
  }

  /**
   * Records to remove a import. No remove is recorded if no such import exists or if such an import is recorded
   * to be added. In that case the record of the addition is discarded.
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records that an import has been removed.
   * </p>
   * @param qualifiedName The import name to remove.
   * @return <code>true</code> is returned of an import of the given name could be found.
   */
  public boolean removeImport(String qualifiedName) {
    return removeEntry(NORMAL_PREFIX + qualifiedName);
  }

  /**
   * Records to remove a static import. No remove is recorded if no such import exists or if such an import is recorded
   * to be added. In that case the record of the addition is discarded.
   * <p>
   * The content of the compilation unit itself is actually not modified
   * in any way by this method; rather, the rewriter just records that a new import has been removed.
   * </p>
   * @param qualifiedName The import name to remove.
   * @return <code>true</code> is returned of an import of the given name could be found.
   */
  public boolean removeStaticImport(String qualifiedName) {
    return removeEntry(STATIC_PREFIX + qualifiedName);
  }

  private static String getRawName(ITypeBinding normalizedBinding) {
    return normalizedBinding.getTypeDeclaration().getName();
  }

  private static String getRawQualifiedName(ITypeBinding normalizedBinding) {
    return normalizedBinding.getTypeDeclaration().getQualifiedName();
  }


  /**
   * Converts all modifications recorded by this rewriter into an object representing the corresponding text
   * edits to the source code of the rewrite's compilation unit. The compilation unit itself is not modified.
   * <p>
   * Calling this methods does not discard the modifications on record. Subsequence modifications are added
   * to the ones already on record. If this method is called again later, the resulting text edit object will accurately
   * reflect the net cumulative effect of all those changes.
   * </p>
   * @param monitor the progress monitor or <code>null</code>
   * @return text edit object describing the changes to the document corresponding to the changes
   * recorded by this rewriter
   * @throws CoreException the exception is thrown if the rewrite fails.
   */
  public final TextEdit rewriteImports(IProgressMonitor monitor) throws CoreException {
    if (monitor == null) {
      monitor= new NullProgressMonitor();
    }

    try {
      monitor.beginTask(Messages.bind(Messages.importRewrite_processDescription), 2);
      if (!hasRecordedChanges()) {
        this.createdImports= CharOperation.NO_STRINGS;
        this.createdStaticImports= CharOperation.NO_STRINGS;
        return new MultiTextEdit();
      }

      CompilationUnit usedAstRoot= this.astRoot;
      if (usedAstRoot == null) {
        ASTParser parser= ASTParser.newParser(AST.JLS8);
        parser.setSource(this.compilationUnit);
        parser.setFocalPosition(0); // reduced AST
        parser.setResolveBindings(false);
        usedAstRoot= (CompilationUnit) parser.createAST(new SubProgressMonitor(monitor, 1));
      }

      ImportRewriteAnalyzer computer=
        new ImportRewriteAnalyzer(
            this.compilationUnit,
            usedAstRoot,
            this.importOrder,
            this.importOnDemandThreshold,
            this.staticImportOnDemandThreshold,
            this.restoreExistingImports,
            this.useContextToFilterImplicitImports);
      computer.setFilterImplicitImports(this.filterImplicitImports);

      if (this.addedImports != null) {
        for (int i= 0; i < this.addedImports.size(); i++) {
          String curr= (String) this.addedImports.get(i);
          computer.addImport(curr.substring(1), STATIC_PREFIX == curr.charAt(0), usedAstRoot, this.restoreExistingImports);
        }
      }

      if (this.removedImports != null) {
        for (int i= 0; i < this.removedImports.size(); i++) {
          String curr= (String) this.removedImports.get(i);
          computer.removeImport(curr.substring(1), STATIC_PREFIX == curr.charAt(0));
        }
      }

      TextEdit result= computer.getResultingEdits(new SubProgressMonitor(monitor, 1));
      this.createdImports= computer.getCreatedImports();
      this.createdStaticImports= computer.getCreatedStaticImports();
      return result;
    } finally {
      monitor.done();
    }
  }

  /**
   * Returns all new non-static imports created by the last invocation of {@link #rewriteImports(IProgressMonitor)}
   * or <code>null</code> if these methods have not been called yet.
   * <p>
   *   Note that this list doesn't need to be the same as the added imports (see {@link #getAddedImports()}) as
   * implicit imports are not created and some imports are represented by on-demand imports instead.
   * </p>
   * @return the created imports
   */
  public String[] getCreatedImports() {
    return this.createdImports;
  }

  /**
   * Returns all new static imports created by the last invocation of {@link #rewriteImports(IProgressMonitor)}
   * or <code>null</code> if these methods have not been called yet.
   * <p>
   * Note that this list doesn't need to be the same as the added static imports ({@link #getAddedStaticImports()}) as
   * implicit imports are not created and some imports are represented by on-demand imports instead.
   * </p
   * @return the created imports
   */
  public String[] getCreatedStaticImports() {
    return this.createdStaticImports;
  }

  /**
   * Returns all non-static imports that are recorded to be added.
   *
   * @return the imports recorded to be added.
   */
  public String[] getAddedImports() {
    return filterFromList(this.addedImports, NORMAL_PREFIX);
  }

  /**
   * Returns all static imports that are recorded to be added.
   *
   * @return the static imports recorded to be added.
   */
  public String[] getAddedStaticImports() {
    return filterFromList(this.addedImports, STATIC_PREFIX);
  }

  /**
   * Returns all non-static imports that are recorded to be removed.
   *
   * @return the imports recorded to be removed.
   */
  public String[] getRemovedImports() {
    return filterFromList(this.removedImports, NORMAL_PREFIX);
  }

  /**
   * Returns all static imports that are recorded to be removed.
   *
   * @return the static imports recorded to be removed.
   */
  public String[] getRemovedStaticImports() {
    return filterFromList(this.removedImports, STATIC_PREFIX);
  }

  /**
   * Returns <code>true</code> if imports have been recorded to be added or removed.
   * @return boolean returns if any changes to imports have been recorded.
   */
  public boolean hasRecordedChanges() {
    return !this.restoreExistingImports ||
      (this.addedImports != null && !this.addedImports.isEmpty()) ||
      (this.removedImports != null && !this.removedImports.isEmpty());
  }


  private static String[] filterFromList(List imports, char prefix) {
    if (imports == null) {
      return CharOperation.NO_STRINGS;
    }
    ArrayList res= new ArrayList();
    for (int i= 0; i < imports.size(); i++) {
      String curr= (String) imports.get(i);
      if (prefix == curr.charAt(0)) {
        res.add(curr.substring(1));
      }
    }
    return (String[]) res.toArray(new String[res.size()]);
  }

  private void annotateList(List annotations, IAnnotationBinding [] annotationBindings, AST ast, ImportRewriteContext context) {
    for (int i = 0; i< annotationBindings.length; i++) {
      Annotation annotation = addAnnotation(annotationBindings[i], ast, context);
      if (annotation != null) annotations.add(annotation);
    }
  }

  private Type annotateType(ITypeBinding binding, AST ast, ImportRewriteContext context, Type type) {
    IAnnotationBinding [] annotationBindings = binding.getTypeAnnotations();
    if (annotationBindings != null && annotationBindings.length > 0 && type instanceof AnnotatableType) {
      annotateList(((AnnotatableType) type).annotations(), annotationBindings, ast, context);
    }
    return type;
  }

  private Type buildType(ITypeBinding binding, ITypeBinding bindingPoint, AST ast, ImportRewriteContext context, Type qualifier) {
    if (binding.equals(bindingPoint)) {
      return qualifier;
    }
    // build the type recursively from left to right
    Type type = binding.isMember() ? buildType(binding.getDeclaringClass(), bindingPoint, ast, context, qualifier) : null;
    type = internalAddImport(binding, ast, context, type, false);
    return type;
  }

  private ITypeBinding checkAnnotationAndGenerics(ITypeBinding binding) {
    ITypeBinding bindingPoint = null;
    while (binding != null) {
      IAnnotationBinding annotationBinding [] = binding.getTypeAnnotations();
      ITypeBinding []  typeArguments = binding.getTypeArguments();
      if ((annotationBinding != null && annotationBinding.length > 0) ||
          (typeArguments != null && typeArguments.length > 0)) {
        bindingPoint = binding;
      }
      if (binding.isMember()) {
        binding = binding.getDeclaringClass();
      } else {
        break;
      }
    }
    return bindingPoint;
  }

  private Type createBaseType(AST ast, ImportRewriteContext context, ITypeBinding normalizedBinding) {
    Type type;
    IAnnotationBinding annotationBinding [] = normalizedBinding.getTypeAnnotations();
    boolean annotsPresent = annotationBinding != null && annotationBinding.length > 0;

    String qualifiedName= getRawQualifiedName(normalizedBinding);
    String res = qualifiedName.length() > 0 ? internalAddImport(qualifiedName, context) : getRawName(normalizedBinding);
 
    if (annotsPresent) {
      int dotIndex = res != null ? res.lastIndexOf('.') : -1;
      if (dotIndex > 0) {
        Name nameQualifier = ast.newName(res.substring(0, dotIndex));
        SimpleName simpleName = ast.newSimpleName(res.substring(dotIndex + 1));
        type = ast.newNameQualifiedType(nameQualifier, simpleName);
      } else {
        type = ast.newSimpleType(ast.newName(res));
      }
      annotateList(((AnnotatableType) type).annotations(), annotationBinding, ast, context);
    } else {
      type = ast.newSimpleType(ast.newName(res));
    }
    return type;
  }

  private Type getArrayType(Type elementType, AST ast, ImportRewriteContext context, ITypeBinding normalizedBinding) {
    int noDimensions = normalizedBinding.getDimensions();
    ArrayType arrayType = ast.newArrayType(elementType, noDimensions);
    if (ast.apiLevel() >= AST.JLS8) {
      for (int i = 0; i < noDimensions; i++) {
        IAnnotationBinding[] typeAnnotations = normalizedBinding.getTypeAnnotations();
        if (typeAnnotations.length > 0) {
          Dimension dimension = (Dimension) arrayType.dimensions().get(i);
          annotateList(dimension.annotations(), typeAnnotations, ast, context);
        }
        normalizedBinding = normalizedBinding.getComponentType();
      }
    }
    return arrayType;
  }

  private Type internalAddImport(ITypeBinding binding, AST ast, ImportRewriteContext context, Type currentType, boolean getBase) {
    Type type = null;
    ITypeBinding normalizedBinding = null;
   
    if (binding.isPrimitive()) {
      type = ast.newPrimitiveType(PrimitiveType.toCode(binding.getName()));
      normalizedBinding= binding;
    } else {
      normalizedBinding= normalizeTypeBinding(binding);
      if (normalizedBinding == null) {
        type = ast.newSimpleType(ast.newSimpleName("invalid")); //$NON-NLS-1$
      } else if (normalizedBinding.isTypeVariable()) {
          // no import
        type = ast.newSimpleType(ast.newSimpleName(binding.getName()));
      } else if (normalizedBinding.isWildcardType()) {
        WildcardType wcType= ast.newWildcardType();
        ITypeBinding bound= normalizedBinding.getBound();
        if (bound != null && !bound.isWildcardType() && !bound.isCapture()) { // bug 96942
          Type boundType= addImport(bound, ast, context);
          wcType.setBound(boundType, normalizedBinding.isUpperbound());
        }
        type = wcType;
      } else if (normalizedBinding.isArray()) {
        Type elementType= addImport(normalizedBinding.getElementType(), ast, context);
        type = getArrayType(elementType, ast, context, normalizedBinding);
      }
    }

    if (type != null) {
      return annotateType(normalizedBinding, ast, context, type);
    }

    if (getBase) {
      type = createBaseType(ast, context, normalizedBinding);
    } else  {
      type = currentType != null ? (Type) ast.newQualifiedType(currentType, ast.newSimpleName(getRawName(normalizedBinding))) :
        ast.newSimpleType(ast.newName(getRawName(normalizedBinding)));
      type = annotateType(normalizedBinding, ast, context, type);
    }

    ITypeBinding[] typeArguments = normalizedBinding.getTypeArguments();
    if (typeArguments.length > 0) {
      ParameterizedType paramType = ast.newParameterizedType(type);
      List arguments = paramType.typeArguments();
      for (int i = 0; i < typeArguments.length; i++) {
        ITypeBinding curr = typeArguments[i];
        if (containsNestedCapture(curr, false)) { // see bug 103044
          arguments.add(ast.newWildcardType());
        } else {
          arguments.add(addImport(curr, ast, context));
        }
      }
      type = paramType;
    }
    return type;
  }

  private Expression addAnnotation(AST ast, Object value, ImportRewriteContext context) {
    if (value instanceof Boolean) {
      return ast.newBooleanLiteral(((Boolean) value).booleanValue());
    } else if (value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long
        || value instanceof Float || value instanceof Double) {
      return ast.newNumberLiteral(value.toString());
    } else if (value instanceof Character) {
      CharacterLiteral result = ast.newCharacterLiteral();
      result.setCharValue(((Character) value).charValue());
      return result;
    } else if (value instanceof ITypeBinding) {
      TypeLiteral result = ast.newTypeLiteral();
      result.setType(addImport((ITypeBinding) value, ast, context));
      return result;
    } else if (value instanceof String) {
      StringLiteral result = ast.newStringLiteral();
      result.setLiteralValue((String) value);
      return result;
    } else if (value instanceof IVariableBinding) {
      IVariableBinding variable = (IVariableBinding) value;

      FieldAccess result = ast.newFieldAccess();
      result.setName(ast.newSimpleName(variable.getName()));
      Type type = addImport(variable.getType(), ast, context);
      Name name;
      if (type instanceof SimpleType) {
        SimpleType simpleType = (SimpleType) type;
        name = simpleType.getName();
        // cut 'name' loose from its parent, so that it can be reused
        simpleType.setName(ast.newSimpleName("a")); //$NON-NLS-1$
      } else {
        name = ast.newName("invalid"); //$NON-NLS-1$
      }
      result.setExpression(name);
      return result;
    } else if (value instanceof IAnnotationBinding) {
      return addAnnotation((IAnnotationBinding) value, ast, context);
    } else if (value instanceof Object[]) {
      Object[] values = (Object[]) value;
      if (values.length == 1)
        return addAnnotation(ast, values[0], context);

      ArrayInitializer initializer = ast.newArrayInitializer();
      List expressions = initializer.expressions();
      int size = values.length;
      for (int i = 0; i < size; i++)
        expressions.add(addAnnotation(ast, values[i], context));
      return initializer;
    } else {
      return null;
    }
  }
}
TOP

Related Classes of org.eclipse.jdt.core.dom.rewrite.ImportRewrite$ImportRewriteContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.