0001: /*******************************************************************************
0002: * Copyright (c) 2000, 2007 IBM Corporation and others.
0003: * All rights reserved. This program and the accompanying materials
0004: * are made available under the terms of the Eclipse Public License v1.0
0005: * which accompanies this distribution, and is available at
0006: * http://www.eclipse.org/legal/epl-v10.html
0007: *
0008: * Contributors:
0009: * IBM Corporation - initial API and implementation
0010: *******************************************************************************/package org.eclipse.jdt.core.dom.rewrite;
0011:
0012: import java.util.ArrayList;
0013: import java.util.List;
0014:
0015: import org.eclipse.core.runtime.CoreException;
0016: import org.eclipse.core.runtime.IProgressMonitor;
0017: import org.eclipse.core.runtime.NullProgressMonitor;
0018: import org.eclipse.core.runtime.SubProgressMonitor;
0019: import org.eclipse.jdt.core.Flags;
0020: import org.eclipse.jdt.core.ICompilationUnit;
0021: import org.eclipse.jdt.core.IImportDeclaration;
0022: import org.eclipse.jdt.core.ITypeRoot;
0023: import org.eclipse.jdt.core.JavaModelException;
0024: import org.eclipse.jdt.core.Signature;
0025: import org.eclipse.jdt.core.compiler.CharOperation;
0026: import org.eclipse.jdt.core.dom.*;
0027: import org.eclipse.jdt.internal.core.dom.rewrite.ImportRewriteAnalyzer;
0028: import org.eclipse.jdt.internal.core.util.Messages;
0029: import org.eclipse.text.edits.MultiTextEdit;
0030: import org.eclipse.text.edits.TextEdit;
0031:
0032: /**
0033: * The {@link ImportRewrite} helps updating imports following a import order and on-demand imports threshold as configured by a project.
0034: * <p>
0035: * 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
0036: * {@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.
0037: * 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.
0038: * </p>
0039: * <p>
0040: * On {@link #rewriteImports(IProgressMonitor)} the rewrite translates these descriptions into
0041: * text edits that can then be applied to the original source. The rewrite infrastructure tries to generate minimal text changes and only
0042: * 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}
0043: * as long as no import statements are modified by the AST rewrite.
0044: * </p>
0045: * <p>The options controlling the import order and on-demand thresholds are:
0046: * <ul><li>{@link #setImportOrder(String[])} specifies the import groups and their preferred order</li>
0047: * <li>{@link #setOnDemandImportThreshold(int)} specifies the number of imports in a group needed for a on-demand import statement (star import)</li>
0048: * <li>{@link #setStaticOnDemandImportThreshold(int)} specifies the number of static imports in a group needed for a on-demand import statement (star import)</li>
0049: *</ul>
0050: * This class is not intended to be subclassed.
0051: * </p>
0052: * @since 3.2
0053: */
0054: public final class ImportRewrite {
0055:
0056: /**
0057: * A {@link ImportRewrite.ImportRewriteContext} can optionally be used in e.g. {@link ImportRewrite#addImport(String, ImportRewrite.ImportRewriteContext)} to
0058: * give more information about the types visible in the scope. These types can be for example inherited inner types where it is
0059: * unnecessary to add import statements for.
0060: *
0061: * </p>
0062: * <p>
0063: * This class can be implemented by clients.
0064: * </p>
0065: */
0066: public static abstract class ImportRewriteContext {
0067:
0068: /**
0069: * Result constant signaling that the given element is know in the context.
0070: */
0071: public final static int RES_NAME_FOUND = 1;
0072:
0073: /**
0074: * Result constant signaling that the given element is not know in the context.
0075: */
0076: public final static int RES_NAME_UNKNOWN = 2;
0077:
0078: /**
0079: * Result constant signaling that the given element is conflicting with an other element in the context.
0080: */
0081: public final static int RES_NAME_CONFLICT = 3;
0082:
0083: /**
0084: * Kind constant specifying that the element is a type import.
0085: */
0086: public final static int KIND_TYPE = 1;
0087:
0088: /**
0089: * Kind constant specifying that the element is a static field import.
0090: */
0091: public final static int KIND_STATIC_FIELD = 2;
0092:
0093: /**
0094: * Kind constant specifying that the element is a static method import.
0095: */
0096: public final static int KIND_STATIC_METHOD = 3;
0097:
0098: /**
0099: * Searches for the given element in the context and reports if the element is known ({@link #RES_NAME_FOUND}),
0100: * unknown ({@link #RES_NAME_UNKNOWN}) or if its name conflicts ({@link #RES_NAME_CONFLICT}) with an other element.
0101: * @param qualifier The qualifier of the element, can be package or the qualified name of a type
0102: * @param name The simple name of the element; either a type, method or field name or * for on-demand imports.
0103: * @param kind The kind of the element. Can be either {@link #KIND_TYPE}, {@link #KIND_STATIC_FIELD} or
0104: * {@link #KIND_STATIC_METHOD}. Implementors should be prepared for new, currently unspecified kinds and return
0105: * {@link #RES_NAME_UNKNOWN} by default.
0106: * @return Returns the result of the lookup. Can be either {@link #RES_NAME_FOUND}, {@link #RES_NAME_UNKNOWN} or
0107: * {@link #RES_NAME_CONFLICT}.
0108: */
0109: public abstract int findInContext(String qualifier,
0110: String name, int kind);
0111: }
0112:
0113: private static final char STATIC_PREFIX = 's';
0114: private static final char NORMAL_PREFIX = 'n';
0115:
0116: private final ImportRewriteContext defaultContext;
0117:
0118: private final ICompilationUnit compilationUnit;
0119: private final CompilationUnit astRoot;
0120:
0121: private final boolean restoreExistingImports;
0122: private final List existingImports;
0123:
0124: private String[] importOrder;
0125: private int importOnDemandThreshold;
0126: private int staticImportOnDemandThreshold;
0127:
0128: private List addedImports;
0129: private List removedImports;
0130:
0131: private String[] createdImports;
0132: private String[] createdStaticImports;
0133:
0134: private boolean filterImplicitImports;
0135:
0136: /**
0137: * Creates a {@link ImportRewrite} from a {@link ICompilationUnit}. If <code>restoreExistingImports</code>
0138: * is <code>true</code>, all existing imports are kept, and new imports will be inserted at best matching locations. If
0139: * <code>restoreExistingImports</code> is <code>false</code>, the existing imports will be removed and only the
0140: * newly added imports will be created.
0141: * <p>
0142: * Note that {@link #create(ICompilationUnit, boolean)} is more efficient than this method if an AST for
0143: * the compilation unit is already available.
0144: * </p>
0145: * @param cu the compilation unit to create the imports for
0146: * @param restoreExistingImports specifies if the existing imports should be kept or removed.
0147: * @return the created import rewriter.
0148: * @throws JavaModelException thrown when the compilation unit could not be accessed.
0149: */
0150: public static ImportRewrite create(ICompilationUnit cu,
0151: boolean restoreExistingImports) throws JavaModelException {
0152: if (cu == null) {
0153: throw new IllegalArgumentException(
0154: "Compilation unit must not be null"); //$NON-NLS-1$
0155: }
0156: List existingImport = null;
0157: if (restoreExistingImports) {
0158: existingImport = new ArrayList();
0159: IImportDeclaration[] imports = cu.getImports();
0160: for (int i = 0; i < imports.length; i++) {
0161: IImportDeclaration curr = imports[i];
0162: char prefix = Flags.isStatic(curr.getFlags()) ? STATIC_PREFIX
0163: : NORMAL_PREFIX;
0164: existingImport.add(prefix + curr.getElementName());
0165: }
0166: }
0167: return new ImportRewrite(cu, null, existingImport);
0168: }
0169:
0170: /**
0171: * Creates a {@link ImportRewrite} from a an AST ({@link CompilationUnit}). The AST has to be created from a
0172: * {@link ICompilationUnit}, that means {@link ASTParser#setSource(ICompilationUnit)} has been used when creating the
0173: * AST. If <code>restoreExistingImports</code> is <code>true</code>, all existing imports are kept, and new imports
0174: * will be inserted at best matching locations. If <code>restoreExistingImports</code> is <code>false</code>, the
0175: * existing imports will be removed and only the newly added imports will be created.
0176: * <p>
0177: * Note that this method is more efficient than using {@link #create(ICompilationUnit, boolean)} if an AST is already available.
0178: * </p>
0179: * @param astRoot the AST root node to create the imports for
0180: * @param restoreExistingImports specifies if the existing imports should be kept or removed.
0181: * @return the created import rewriter.
0182: * @throws IllegalArgumentException thrown when the passed AST is null or was not created from a compilation unit.
0183: */
0184: public static ImportRewrite create(CompilationUnit astRoot,
0185: boolean restoreExistingImports) {
0186: if (astRoot == null) {
0187: throw new IllegalArgumentException("AST must not be null"); //$NON-NLS-1$
0188: }
0189: ITypeRoot typeRoot = astRoot.getTypeRoot();
0190: if (!(typeRoot instanceof ICompilationUnit)) {
0191: throw new IllegalArgumentException(
0192: "AST must have been constructed from a Java element"); //$NON-NLS-1$
0193: }
0194: List existingImport = null;
0195: if (restoreExistingImports) {
0196: existingImport = new ArrayList();
0197: List imports = astRoot.imports();
0198: for (int i = 0; i < imports.size(); i++) {
0199: ImportDeclaration curr = (ImportDeclaration) imports
0200: .get(i);
0201: StringBuffer buf = new StringBuffer();
0202: buf
0203: .append(
0204: curr.isStatic() ? STATIC_PREFIX
0205: : NORMAL_PREFIX).append(
0206: curr.getName().getFullyQualifiedName());
0207: if (curr.isOnDemand()) {
0208: if (buf.length() > 1)
0209: buf.append('.');
0210: buf.append('*');
0211: }
0212: existingImport.add(buf.toString());
0213: }
0214: }
0215: return new ImportRewrite((ICompilationUnit) typeRoot, astRoot,
0216: existingImport);
0217: }
0218:
0219: private ImportRewrite(ICompilationUnit cu, CompilationUnit astRoot,
0220: List existingImports) {
0221: this .compilationUnit = cu;
0222: this .astRoot = astRoot; // might be null
0223: if (existingImports != null) {
0224: this .existingImports = existingImports;
0225: this .restoreExistingImports = !existingImports.isEmpty();
0226: } else {
0227: this .existingImports = new ArrayList();
0228: this .restoreExistingImports = false;
0229: }
0230: this .filterImplicitImports = true;
0231:
0232: this .defaultContext = new ImportRewriteContext() {
0233: public int findInContext(String qualifier, String name,
0234: int kind) {
0235: return findInImports(qualifier, name, kind);
0236: }
0237: };
0238: this .addedImports = null; // Initialized on use
0239: this .removedImports = null; // Initialized on use
0240: this .createdImports = null;
0241: this .createdStaticImports = null;
0242:
0243: this .importOrder = CharOperation.NO_STRINGS;
0244: this .importOnDemandThreshold = 99;
0245: this .staticImportOnDemandThreshold = 99;
0246: }
0247:
0248: /**
0249: * Defines the import groups and order to be used by the {@link ImportRewrite}.
0250: * Imports are added to the group matching their qualified name most. The empty group name groups all imports not matching
0251: * any other group. Static imports are managed in separate groups. Static import group names are prefixed with a '#' character.
0252: * @param order A list of strings defining the import groups. A group name must be a valid package name or empty. If can be
0253: * prefixed by the '#' character for static import groups
0254: */
0255: public void setImportOrder(String[] order) {
0256: if (order == null)
0257: throw new IllegalArgumentException("Order must not be null"); //$NON-NLS-1$
0258: this .importOrder = order;
0259: }
0260:
0261: /**
0262: * Sets the on-demand import threshold for normal (non-static) imports.
0263: * This threshold defines the number of imports that need to be in a group to use
0264: * a on-demand (star) import declaration instead.
0265: *
0266: * @param threshold a positive number defining the on-demand import threshold
0267: * for normal (non-static) imports.
0268: * @throws IllegalArgumentException a {@link IllegalArgumentException} is thrown
0269: * if the number is not positive.
0270: */
0271: public void setOnDemandImportThreshold(int threshold) {
0272: if (threshold <= 0)
0273: throw new IllegalArgumentException(
0274: "Threshold must be positive."); //$NON-NLS-1$
0275: this .importOnDemandThreshold = threshold;
0276: }
0277:
0278: /**
0279: * Sets the on-demand import threshold for static imports.
0280: * This threshold defines the number of imports that need to be in a group to use
0281: * a on-demand (star) import declaration instead.
0282: *
0283: * @param threshold a positive number defining the on-demand import threshold
0284: * for normal (non-static) imports.
0285: * @throws IllegalArgumentException a {@link IllegalArgumentException} is thrown
0286: * if the number is not positive.
0287: */
0288: public void setStaticOnDemandImportThreshold(int threshold) {
0289: if (threshold <= 0)
0290: throw new IllegalArgumentException(
0291: "Threshold must be positive."); //$NON-NLS-1$
0292: this .staticImportOnDemandThreshold = threshold;
0293: }
0294:
0295: /**
0296: * The compilation unit for which this import rewrite was created for.
0297: * @return the compilation unit for which this import rewrite was created for.
0298: */
0299: public ICompilationUnit getCompilationUnit() {
0300: return this .compilationUnit;
0301: }
0302:
0303: /**
0304: * Returns the default rewrite context that only knows about the imported types. Clients
0305: * can write their own context and use the default context for the default behavior.
0306: * @return the default import rewrite context.
0307: */
0308: public ImportRewriteContext getDefaultImportRewriteContext() {
0309: return this .defaultContext;
0310: }
0311:
0312: /**
0313: * Specifies that implicit imports (types in default package, package <code>java.lang</code> or
0314: * in the same package as the rewrite compilation unit should not be created except if necessary
0315: * to resolve an on-demand import conflict. The filter is enabled by default.
0316: * @param filterImplicitImports if set, implicit imports will be filtered.
0317: */
0318: public void setFilterImplicitImports(boolean filterImplicitImports) {
0319: this .filterImplicitImports = filterImplicitImports;
0320: }
0321:
0322: private static int compareImport(char prefix, String qualifier,
0323: String name, String curr) {
0324: if (curr.charAt(0) != prefix || !curr.endsWith(name)) {
0325: return ImportRewriteContext.RES_NAME_UNKNOWN;
0326: }
0327:
0328: curr = curr.substring(1); // remove the prefix
0329:
0330: if (curr.length() == name.length()) {
0331: if (qualifier.length() == 0) {
0332: return ImportRewriteContext.RES_NAME_FOUND;
0333: }
0334: return ImportRewriteContext.RES_NAME_CONFLICT;
0335: }
0336: // at this place: curr.length > name.length
0337:
0338: int dotPos = curr.length() - name.length() - 1;
0339: if (curr.charAt(dotPos) != '.') {
0340: return ImportRewriteContext.RES_NAME_UNKNOWN;
0341: }
0342: if (qualifier.length() != dotPos || !curr.startsWith(qualifier)) {
0343: return ImportRewriteContext.RES_NAME_CONFLICT;
0344: }
0345: return ImportRewriteContext.RES_NAME_FOUND;
0346: }
0347:
0348: /**
0349: * Not API, package visibility as accessed from an anonymous type
0350: */
0351: /* package */final int findInImports(String qualifier,
0352: String name, int kind) {
0353: boolean allowAmbiguity = (kind == ImportRewriteContext.KIND_STATIC_METHOD)
0354: || (name.length() == 1 && name.charAt(0) == '*');
0355: List imports = this .existingImports;
0356: char prefix = (kind == ImportRewriteContext.KIND_TYPE) ? NORMAL_PREFIX
0357: : STATIC_PREFIX;
0358:
0359: for (int i = imports.size() - 1; i >= 0; i--) {
0360: String curr = (String) imports.get(i);
0361: int res = compareImport(prefix, qualifier, name, curr);
0362: if (res != ImportRewriteContext.RES_NAME_UNKNOWN) {
0363: if (!allowAmbiguity
0364: || res == ImportRewriteContext.RES_NAME_FOUND) {
0365: return res;
0366: }
0367: }
0368: }
0369: return ImportRewriteContext.RES_NAME_UNKNOWN;
0370: }
0371:
0372: /**
0373: * Adds a new import to the rewriter's record and returns a {@link Type} node that can be used
0374: * in the code as a reference to the type. The type binding can be an array binding, type variable or wildcard.
0375: * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
0376: * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
0377: * of wildcards are ignored.
0378: * <p>
0379: * 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.
0380: * </p>
0381: * <p>
0382: * The content of the compilation unit itself is actually not modified
0383: * in any way by this method; rather, the rewriter just records that a new import has been added.
0384: * </p>
0385: * @param typeSig the signature of the type to be added.
0386: * @param ast the AST to create the returned type for.
0387: * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
0388: * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
0389: */
0390: public Type addImportFromSignature(String typeSig, AST ast) {
0391: return addImportFromSignature(typeSig, ast, this .defaultContext);
0392: }
0393:
0394: /**
0395: * Adds a new import to the rewriter's record and returns a {@link Type} node that can be used
0396: * in the code as a reference to the type. The type binding can be an array binding, type variable or wildcard.
0397: * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
0398: * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
0399: * of wildcards are ignored.
0400: * <p>
0401: * 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.
0402: * </p>
0403: * <p>
0404: * The content of the compilation unit itself is actually not modified
0405: * in any way by this method; rather, the rewriter just records that a new import has been added.
0406: * </p>
0407: * @param typeSig the signature of the type to be added.
0408: * @param ast the AST to create the returned type for.
0409: * @param context an optional context that knows about types visible in the current scope or <code>null</code>
0410: * to use the default context only using the available imports.
0411: * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
0412: * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
0413: */
0414: public Type addImportFromSignature(String typeSig, AST ast,
0415: ImportRewriteContext context) {
0416: if (typeSig == null || typeSig.length() == 0) {
0417: throw new IllegalArgumentException(
0418: "Invalid type signature: empty or null"); //$NON-NLS-1$
0419: }
0420: int sigKind = Signature.getTypeSignatureKind(typeSig);
0421: switch (sigKind) {
0422: case Signature.BASE_TYPE_SIGNATURE:
0423: return ast.newPrimitiveType(PrimitiveType.toCode(Signature
0424: .toString(typeSig)));
0425: case Signature.ARRAY_TYPE_SIGNATURE:
0426: Type elementType = addImportFromSignature(Signature
0427: .getElementType(typeSig), ast, context);
0428: return ast.newArrayType(elementType, Signature
0429: .getArrayCount(typeSig));
0430: case Signature.CLASS_TYPE_SIGNATURE:
0431: String erasureSig = Signature.getTypeErasure(typeSig);
0432:
0433: String erasureName = Signature.toString(erasureSig);
0434: if (erasureSig.charAt(0) == Signature.C_RESOLVED) {
0435: erasureName = internalAddImport(erasureName, context);
0436: }
0437: Type baseType = ast.newSimpleType(ast.newName(erasureName));
0438: String[] typeArguments = Signature
0439: .getTypeArguments(typeSig);
0440: if (typeArguments.length > 0) {
0441: ParameterizedType type = ast
0442: .newParameterizedType(baseType);
0443: List argNodes = type.typeArguments();
0444: for (int i = 0; i < typeArguments.length; i++) {
0445: String curr = typeArguments[i];
0446: if (containsNestedCapture(curr)) { // see bug 103044
0447: argNodes.add(ast.newWildcardType());
0448: } else {
0449: argNodes.add(addImportFromSignature(curr, ast,
0450: context));
0451: }
0452: }
0453: return type;
0454: }
0455: return baseType;
0456: case Signature.TYPE_VARIABLE_SIGNATURE:
0457: return ast.newSimpleType(ast.newSimpleName(Signature
0458: .toString(typeSig)));
0459: case Signature.WILDCARD_TYPE_SIGNATURE:
0460: WildcardType wildcardType = ast.newWildcardType();
0461: char ch = typeSig.charAt(0);
0462: if (ch != Signature.C_STAR) {
0463: Type bound = addImportFromSignature(typeSig
0464: .substring(1), ast, context);
0465: wildcardType.setBound(bound, ch == Signature.C_EXTENDS);
0466: }
0467: return wildcardType;
0468: case Signature.CAPTURE_TYPE_SIGNATURE:
0469: return addImportFromSignature(typeSig.substring(1), ast,
0470: context);
0471: default:
0472: throw new IllegalArgumentException(
0473: "Unknown type signature kind: " + typeSig); //$NON-NLS-1$
0474: }
0475: }
0476:
0477: /**
0478: * Adds a new import to the rewriter's record and returns a type reference that can be used
0479: * in the code. The type binding can be an array binding, type variable or wildcard.
0480: * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
0481: * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
0482: * of wildcards are ignored.
0483: * <p>
0484: * 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.
0485: * </p>
0486: * <p>
0487: * The content of the compilation unit itself is actually not modified
0488: * in any way by this method; rather, the rewriter just records that a new import has been added.
0489: * </p>
0490: * @param binding the signature of the type to be added.
0491: * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
0492: * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
0493: */
0494: public String addImport(ITypeBinding binding) {
0495: return addImport(binding, this .defaultContext);
0496: }
0497:
0498: /**
0499: * Adds a new import to the rewriter's record and returns a type reference that can be used
0500: * in the code. The type binding can be an array binding, type variable or wildcard.
0501: * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
0502: * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
0503: * of wildcards are ignored.
0504: * <p>
0505: * 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.
0506: * </p>
0507: * <p>
0508: * The content of the compilation unit itself is actually not modified
0509: * in any way by this method; rather, the rewriter just records that a new import has been added.
0510: * </p>
0511: * @param binding the signature of the type to be added.
0512: * @param context an optional context that knows about types visible in the current scope or <code>null</code>
0513: * to use the default context only using the available imports.
0514: * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
0515: * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
0516: */
0517: public String addImport(ITypeBinding binding,
0518: ImportRewriteContext context) {
0519: if (binding.isPrimitive() || binding.isTypeVariable()
0520: || binding.isRecovered()) {
0521: return binding.getName();
0522: }
0523:
0524: ITypeBinding normalizedBinding = normalizeTypeBinding(binding);
0525: if (normalizedBinding == null) {
0526: return "invalid"; //$NON-NLS-1$
0527: }
0528: if (normalizedBinding.isWildcardType()) {
0529: StringBuffer res = new StringBuffer("?"); //$NON-NLS-1$
0530: ITypeBinding bound = normalizedBinding.getBound();
0531: if (bound != null && !bound.isWildcardType()
0532: && !bound.isCapture()) { // bug 95942
0533: if (normalizedBinding.isUpperbound()) {
0534: res.append(" extends "); //$NON-NLS-1$
0535: } else {
0536: res.append(" super "); //$NON-NLS-1$
0537: }
0538: res.append(addImport(bound, context));
0539: }
0540: return res.toString();
0541: }
0542:
0543: if (normalizedBinding.isArray()) {
0544: StringBuffer res = new StringBuffer(addImport(
0545: normalizedBinding.getElementType(), context));
0546: for (int i = normalizedBinding.getDimensions(); i > 0; i--) {
0547: res.append("[]"); //$NON-NLS-1$
0548: }
0549: return res.toString();
0550: }
0551:
0552: String qualifiedName = getRawQualifiedName(normalizedBinding);
0553: if (qualifiedName.length() > 0) {
0554: String str = internalAddImport(qualifiedName, context);
0555:
0556: ITypeBinding[] typeArguments = normalizedBinding
0557: .getTypeArguments();
0558: if (typeArguments.length > 0) {
0559: StringBuffer res = new StringBuffer(str);
0560: res.append('<');
0561: for (int i = 0; i < typeArguments.length; i++) {
0562: if (i > 0) {
0563: res.append(',');
0564: }
0565: ITypeBinding curr = typeArguments[i];
0566: if (containsNestedCapture(curr, false)) { // see bug 103044
0567: res.append('?');
0568: } else {
0569: res.append(addImport(curr, context));
0570: }
0571: }
0572: res.append('>');
0573: return res.toString();
0574: }
0575: return str;
0576: }
0577: return getRawName(normalizedBinding);
0578: }
0579:
0580: private boolean containsNestedCapture(ITypeBinding binding,
0581: boolean isNested) {
0582: if (binding == null || binding.isPrimitive()
0583: || binding.isTypeVariable()) {
0584: return false;
0585: }
0586: if (binding.isCapture()) {
0587: if (isNested) {
0588: return true;
0589: }
0590: return containsNestedCapture(binding.getWildcard(), true);
0591: }
0592: if (binding.isWildcardType()) {
0593: return containsNestedCapture(binding.getBound(), true);
0594: }
0595: if (binding.isArray()) {
0596: return containsNestedCapture(binding.getElementType(), true);
0597: }
0598: ITypeBinding[] typeArguments = binding.getTypeArguments();
0599: for (int i = 0; i < typeArguments.length; i++) {
0600: if (containsNestedCapture(typeArguments[i], true)) {
0601: return true;
0602: }
0603: }
0604: return false;
0605: }
0606:
0607: private boolean containsNestedCapture(String signature) {
0608: return signature.length() > 1
0609: && signature.indexOf(Signature.C_CAPTURE, 1) != -1;
0610: }
0611:
0612: private static ITypeBinding normalizeTypeBinding(
0613: ITypeBinding binding) {
0614: if (binding != null && !binding.isNullType()
0615: && !"void".equals(binding.getName())) { //$NON-NLS-1$
0616: if (binding.isAnonymous()) {
0617: ITypeBinding[] baseBindings = binding.getInterfaces();
0618: if (baseBindings.length > 0) {
0619: return baseBindings[0];
0620: }
0621: return binding.getSuperclass();
0622: }
0623: if (binding.isCapture()) {
0624: return binding.getWildcard();
0625: }
0626: return binding;
0627: }
0628: return null;
0629: }
0630:
0631: /**
0632: * Adds a new import to the rewriter's record and returns a {@link Type} that can be used
0633: * in the code. The type binding can be an array binding, type variable or wildcard.
0634: * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
0635: * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
0636: * of wildcards are ignored.
0637: * <p>
0638: * 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.
0639: * </p>
0640: * <p>
0641: * The content of the compilation unit itself is actually not modified
0642: * in any way by this method; rather, the rewriter just records that a new import has been added.
0643: * </p>
0644: * @param binding the signature of the type to be added.
0645: * @param ast the AST to create the returned type for.
0646: * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
0647: * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
0648: */
0649: public Type addImport(ITypeBinding binding, AST ast) {
0650: return addImport(binding, ast, this .defaultContext);
0651: }
0652:
0653: /**
0654: * Adds a new import to the rewriter's record and returns a {@link Type} that can be used
0655: * in the code. The type binding can be an array binding, type variable or wildcard.
0656: * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
0657: * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
0658: * of wildcards are ignored.
0659: * <p>
0660: * 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.
0661: * </p>
0662: * <p>
0663: * The content of the compilation unit itself is actually not modified
0664: * in any way by this method; rather, the rewriter just records that a new import has been added.
0665: * </p>
0666: * @param binding the signature of the type to be added.
0667: * @param ast the AST to create the returned type for.
0668: * @param context an optional context that knows about types visible in the current scope or <code>null</code>
0669: * to use the default context only using the available imports.
0670: * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
0671: * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
0672: */
0673: public Type addImport(ITypeBinding binding, AST ast,
0674: ImportRewriteContext context) {
0675: if (binding.isPrimitive()) {
0676: return ast.newPrimitiveType(PrimitiveType.toCode(binding
0677: .getName()));
0678: }
0679:
0680: ITypeBinding normalizedBinding = normalizeTypeBinding(binding);
0681: if (normalizedBinding == null) {
0682: return ast.newSimpleType(ast.newSimpleName("invalid")); //$NON-NLS-1$
0683: }
0684:
0685: if (normalizedBinding.isTypeVariable()) {
0686: // no import
0687: return ast.newSimpleType(ast.newSimpleName(binding
0688: .getName()));
0689: }
0690: if (normalizedBinding.isWildcardType()) {
0691: WildcardType wcType = ast.newWildcardType();
0692: ITypeBinding bound = normalizedBinding.getBound();
0693: if (bound != null && !bound.isWildcardType()
0694: && !bound.isCapture()) { // bug 96942
0695: Type boundType = addImport(bound, ast, context);
0696: wcType.setBound(boundType, normalizedBinding
0697: .isUpperbound());
0698: }
0699: return wcType;
0700: }
0701:
0702: if (normalizedBinding.isArray()) {
0703: Type elementType = addImport(normalizedBinding
0704: .getElementType(), ast, context);
0705: return ast.newArrayType(elementType, normalizedBinding
0706: .getDimensions());
0707: }
0708:
0709: String qualifiedName = getRawQualifiedName(normalizedBinding);
0710: if (qualifiedName.length() > 0) {
0711: String res = internalAddImport(qualifiedName, context);
0712:
0713: ITypeBinding[] typeArguments = normalizedBinding
0714: .getTypeArguments();
0715: if (typeArguments.length > 0) {
0716: Type erasureType = ast.newSimpleType(ast.newName(res));
0717: ParameterizedType paramType = ast
0718: .newParameterizedType(erasureType);
0719: List arguments = paramType.typeArguments();
0720: for (int i = 0; i < typeArguments.length; i++) {
0721: ITypeBinding curr = typeArguments[i];
0722: if (containsNestedCapture(curr, false)) { // see bug 103044
0723: arguments.add(ast.newWildcardType());
0724: } else {
0725: arguments.add(addImport(curr, ast, context));
0726: }
0727: }
0728: return paramType;
0729: }
0730: return ast.newSimpleType(ast.newName(res));
0731: }
0732: return ast.newSimpleType(ast
0733: .newName(getRawName(normalizedBinding)));
0734: }
0735:
0736: /**
0737: * Adds a new import to the rewriter's record and returns a type reference that can be used
0738: * in the code. The type binding can only be an array or non-generic type.
0739: * <p>
0740: * 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.
0741: * </p>
0742: * <p>
0743: * The content of the compilation unit itself is actually not modified
0744: * in any way by this method; rather, the rewriter just records that a new import has been added.
0745: * </p>
0746: * @param qualifiedTypeName the qualified type name of the type to be added
0747: * @param context an optional context that knows about types visible in the current scope or <code>null</code>
0748: * to use the default context only using the available imports.
0749: * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
0750: * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
0751: */
0752: public String addImport(String qualifiedTypeName,
0753: ImportRewriteContext context) {
0754: int angleBracketOffset = qualifiedTypeName.indexOf('<');
0755: if (angleBracketOffset != -1) {
0756: return internalAddImport(qualifiedTypeName.substring(0,
0757: angleBracketOffset), context)
0758: + qualifiedTypeName.substring(angleBracketOffset);
0759: }
0760: int bracketOffset = qualifiedTypeName.indexOf('[');
0761: if (bracketOffset != -1) {
0762: return internalAddImport(qualifiedTypeName.substring(0,
0763: bracketOffset), context)
0764: + qualifiedTypeName.substring(bracketOffset);
0765: }
0766: return internalAddImport(qualifiedTypeName, context);
0767: }
0768:
0769: /**
0770: * Adds a new import to the rewriter's record and returns a type reference that can be used
0771: * in the code. The type binding can only be an array or non-generic type.
0772: * <p>
0773: * 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.
0774: * </p>
0775: * <p>
0776: * The content of the compilation unit itself is actually not modified
0777: * in any way by this method; rather, the rewriter just records that a new import has been added.
0778: * </p>
0779: * @param qualifiedTypeName the qualified type name of the type to be added
0780: * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
0781: * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
0782: */
0783: public String addImport(String qualifiedTypeName) {
0784: return addImport(qualifiedTypeName, this .defaultContext);
0785: }
0786:
0787: /**
0788: * Adds a new static import to the rewriter's record and returns a reference that can be used in the code. The reference will
0789: * be fully qualified if an import conflict prevented the import or unqualified if the import succeeded or was already
0790: * existing.
0791: * <p>
0792: * 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.
0793: * </p>
0794: * <p>
0795: * The content of the compilation unit itself is actually not modified
0796: * in any way by this method; rather, the rewriter just records that a new import has been added.
0797: * </p>
0798: * @param binding The binding of the static field or method to be added.
0799: * @return returns either the simple member name if the import was successful or else the qualified name if
0800: * an import conflict prevented the import.
0801: * @throws IllegalArgumentException an {@link IllegalArgumentException} is thrown if the binding is not a static field
0802: * or method.
0803: */
0804: public String addStaticImport(IBinding binding) {
0805: return addStaticImport(binding, this .defaultContext);
0806: }
0807:
0808: /**
0809: * Adds a new static import to the rewriter's record and returns a reference that can be used in the code. The reference will
0810: * be fully qualified if an import conflict prevented the import or unqualified if the import succeeded or was already
0811: * existing.
0812: * <p>
0813: * 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.
0814: * </p>
0815: * <p>
0816: * The content of the compilation unit itself is actually not modified
0817: * in any way by this method; rather, the rewriter just records that a new import has been added.
0818: * </p>
0819: * @param binding The binding of the static field or method to be added.
0820: * @param context an optional context that knows about members visible in the current scope or <code>null</code>
0821: * to use the default context only using the available imports.
0822: * @return returns either the simple member name if the import was successful or else the qualified name if
0823: * an import conflict prevented the import.
0824: * @throws IllegalArgumentException an {@link IllegalArgumentException} is thrown if the binding is not a static field
0825: * or method.
0826: */
0827: public String addStaticImport(IBinding binding,
0828: ImportRewriteContext context) {
0829: if (Modifier.isStatic(binding.getModifiers())) {
0830: if (binding instanceof IVariableBinding) {
0831: IVariableBinding variableBinding = (IVariableBinding) binding;
0832: if (variableBinding.isField()) {
0833: ITypeBinding declaringType = variableBinding
0834: .getDeclaringClass();
0835: return addStaticImport(
0836: getRawQualifiedName(declaringType), binding
0837: .getName(), true, context);
0838: }
0839: } else if (binding instanceof IMethodBinding) {
0840: ITypeBinding declaringType = ((IMethodBinding) binding)
0841: .getDeclaringClass();
0842: return addStaticImport(
0843: getRawQualifiedName(declaringType), binding
0844: .getName(), false, context);
0845: }
0846: }
0847: throw new IllegalArgumentException(
0848: "Binding must be a static field or method."); //$NON-NLS-1$
0849: }
0850:
0851: /**
0852: * Adds a new static import to the rewriter's record and returns a reference that can be used in the code. The reference will
0853: * be fully qualified if an import conflict prevented the import or unqualified if the import succeeded or was already
0854: * existing.
0855: * <p>
0856: * 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.
0857: * </p>
0858: * <p>
0859: * The content of the compilation unit itself is actually not modified
0860: * in any way by this method; rather, the rewriter just records that a new import has been added.
0861: * </p>
0862: * @param declaringTypeName The qualified name of the static's member declaring type
0863: * @param simpleName the simple name of the member; either a field or a method name.
0864: * @param isField <code>true</code> specifies that the member is a field, <code>false</code> if it is a
0865: * method.
0866: * @return returns either the simple member name if the import was successful or else the qualified name if
0867: * an import conflict prevented the import.
0868: */
0869: public String addStaticImport(String declaringTypeName,
0870: String simpleName, boolean isField) {
0871: return addStaticImport(declaringTypeName, simpleName, isField,
0872: this .defaultContext);
0873: }
0874:
0875: /**
0876: * Adds a new static import to the rewriter's record and returns a reference that can be used in the code. The reference will
0877: * be fully qualified if an import conflict prevented the import or unqualified if the import succeeded or was already
0878: * existing.
0879: * <p>
0880: * 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.
0881: * </p>
0882: * <p>
0883: * The content of the compilation unit itself is actually not modified
0884: * in any way by this method; rather, the rewriter just records that a new import has been added.
0885: * </p>
0886: * @param declaringTypeName The qualified name of the static's member declaring type
0887: * @param simpleName the simple name of the member; either a field or a method name.
0888: * @param isField <code>true</code> specifies that the member is a field, <code>false</code> if it is a
0889: * method.
0890: * @param context an optional context that knows about members visible in the current scope or <code>null</code>
0891: * to use the default context only using the available imports.
0892: * @return returns either the simple member name if the import was successful or else the qualified name if
0893: * an import conflict prevented the import.
0894: */
0895: public String addStaticImport(String declaringTypeName,
0896: String simpleName, boolean isField,
0897: ImportRewriteContext context) {
0898: if (declaringTypeName.indexOf('.') == -1) {
0899: return declaringTypeName + '.' + simpleName;
0900: }
0901: if (context == null) {
0902: context = this .defaultContext;
0903: }
0904: int kind = isField ? ImportRewriteContext.KIND_STATIC_FIELD
0905: : ImportRewriteContext.KIND_STATIC_METHOD;
0906: int res = context.findInContext(declaringTypeName, simpleName,
0907: kind);
0908: if (res == ImportRewriteContext.RES_NAME_CONFLICT) {
0909: return declaringTypeName + '.' + simpleName;
0910: }
0911: if (res == ImportRewriteContext.RES_NAME_UNKNOWN) {
0912: addEntry(STATIC_PREFIX + declaringTypeName + '.'
0913: + simpleName);
0914: }
0915: return simpleName;
0916: }
0917:
0918: private String internalAddImport(String fullTypeName,
0919: ImportRewriteContext context) {
0920: int idx = fullTypeName.lastIndexOf('.');
0921: String typeContainerName, typeName;
0922: if (idx != -1) {
0923: typeContainerName = fullTypeName.substring(0, idx);
0924: typeName = fullTypeName.substring(idx + 1);
0925: } else {
0926: typeContainerName = ""; //$NON-NLS-1$
0927: typeName = fullTypeName;
0928: }
0929:
0930: if (typeContainerName.length() == 0
0931: && PrimitiveType.toCode(typeName) != null) {
0932: return fullTypeName;
0933: }
0934:
0935: if (context == null)
0936: context = this .defaultContext;
0937:
0938: int res = context.findInContext(typeContainerName, typeName,
0939: ImportRewriteContext.KIND_TYPE);
0940: if (res == ImportRewriteContext.RES_NAME_CONFLICT) {
0941: return fullTypeName;
0942: }
0943: if (res == ImportRewriteContext.RES_NAME_UNKNOWN) {
0944: addEntry(NORMAL_PREFIX + fullTypeName);
0945: }
0946: return typeName;
0947: }
0948:
0949: private void addEntry(String entry) {
0950: this .existingImports.add(entry);
0951:
0952: if (this .removedImports != null) {
0953: if (this .removedImports.remove(entry)) {
0954: return;
0955: }
0956: }
0957:
0958: if (this .addedImports == null) {
0959: this .addedImports = new ArrayList();
0960: }
0961: this .addedImports.add(entry);
0962: }
0963:
0964: private boolean removeEntry(String entry) {
0965: if (this .existingImports.remove(entry)) {
0966: if (this .addedImports != null) {
0967: if (this .addedImports.remove(entry)) {
0968: return true;
0969: }
0970: }
0971: if (this .removedImports == null) {
0972: this .removedImports = new ArrayList();
0973: }
0974: this .removedImports.add(entry);
0975: return true;
0976: }
0977: return false;
0978: }
0979:
0980: /**
0981: * Records to remove a import. No remove is recorded if no such import exists or if such an import is recorded
0982: * to be added. In that case the record of the addition is discarded.
0983: * <p>
0984: * The content of the compilation unit itself is actually not modified
0985: * in any way by this method; rather, the rewriter just records that an import has been removed.
0986: * </p>
0987: * @param qualifiedName The import name to remove.
0988: * @return <code>true</code> is returned of an import of the given name could be found.
0989: */
0990: public boolean removeImport(String qualifiedName) {
0991: return removeEntry(NORMAL_PREFIX + qualifiedName);
0992: }
0993:
0994: /**
0995: * Records to remove a static import. No remove is recorded if no such import exists or if such an import is recorded
0996: * to be added. In that case the record of the addition is discarded.
0997: * <p>
0998: * The content of the compilation unit itself is actually not modified
0999: * in any way by this method; rather, the rewriter just records that a new import has been removed.
1000: * </p>
1001: * @param qualifiedName The import name to remove.
1002: * @return <code>true</code> is returned of an import of the given name could be found.
1003: */
1004: public boolean removeStaticImport(String qualifiedName) {
1005: return removeEntry(STATIC_PREFIX + qualifiedName);
1006: }
1007:
1008: private static String getRawName(ITypeBinding normalizedBinding) {
1009: return normalizedBinding.getTypeDeclaration().getName();
1010: }
1011:
1012: private static String getRawQualifiedName(
1013: ITypeBinding normalizedBinding) {
1014: return normalizedBinding.getTypeDeclaration()
1015: .getQualifiedName();
1016: }
1017:
1018: /**
1019: * Converts all modifications recorded by this rewriter into an object representing the corresponding text
1020: * edits to the source code of the rewrite's compilation unit. The compilation unit itself is not modified.
1021: * <p>
1022: * Calling this methods does not discard the modifications on record. Subsequence modifications are added
1023: * to the ones already on record. If this method is called again later, the resulting text edit object will accurately
1024: * reflect the net cumulative affect of all those changes.
1025: * </p>
1026: * @param monitor the progress monitor or <code>null</code>
1027: * @return text edit object describing the changes to the document corresponding to the changes
1028: * recorded by this rewriter
1029: * @throws CoreException the exception is thrown if the rewrite fails.
1030: */
1031: public final TextEdit rewriteImports(IProgressMonitor monitor)
1032: throws CoreException {
1033: if (monitor == null) {
1034: monitor = new NullProgressMonitor();
1035: }
1036:
1037: try {
1038: monitor
1039: .beginTask(
1040: Messages
1041: .bind(Messages.importRewrite_processDescription),
1042: 2);
1043: if (!hasRecordedChanges()) {
1044: this .createdImports = CharOperation.NO_STRINGS;
1045: this .createdStaticImports = CharOperation.NO_STRINGS;
1046: return new MultiTextEdit();
1047: }
1048:
1049: CompilationUnit usedAstRoot = this .astRoot;
1050: if (usedAstRoot == null) {
1051: ASTParser parser = ASTParser.newParser(AST.JLS3);
1052: parser.setSource(this .compilationUnit);
1053: parser.setFocalPosition(0); // reduced AST
1054: parser.setResolveBindings(false);
1055: usedAstRoot = (CompilationUnit) parser
1056: .createAST(new SubProgressMonitor(monitor, 1));
1057: }
1058:
1059: ImportRewriteAnalyzer computer = new ImportRewriteAnalyzer(
1060: this .compilationUnit, usedAstRoot,
1061: this .importOrder, this .importOnDemandThreshold,
1062: this .staticImportOnDemandThreshold,
1063: this .restoreExistingImports);
1064: computer
1065: .setFilterImplicitImports(this .filterImplicitImports);
1066:
1067: if (this .addedImports != null) {
1068: for (int i = 0; i < this .addedImports.size(); i++) {
1069: String curr = (String) this .addedImports.get(i);
1070: computer.addImport(curr.substring(1),
1071: STATIC_PREFIX == curr.charAt(0));
1072: }
1073: }
1074:
1075: if (this .removedImports != null) {
1076: for (int i = 0; i < this .removedImports.size(); i++) {
1077: String curr = (String) this .removedImports.get(i);
1078: computer.removeImport(curr.substring(1),
1079: STATIC_PREFIX == curr.charAt(0));
1080: }
1081: }
1082:
1083: TextEdit result = computer
1084: .getResultingEdits(new SubProgressMonitor(monitor,
1085: 1));
1086: this .createdImports = computer.getCreatedImports();
1087: this .createdStaticImports = computer
1088: .getCreatedStaticImports();
1089: return result;
1090: } finally {
1091: monitor.done();
1092: }
1093: }
1094:
1095: /**
1096: * Returns all new non-static imports created by the last invocation of {@link #rewriteImports(IProgressMonitor)}
1097: * or <code>null</code> if these methods have not been called yet.
1098: * <p>
1099: * Note that this list doesn't need to be the same as the added imports (see {@link #getAddedImports()}) as
1100: * implicit imports are not created and some imports are represented by on-demand imports instead.
1101: * </p>
1102: * @return the created imports
1103: */
1104: public String[] getCreatedImports() {
1105: return this .createdImports;
1106: }
1107:
1108: /**
1109: * Returns all new static imports created by the last invocation of {@link #rewriteImports(IProgressMonitor)}
1110: * or <code>null</code> if these methods have not been called yet.
1111: * <p>
1112: * Note that this list doesn't need to be the same as the added static imports ({@link #getAddedStaticImports()}) as
1113: * implicit imports are not created and some imports are represented by on-demand imports instead.
1114: * </p
1115: * @return the created imports
1116: */
1117: public String[] getCreatedStaticImports() {
1118: return this .createdStaticImports;
1119: }
1120:
1121: /**
1122: * Returns all non-static imports that are recorded to be added.
1123: *
1124: * @return the imports recorded to be added.
1125: */
1126: public String[] getAddedImports() {
1127: return filterFromList(this .addedImports, NORMAL_PREFIX);
1128: }
1129:
1130: /**
1131: * Returns all static imports that are recorded to be added.
1132: *
1133: * @return the static imports recorded to be added.
1134: */
1135: public String[] getAddedStaticImports() {
1136: return filterFromList(this .addedImports, STATIC_PREFIX);
1137: }
1138:
1139: /**
1140: * Returns all non-static imports that are recorded to be removed.
1141: *
1142: * @return the imports recorded to be removed.
1143: */
1144: public String[] getRemovedImports() {
1145: return filterFromList(this .removedImports, NORMAL_PREFIX);
1146: }
1147:
1148: /**
1149: * Returns all static imports that are recorded to be removed.
1150: *
1151: * @return the static imports recorded to be removed.
1152: */
1153: public String[] getRemovedStaticImports() {
1154: return filterFromList(this .removedImports, STATIC_PREFIX);
1155: }
1156:
1157: /**
1158: * Returns <code>true</code> if imports have been recorded to be added or removed.
1159: * @return boolean returns if any changes to imports have been recorded.
1160: */
1161: public boolean hasRecordedChanges() {
1162: return !this .restoreExistingImports
1163: || (this .addedImports != null && !this .addedImports
1164: .isEmpty())
1165: || (this .removedImports != null && !this .removedImports
1166: .isEmpty());
1167: }
1168:
1169: private static String[] filterFromList(List imports, char prefix) {
1170: if (imports == null) {
1171: return CharOperation.NO_STRINGS;
1172: }
1173: ArrayList res = new ArrayList();
1174: for (int i = 0; i < imports.size(); i++) {
1175: String curr = (String) imports.get(i);
1176: if (prefix == curr.charAt(0)) {
1177: res.add(curr.substring(1));
1178: }
1179: }
1180: return (String[]) res.toArray(new String[res.size()]);
1181: }
1182:
1183: }
|