KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > core > dom > rewrite > ImportRewrite


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

11
12 package org.eclipse.jdt.core.dom.rewrite;
13
14 import java.util.ArrayList JavaDoc;
15 import java.util.List JavaDoc;
16
17 import org.eclipse.core.runtime.CoreException;
18 import org.eclipse.core.runtime.IProgressMonitor;
19 import org.eclipse.core.runtime.NullProgressMonitor;
20 import org.eclipse.core.runtime.SubProgressMonitor;
21 import org.eclipse.jdt.core.Flags;
22 import org.eclipse.jdt.core.ICompilationUnit;
23 import org.eclipse.jdt.core.IImportDeclaration;
24 import org.eclipse.jdt.core.ITypeRoot;
25 import org.eclipse.jdt.core.JavaModelException;
26 import org.eclipse.jdt.core.Signature;
27 import org.eclipse.jdt.core.compiler.CharOperation;
28 import org.eclipse.jdt.core.dom.*;
29 import org.eclipse.jdt.internal.core.dom.rewrite.ImportRewriteAnalyzer;
30 import org.eclipse.jdt.internal.core.util.Messages;
31 import org.eclipse.text.edits.MultiTextEdit;
32 import org.eclipse.text.edits.TextEdit;
33
34
35 /**
36  * The {@link ImportRewrite} helps updating imports following a import order and on-demand imports threshold as configured by a project.
37  * <p>
38  * 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
39  * {@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.
40  * 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.
41  * </p>
42  * <p>
43  * On {@link #rewriteImports(IProgressMonitor)} the rewrite translates these descriptions into
44  * text edits that can then be applied to the original source. The rewrite infrastructure tries to generate minimal text changes and only
45  * 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}
46  * as long as no import statements are modified by the AST rewrite.
47  * </p>
48  * <p>The options controlling the import order and on-demand thresholds are:
49  * <ul><li>{@link #setImportOrder(String[])} specifies the import groups and their preferred order</li>
50  * <li>{@link #setOnDemandImportThreshold(int)} specifies the number of imports in a group needed for a on-demand import statement (star import)</li>
51  * <li>{@link #setStaticOnDemandImportThreshold(int)} specifies the number of static imports in a group needed for a on-demand import statement (star import)</li>
52  *</ul>
53  * This class is not intended to be subclassed.
54  * </p>
55  * @since 3.2
56  */

57 public final class ImportRewrite {
58     
59     /**
60      * A {@link ImportRewrite.ImportRewriteContext} can optionally be used in e.g. {@link ImportRewrite#addImport(String, ImportRewrite.ImportRewriteContext)} to
61      * give more information about the types visible in the scope. These types can be for example inherited inner types where it is
62      * unnecessary to add import statements for.
63      *
64      * </p>
65      * <p>
66      * This class can be implemented by clients.
67      * </p>
68      */

69     public static abstract class ImportRewriteContext {
70         
71         /**
72          * Result constant signaling that the given element is know in the context.
73          */

74         public final static int RES_NAME_FOUND= 1;
75         
76         /**
77          * Result constant signaling that the given element is not know in the context.
78          */

79         public final static int RES_NAME_UNKNOWN= 2;
80         
81         /**
82          * Result constant signaling that the given element is conflicting with an other element in the context.
83          */

84         public final static int RES_NAME_CONFLICT= 3;
85         
86         /**
87          * Kind constant specifying that the element is a type import.
88          */

89         public final static int KIND_TYPE= 1;
90         
91         /**
92          * Kind constant specifying that the element is a static field import.
93          */

94         public final static int KIND_STATIC_FIELD= 2;
95         
96         /**
97          * Kind constant specifying that the element is a static method import.
98          */

99         public final static int KIND_STATIC_METHOD= 3;
100         
101         /**
102          * Searches for the given element in the context and reports if the element is known ({@link #RES_NAME_FOUND}),
103          * unknown ({@link #RES_NAME_UNKNOWN}) or if its name conflicts ({@link #RES_NAME_CONFLICT}) with an other element.
104          * @param qualifier The qualifier of the element, can be package or the qualified name of a type
105          * @param name The simple name of the element; either a type, method or field name or * for on-demand imports.
106          * @param kind The kind of the element. Can be either {@link #KIND_TYPE}, {@link #KIND_STATIC_FIELD} or
107          * {@link #KIND_STATIC_METHOD}. Implementors should be prepared for new, currently unspecified kinds and return
108          * {@link #RES_NAME_UNKNOWN} by default.
109          * @return Returns the result of the lookup. Can be either {@link #RES_NAME_FOUND}, {@link #RES_NAME_UNKNOWN} or
110          * {@link #RES_NAME_CONFLICT}.
111          */

112         public abstract int findInContext(String JavaDoc qualifier, String JavaDoc name, int kind);
113     }
114     
115     private static final char STATIC_PREFIX= 's';
116     private static final char NORMAL_PREFIX= 'n';
117     
118     private final ImportRewriteContext defaultContext;
119
120     private final ICompilationUnit compilationUnit;
121     private final CompilationUnit astRoot;
122     
123     private final boolean restoreExistingImports;
124     private final List JavaDoc existingImports;
125     
126     private String JavaDoc[] importOrder;
127     private int importOnDemandThreshold;
128     private int staticImportOnDemandThreshold;
129     
130     private List JavaDoc addedImports;
131     private List JavaDoc removedImports;
132
133     private String JavaDoc[] createdImports;
134     private String JavaDoc[] createdStaticImports;
135     
136     private boolean filterImplicitImports;
137     
138     /**
139      * Creates a {@link ImportRewrite} from a {@link ICompilationUnit}. If <code>restoreExistingImports</code>
140      * is <code>true</code>, all existing imports are kept, and new imports will be inserted at best matching locations. If
141      * <code>restoreExistingImports</code> is <code>false</code>, the existing imports will be removed and only the
142      * newly added imports will be created.
143      * <p>
144      * Note that {@link #create(ICompilationUnit, boolean)} is more efficient than this method if an AST for
145      * the compilation unit is already available.
146      * </p>
147      * @param cu the compilation unit to create the imports for
148      * @param restoreExistingImports specifies if the existing imports should be kept or removed.
149      * @return the created import rewriter.
150      * @throws JavaModelException thrown when the compilation unit could not be accessed.
151      */

152     public static ImportRewrite create(ICompilationUnit cu, boolean restoreExistingImports) throws JavaModelException {
153         if (cu == null) {
154             throw new IllegalArgumentException JavaDoc("Compilation unit must not be null"); //$NON-NLS-1$
155
}
156         List JavaDoc existingImport= null;
157         if (restoreExistingImports) {
158             existingImport= new ArrayList JavaDoc();
159             IImportDeclaration[] imports= cu.getImports();
160             for (int i= 0; i < imports.length; i++) {
161                 IImportDeclaration curr= imports[i];
162                 char prefix= Flags.isStatic(curr.getFlags()) ? STATIC_PREFIX : NORMAL_PREFIX;
163                 existingImport.add(prefix + curr.getElementName());
164             }
165         }
166         return new ImportRewrite(cu, null, existingImport);
167     }
168     
169     /**
170      * Creates a {@link ImportRewrite} from a an AST ({@link CompilationUnit}). The AST has to be created from a
171      * {@link ICompilationUnit}, that means {@link ASTParser#setSource(ICompilationUnit)} has been used when creating the
172      * AST. If <code>restoreExistingImports</code> is <code>true</code>, all existing imports are kept, and new imports
173      * will be inserted at best matching locations. If <code>restoreExistingImports</code> is <code>false</code>, the
174      * existing imports will be removed and only the newly added imports will be created.
175      * <p>
176      * Note that this method is more efficient than using {@link #create(ICompilationUnit, boolean)} if an AST is already available.
177      * </p>
178      * @param astRoot the AST root node to create the imports for
179      * @param restoreExistingImports specifies if the existing imports should be kept or removed.
180      * @return the created import rewriter.
181      * @throws IllegalArgumentException thrown when the passed AST is null or was not created from a compilation unit.
182      */

183     public static ImportRewrite create(CompilationUnit astRoot, boolean restoreExistingImports) {
184         if (astRoot == null) {
185             throw new IllegalArgumentException JavaDoc("AST must not be null"); //$NON-NLS-1$
186
}
187         ITypeRoot typeRoot = astRoot.getTypeRoot();
188         if (!(typeRoot instanceof ICompilationUnit)) {
189             throw new IllegalArgumentException JavaDoc("AST must have been constructed from a Java element"); //$NON-NLS-1$
190
}
191         List JavaDoc existingImport= null;
192         if (restoreExistingImports) {
193             existingImport= new ArrayList JavaDoc();
194             List JavaDoc imports= astRoot.imports();
195             for (int i= 0; i < imports.size(); i++) {
196                 ImportDeclaration curr= (ImportDeclaration) imports.get(i);
197                 StringBuffer JavaDoc buf= new StringBuffer JavaDoc();
198                 buf.append(curr.isStatic() ? STATIC_PREFIX : NORMAL_PREFIX).append(curr.getName().getFullyQualifiedName());
199                 if (curr.isOnDemand()) {
200                     if (buf.length() > 1)
201                         buf.append('.');
202                     buf.append('*');
203                 }
204                 existingImport.add(buf.toString());
205             }
206         }
207         return new ImportRewrite((ICompilationUnit) typeRoot, astRoot, existingImport);
208     }
209         
210     private ImportRewrite(ICompilationUnit cu, CompilationUnit astRoot, List JavaDoc existingImports) {
211         this.compilationUnit= cu;
212         this.astRoot= astRoot; // might be null
213
if (existingImports != null) {
214             this.existingImports= existingImports;
215             this.restoreExistingImports= !existingImports.isEmpty();
216         } else {
217             this.existingImports= new ArrayList JavaDoc();
218             this.restoreExistingImports= false;
219         }
220         this.filterImplicitImports= true;
221
222         this.defaultContext= new ImportRewriteContext() {
223             public int findInContext(String JavaDoc qualifier, String JavaDoc name, int kind) {
224                 return findInImports(qualifier, name, kind);
225             }
226         };
227         this.addedImports= null; // Initialized on use
228
this.removedImports= null; // Initialized on use
229
this.createdImports= null;
230         this.createdStaticImports= null;
231         
232         this.importOrder= CharOperation.NO_STRINGS;
233         this.importOnDemandThreshold= 99;
234         this.staticImportOnDemandThreshold= 99;
235     }
236     
237     
238      /**
239      * Defines the import groups and order to be used by the {@link ImportRewrite}.
240      * Imports are added to the group matching their qualified name most. The empty group name groups all imports not matching
241      * any other group. Static imports are managed in separate groups. Static import group names are prefixed with a '#' character.
242      * @param order A list of strings defining the import groups. A group name must be a valid package name or empty. If can be
243      * prefixed by the '#' character for static import groups
244      */

245     public void setImportOrder(String JavaDoc[] order) {
246         if (order == null)
247             throw new IllegalArgumentException JavaDoc("Order must not be null"); //$NON-NLS-1$
248
this.importOrder= order;
249     }
250     
251      /**
252      * Sets the on-demand import threshold for normal (non-static) imports.
253      * This threshold defines the number of imports that need to be in a group to use
254      * a on-demand (star) import declaration instead.
255      *
256      * @param threshold a positive number defining the on-demand import threshold
257      * for normal (non-static) imports.
258      * @throws IllegalArgumentException a {@link IllegalArgumentException} is thrown
259      * if the number is not positive.
260      */

261     public void setOnDemandImportThreshold(int threshold) {
262         if (threshold <= 0)
263             throw new IllegalArgumentException JavaDoc("Threshold must be positive."); //$NON-NLS-1$
264
this.importOnDemandThreshold= threshold;
265     }
266     
267      /**
268      * Sets the on-demand import threshold for static imports.
269      * This threshold defines the number of imports that need to be in a group to use
270      * a on-demand (star) import declaration instead.
271      *
272      * @param threshold a positive number defining the on-demand import threshold
273      * for normal (non-static) imports.
274      * @throws IllegalArgumentException a {@link IllegalArgumentException} is thrown
275      * if the number is not positive.
276      */

277     public void setStaticOnDemandImportThreshold(int threshold) {
278         if (threshold <= 0)
279             throw new IllegalArgumentException JavaDoc("Threshold must be positive."); //$NON-NLS-1$
280
this.staticImportOnDemandThreshold= threshold;
281     }
282     
283     /**
284      * The compilation unit for which this import rewrite was created for.
285      * @return the compilation unit for which this import rewrite was created for.
286      */

287     public ICompilationUnit getCompilationUnit() {
288         return this.compilationUnit;
289     }
290
291     /**
292      * Returns the default rewrite context that only knows about the imported types. Clients
293      * can write their own context and use the default context for the default behavior.
294      * @return the default import rewrite context.
295      */

296     public ImportRewriteContext getDefaultImportRewriteContext() {
297         return this.defaultContext;
298     }
299     
300     /**
301      * Specifies that implicit imports (types in default package, package <code>java.lang</code> or
302      * in the same package as the rewrite compilation unit should not be created except if necessary
303      * to resolve an on-demand import conflict. The filter is enabled by default.
304      * @param filterImplicitImports if set, implicit imports will be filtered.
305      */

306     public void setFilterImplicitImports(boolean filterImplicitImports) {
307         this.filterImplicitImports= filterImplicitImports;
308     }
309     
310     private static int compareImport(char prefix, String JavaDoc qualifier, String JavaDoc name, String JavaDoc curr) {
311         if (curr.charAt(0) != prefix || !curr.endsWith(name)) {
312             return ImportRewriteContext.RES_NAME_UNKNOWN;
313         }
314         
315         curr= curr.substring(1); // remove the prefix
316

317         if (curr.length() == name.length()) {
318             if (qualifier.length() == 0) {
319                 return ImportRewriteContext.RES_NAME_FOUND;
320             }
321             return ImportRewriteContext.RES_NAME_CONFLICT;
322         }
323         // at this place: curr.length > name.length
324

325         int dotPos= curr.length() - name.length() - 1;
326         if (curr.charAt(dotPos) != '.') {
327             return ImportRewriteContext.RES_NAME_UNKNOWN;
328         }
329         if (qualifier.length() != dotPos || !curr.startsWith(qualifier)) {
330             return ImportRewriteContext.RES_NAME_CONFLICT;
331         }
332         return ImportRewriteContext.RES_NAME_FOUND;
333     }
334     
335     /**
336      * Not API, package visibility as accessed from an anonymous type
337      */

338     /* package */ final int findInImports(String JavaDoc qualifier, String JavaDoc name, int kind) {
339         boolean allowAmbiguity= (kind == ImportRewriteContext.KIND_STATIC_METHOD) || (name.length() == 1 && name.charAt(0) == '*');
340         List JavaDoc imports= this.existingImports;
341         char prefix= (kind == ImportRewriteContext.KIND_TYPE) ? NORMAL_PREFIX : STATIC_PREFIX;
342         
343         for (int i= imports.size() - 1; i >= 0 ; i--) {
344             String JavaDoc curr= (String JavaDoc) imports.get(i);
345             int res= compareImport(prefix, qualifier, name, curr);
346             if (res != ImportRewriteContext.RES_NAME_UNKNOWN) {
347                 if (!allowAmbiguity || res == ImportRewriteContext.RES_NAME_FOUND) {
348                     return res;
349                 }
350             }
351         }
352         return ImportRewriteContext.RES_NAME_UNKNOWN;
353     }
354
355     /**
356      * Adds a new import to the rewriter's record and returns a {@link Type} node that can be used
357      * in the code as a reference to the type. The type binding can be an array binding, type variable or wildcard.
358      * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
359      * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
360      * of wildcards are ignored.
361      * <p>
362      * 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.
363      * </p>
364      * <p>
365      * The content of the compilation unit itself is actually not modified
366      * in any way by this method; rather, the rewriter just records that a new import has been added.
367      * </p>
368      * @param typeSig the signature of the type to be added.
369      * @param ast the AST to create the returned type for.
370      * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
371      * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
372      */

373     public Type addImportFromSignature(String JavaDoc typeSig, AST ast) {
374         return addImportFromSignature(typeSig, ast, this.defaultContext);
375     }
376     
377     /**
378      * Adds a new import to the rewriter's record and returns a {@link Type} node that can be used
379      * in the code as a reference to the type. The type binding can be an array binding, type variable or wildcard.
380      * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
381      * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
382      * of wildcards are ignored.
383      * <p>
384      * 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.
385      * </p>
386      * <p>
387      * The content of the compilation unit itself is actually not modified
388      * in any way by this method; rather, the rewriter just records that a new import has been added.
389      * </p>
390      * @param typeSig the signature of the type to be added.
391      * @param ast the AST to create the returned type for.
392      * @param context an optional context that knows about types visible in the current scope or <code>null</code>
393      * to use the default context only using the available imports.
394      * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
395      * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
396      */

397     public Type addImportFromSignature(String JavaDoc typeSig, AST ast, ImportRewriteContext context) {
398         if (typeSig == null || typeSig.length() == 0) {
399             throw new IllegalArgumentException JavaDoc("Invalid type signature: empty or null"); //$NON-NLS-1$
400
}
401         int sigKind= Signature.getTypeSignatureKind(typeSig);
402         switch (sigKind) {
403             case Signature.BASE_TYPE_SIGNATURE:
404                 return ast.newPrimitiveType(PrimitiveType.toCode(Signature.toString(typeSig)));
405             case Signature.ARRAY_TYPE_SIGNATURE:
406                 Type elementType= addImportFromSignature(Signature.getElementType(typeSig), ast, context);
407                 return ast.newArrayType(elementType, Signature.getArrayCount(typeSig));
408             case Signature.CLASS_TYPE_SIGNATURE:
409                 String JavaDoc erasureSig= Signature.getTypeErasure(typeSig);
410
411                 String JavaDoc erasureName= Signature.toString(erasureSig);
412                 if (erasureSig.charAt(0) == Signature.C_RESOLVED) {
413                     erasureName= internalAddImport(erasureName, context);
414                 }
415                 Type baseType= ast.newSimpleType(ast.newName(erasureName));
416                 String JavaDoc[] typeArguments= Signature.getTypeArguments(typeSig);
417                 if (typeArguments.length > 0) {
418                     ParameterizedType type= ast.newParameterizedType(baseType);
419                     List JavaDoc argNodes= type.typeArguments();
420                     for (int i= 0; i < typeArguments.length; i++) {
421                         String JavaDoc curr= typeArguments[i];
422                         if (containsNestedCapture(curr)) { // see bug 103044
423
argNodes.add(ast.newWildcardType());
424                         } else {
425                             argNodes.add(addImportFromSignature(curr, ast, context));
426                         }
427                     }
428                     return type;
429                 }
430                 return baseType;
431             case Signature.TYPE_VARIABLE_SIGNATURE:
432                 return ast.newSimpleType(ast.newSimpleName(Signature.toString(typeSig)));
433             case Signature.WILDCARD_TYPE_SIGNATURE:
434                 WildcardType wildcardType= ast.newWildcardType();
435                 char ch= typeSig.charAt(0);
436                 if (ch != Signature.C_STAR) {
437                     Type bound= addImportFromSignature(typeSig.substring(1), ast, context);
438                     wildcardType.setBound(bound, ch == Signature.C_EXTENDS);
439                 }
440                 return wildcardType;
441             case Signature.CAPTURE_TYPE_SIGNATURE:
442                 return addImportFromSignature(typeSig.substring(1), ast, context);
443             default:
444                 throw new IllegalArgumentException JavaDoc("Unknown type signature kind: " + typeSig); //$NON-NLS-1$
445
}
446     }
447     
448
449
450     /**
451      * Adds a new import to the rewriter's record and returns a type reference that can be used
452      * in the code. The type binding can be an array binding, type variable or wildcard.
453      * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
454      * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
455      * of wildcards are ignored.
456      * <p>
457      * 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.
458      * </p>
459      * <p>
460      * The content of the compilation unit itself is actually not modified
461      * in any way by this method; rather, the rewriter just records that a new import has been added.
462      * </p>
463      * @param binding the signature of the type to be added.
464      * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
465      * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
466      */

467     public String JavaDoc addImport(ITypeBinding binding) {
468         return addImport(binding, this.defaultContext);
469     }
470         
471     /**
472      * Adds a new import to the rewriter's record and returns a type reference that can be used
473      * in the code. The type binding can be an array binding, type variable or wildcard.
474      * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
475      * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
476      * of wildcards are ignored.
477      * <p>
478      * 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.
479      * </p>
480      * <p>
481      * The content of the compilation unit itself is actually not modified
482      * in any way by this method; rather, the rewriter just records that a new import has been added.
483      * </p>
484      * @param binding the signature of the type to be added.
485      * @param context an optional context that knows about types visible in the current scope or <code>null</code>
486      * to use the default context only using the available imports.
487      * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
488      * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
489      */

490     public String JavaDoc addImport(ITypeBinding binding, ImportRewriteContext context) {
491         if (binding.isPrimitive() || binding.isTypeVariable() || binding.isRecovered()) {
492             return binding.getName();
493         }
494         
495         ITypeBinding normalizedBinding= normalizeTypeBinding(binding);
496         if (normalizedBinding == null) {
497             return "invalid"; //$NON-NLS-1$
498
}
499         if (normalizedBinding.isWildcardType()) {
500             StringBuffer JavaDoc res= new StringBuffer JavaDoc("?"); //$NON-NLS-1$
501
ITypeBinding bound= normalizedBinding.getBound();
502             if (bound != null && !bound.isWildcardType() && !bound.isCapture()) { // bug 95942
503
if (normalizedBinding.isUpperbound()) {
504                     res.append(" extends "); //$NON-NLS-1$
505
} else {
506                     res.append(" super "); //$NON-NLS-1$
507
}
508                 res.append(addImport(bound, context));
509             }
510             return res.toString();
511         }
512         
513         if (normalizedBinding.isArray()) {
514             StringBuffer JavaDoc res= new StringBuffer JavaDoc(addImport(normalizedBinding.getElementType(), context));
515             for (int i= normalizedBinding.getDimensions(); i > 0; i--) {
516                 res.append("[]"); //$NON-NLS-1$
517
}
518             return res.toString();
519         }
520     
521         String JavaDoc qualifiedName= getRawQualifiedName(normalizedBinding);
522         if (qualifiedName.length() > 0) {
523             String JavaDoc str= internalAddImport(qualifiedName, context);
524             
525             ITypeBinding[] typeArguments= normalizedBinding.getTypeArguments();
526             if (typeArguments.length > 0) {
527                 StringBuffer JavaDoc res= new StringBuffer JavaDoc(str);
528                 res.append('<');
529                 for (int i= 0; i < typeArguments.length; i++) {
530                     if (i > 0) {
531                         res.append(',');
532                     }
533                     ITypeBinding curr= typeArguments[i];
534                     if (containsNestedCapture(curr, false)) { // see bug 103044
535
res.append('?');
536                     } else {
537                         res.append(addImport(curr, context));
538                     }
539                 }
540                 res.append('>');
541                 return res.toString();
542             }
543             return str;
544         }
545         return getRawName(normalizedBinding);
546     }
547     
548     private boolean containsNestedCapture(ITypeBinding binding, boolean isNested) {
549         if (binding == null || binding.isPrimitive() || binding.isTypeVariable()) {
550             return false;
551         }
552         if (binding.isCapture()) {
553             if (isNested) {
554                 return true;
555             }
556             return containsNestedCapture(binding.getWildcard(), true);
557         }
558         if (binding.isWildcardType()) {
559             return containsNestedCapture(binding.getBound(), true);
560         }
561         if (binding.isArray()) {
562             return containsNestedCapture(binding.getElementType(), true);
563         }
564         ITypeBinding[] typeArguments= binding.getTypeArguments();
565         for (int i= 0; i < typeArguments.length; i++) {
566             if (containsNestedCapture(typeArguments[i], true)) {
567                 return true;
568             }
569         }
570         return false;
571     }
572     
573     private boolean containsNestedCapture(String JavaDoc signature) {
574         return signature.length() > 1 && signature.indexOf(Signature.C_CAPTURE, 1) != -1;
575     }
576
577     private static ITypeBinding normalizeTypeBinding(ITypeBinding binding) {
578         if (binding != null && !binding.isNullType() && !"void".equals(binding.getName())) { //$NON-NLS-1$
579
if (binding.isAnonymous()) {
580                 ITypeBinding[] baseBindings= binding.getInterfaces();
581                 if (baseBindings.length > 0) {
582                     return baseBindings[0];
583                 }
584                 return binding.getSuperclass();
585             }
586             if (binding.isCapture()) {
587                 return binding.getWildcard();
588             }
589             return binding;
590         }
591         return null;
592     }
593     
594     /**
595      * Adds a new import to the rewriter's record and returns a {@link Type} that can be used
596      * in the code. The type binding can be an array binding, type variable or wildcard.
597      * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
598      * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
599      * of wildcards are ignored.
600      * <p>
601      * 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.
602      * </p>
603      * <p>
604      * The content of the compilation unit itself is actually not modified
605      * in any way by this method; rather, the rewriter just records that a new import has been added.
606      * </p>
607      * @param binding the signature of the type to be added.
608      * @param ast the AST to create the returned type for.
609      * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
610      * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
611      */

612     public Type addImport(ITypeBinding binding, AST ast) {
613         return addImport(binding, ast, this.defaultContext);
614     }
615     
616     /**
617      * Adds a new import to the rewriter's record and returns a {@link Type} that can be used
618      * in the code. The type binding can be an array binding, type variable or wildcard.
619      * If the binding is a generic type, the type parameters are ignored. For parameterized types, also the type
620      * arguments are processed and imports added if necessary. Anonymous types inside type arguments are normalized to their base type, wildcard
621      * of wildcards are ignored.
622      * <p>
623      * 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.
624      * </p>
625      * <p>
626      * The content of the compilation unit itself is actually not modified
627      * in any way by this method; rather, the rewriter just records that a new import has been added.
628      * </p>
629      * @param binding the signature of the type to be added.
630      * @param ast the AST to create the returned type for.
631      * @param context an optional context that knows about types visible in the current scope or <code>null</code>
632      * to use the default context only using the available imports.
633      * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
634      * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
635      */

636     public Type addImport(ITypeBinding binding, AST ast, ImportRewriteContext context) {
637         if (binding.isPrimitive()) {
638             return ast.newPrimitiveType(PrimitiveType.toCode(binding.getName()));
639         }
640         
641         ITypeBinding normalizedBinding= normalizeTypeBinding(binding);
642         if (normalizedBinding == null) {
643             return ast.newSimpleType(ast.newSimpleName("invalid")); //$NON-NLS-1$
644
}
645         
646         if (normalizedBinding.isTypeVariable()) {
647             // no import
648
return ast.newSimpleType(ast.newSimpleName(binding.getName()));
649         }
650         if (normalizedBinding.isWildcardType()) {
651             WildcardType wcType= ast.newWildcardType();
652             ITypeBinding bound= normalizedBinding.getBound();
653             if (bound != null && !bound.isWildcardType() && !bound.isCapture()) { // bug 96942
654
Type boundType= addImport(bound, ast, context);
655                 wcType.setBound(boundType, normalizedBinding.isUpperbound());
656             }
657             return wcType;
658         }
659         
660         if (normalizedBinding.isArray()) {
661             Type elementType= addImport(normalizedBinding.getElementType(), ast, context);
662             return ast.newArrayType(elementType, normalizedBinding.getDimensions());
663         }
664         
665         String JavaDoc qualifiedName= getRawQualifiedName(normalizedBinding);
666         if (qualifiedName.length() > 0) {
667             String JavaDoc res= internalAddImport(qualifiedName, context);
668             
669             ITypeBinding[] typeArguments= normalizedBinding.getTypeArguments();
670             if (typeArguments.length > 0) {
671                 Type erasureType= ast.newSimpleType(ast.newName(res));
672                 ParameterizedType paramType= ast.newParameterizedType(erasureType);
673                 List JavaDoc arguments= paramType.typeArguments();
674                 for (int i= 0; i < typeArguments.length; i++) {
675                     ITypeBinding curr= typeArguments[i];
676                     if (containsNestedCapture(curr, false)) { // see bug 103044
677
arguments.add(ast.newWildcardType());
678                     } else {
679                         arguments.add(addImport(curr, ast, context));
680                     }
681                 }
682                 return paramType;
683             }
684             return ast.newSimpleType(ast.newName(res));
685         }
686         return ast.newSimpleType(ast.newName(getRawName(normalizedBinding)));
687     }
688
689     
690     /**
691      * Adds a new import to the rewriter's record and returns a type reference that can be used
692      * in the code. The type binding can only be an array or non-generic type.
693      * <p>
694      * 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.
695      * </p>
696      * <p>
697      * The content of the compilation unit itself is actually not modified
698      * in any way by this method; rather, the rewriter just records that a new import has been added.
699      * </p>
700      * @param qualifiedTypeName the qualified type name of the type to be added
701      * @param context an optional context that knows about types visible in the current scope or <code>null</code>
702      * to use the default context only using the available imports.
703      * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
704      * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
705      */

706     public String JavaDoc addImport(String JavaDoc qualifiedTypeName, ImportRewriteContext context) {
707         int angleBracketOffset= qualifiedTypeName.indexOf('<');
708         if (angleBracketOffset != -1) {
709             return internalAddImport(qualifiedTypeName.substring(0, angleBracketOffset), context) + qualifiedTypeName.substring(angleBracketOffset);
710         }
711         int bracketOffset= qualifiedTypeName.indexOf('[');
712         if (bracketOffset != -1) {
713             return internalAddImport(qualifiedTypeName.substring(0, bracketOffset), context) + qualifiedTypeName.substring(bracketOffset);
714         }
715         return internalAddImport(qualifiedTypeName, context);
716     }
717     
718     /**
719      * Adds a new import to the rewriter's record and returns a type reference that can be used
720      * in the code. The type binding can only be an array or non-generic type.
721      * <p>
722      * 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.
723      * </p>
724      * <p>
725      * The content of the compilation unit itself is actually not modified
726      * in any way by this method; rather, the rewriter just records that a new import has been added.
727      * </p>
728      * @param qualifiedTypeName the qualified type name of the type to be added
729      * @return returns a type to which the type binding can be assigned to. The returned type contains is unqualified
730      * when an import could be added or was already known. It is fully qualified, if an import conflict prevented the import.
731      */

732     public String JavaDoc addImport(String JavaDoc qualifiedTypeName) {
733         return addImport(qualifiedTypeName, this.defaultContext);
734     }
735     
736     /**
737      * Adds a new static import to the rewriter's record and returns a reference that can be used in the code. The reference will
738      * be fully qualified if an import conflict prevented the import or unqualified if the import succeeded or was already
739      * existing.
740      * <p>
741      * 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.
742      * </p>
743      * <p>
744      * The content of the compilation unit itself is actually not modified
745      * in any way by this method; rather, the rewriter just records that a new import has been added.
746      * </p>
747      * @param binding The binding of the static field or method to be added.
748      * @return returns either the simple member name if the import was successful or else the qualified name if
749      * an import conflict prevented the import.
750      * @throws IllegalArgumentException an {@link IllegalArgumentException} is thrown if the binding is not a static field
751      * or method.
752      */

753     public String JavaDoc addStaticImport(IBinding binding) {
754         return addStaticImport(binding, this.defaultContext);
755     }
756         
757     /**
758      * Adds a new static import to the rewriter's record and returns a reference that can be used in the code. The reference will
759      * be fully qualified if an import conflict prevented the import or unqualified if the import succeeded or was already
760      * existing.
761      * <p>
762      * 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.
763      * </p>
764      * <p>
765      * The content of the compilation unit itself is actually not modified
766      * in any way by this method; rather, the rewriter just records that a new import has been added.
767      * </p>
768      * @param binding The binding of the static field or method to be added.
769      * @param context an optional context that knows about members visible in the current scope or <code>null</code>
770      * to use the default context only using the available imports.
771      * @return returns either the simple member name if the import was successful or else the qualified name if
772      * an import conflict prevented the import.
773      * @throws IllegalArgumentException an {@link IllegalArgumentException} is thrown if the binding is not a static field
774      * or method.
775      */

776     public String JavaDoc addStaticImport(IBinding binding, ImportRewriteContext context) {
777         if (Modifier.isStatic(binding.getModifiers())) {
778             if (binding instanceof IVariableBinding) {
779                 IVariableBinding variableBinding= (IVariableBinding) binding;
780                 if (variableBinding.isField()) {
781                     ITypeBinding declaringType= variableBinding.getDeclaringClass();
782                     return addStaticImport(getRawQualifiedName(declaringType), binding.getName(), true, context);
783                 }
784             } else if (binding instanceof IMethodBinding) {
785                 ITypeBinding declaringType= ((IMethodBinding) binding).getDeclaringClass();
786                 return addStaticImport(getRawQualifiedName(declaringType), binding.getName(), false, context);
787             }
788         }
789         throw new IllegalArgumentException JavaDoc("Binding must be a static field or method."); //$NON-NLS-1$
790
}
791     
792     /**
793      * Adds a new static import to the rewriter's record and returns a reference that can be used in the code. The reference will
794      * be fully qualified if an import conflict prevented the import or unqualified if the import succeeded or was already
795      * existing.
796      * <p>
797      * 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.
798      * </p>
799      * <p>
800      * The content of the compilation unit itself is actually not modified
801      * in any way by this method; rather, the rewriter just records that a new import has been added.
802      * </p>
803      * @param declaringTypeName The qualified name of the static's member declaring type
804      * @param simpleName the simple name of the member; either a field or a method name.
805      * @param isField <code>true</code> specifies that the member is a field, <code>false</code> if it is a
806      * method.
807      * @return returns either the simple member name if the import was successful or else the qualified name if
808      * an import conflict prevented the import.
809      */

810     public String JavaDoc addStaticImport(String JavaDoc declaringTypeName, String JavaDoc simpleName, boolean isField) {
811         return addStaticImport(declaringTypeName, simpleName, isField, this.defaultContext);
812     }
813     
814     /**
815      * Adds a new static import to the rewriter's record and returns a reference that can be used in the code. The reference will
816      * be fully qualified if an import conflict prevented the import or unqualified if the import succeeded or was already
817      * existing.
818      * <p>
819      * 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.
820      * </p>
821      * <p>
822      * The content of the compilation unit itself is actually not modified
823      * in any way by this method; rather, the rewriter just records that a new import has been added.
824      * </p>
825      * @param declaringTypeName The qualified name of the static's member declaring type
826      * @param simpleName the simple name of the member; either a field or a method name.
827      * @param isField <code>true</code> specifies that the member is a field, <code>false</code> if it is a
828      * method.
829      * @param context an optional context that knows about members visible in the current scope or <code>null</code>
830      * to use the default context only using the available imports.
831      * @return returns either the simple member name if the import was successful or else the qualified name if
832      * an import conflict prevented the import.
833      */

834     public String JavaDoc addStaticImport(String JavaDoc declaringTypeName, String JavaDoc simpleName, boolean isField, ImportRewriteContext context) {
835         if (declaringTypeName.indexOf('.') == -1) {
836             return declaringTypeName + '.' + simpleName;
837         }
838         if (context == null) {
839             context= this.defaultContext;
840         }
841         int kind= isField ? ImportRewriteContext.KIND_STATIC_FIELD : ImportRewriteContext.KIND_STATIC_METHOD;
842         int res= context.findInContext(declaringTypeName, simpleName, kind);
843         if (res == ImportRewriteContext.RES_NAME_CONFLICT) {
844             return declaringTypeName + '.' + simpleName;
845         }
846         if (res == ImportRewriteContext.RES_NAME_UNKNOWN) {
847             addEntry(STATIC_PREFIX + declaringTypeName + '.' + simpleName);
848         }
849         return simpleName;
850     }
851     
852     private String JavaDoc internalAddImport(String JavaDoc fullTypeName, ImportRewriteContext context) {
853         int idx= fullTypeName.lastIndexOf('.');
854         String JavaDoc typeContainerName, typeName;
855         if (idx != -1) {
856             typeContainerName= fullTypeName.substring(0, idx);
857             typeName= fullTypeName.substring(idx + 1);
858         } else {
859             typeContainerName= ""; //$NON-NLS-1$
860
typeName= fullTypeName;
861         }
862         
863         if (typeContainerName.length() == 0 && PrimitiveType.toCode(typeName) != null) {
864             return fullTypeName;
865         }
866         
867         if (context == null)
868             context= this.defaultContext;
869         
870         int res= context.findInContext(typeContainerName, typeName, ImportRewriteContext.KIND_TYPE);
871         if (res == ImportRewriteContext.RES_NAME_CONFLICT) {
872             return fullTypeName;
873         }
874         if (res == ImportRewriteContext.RES_NAME_UNKNOWN) {
875             addEntry(NORMAL_PREFIX + fullTypeName);
876         }
877         return typeName;
878     }
879     
880     private void addEntry(String JavaDoc entry) {
881         this.existingImports.add(entry);
882         
883         if (this.removedImports != null) {
884             if (this.removedImports.remove(entry)) {
885                 return;
886             }
887         }
888         
889         if (this.addedImports == null) {
890             this.addedImports= new ArrayList JavaDoc();
891         }
892         this.addedImports.add(entry);
893     }
894     
895     private boolean removeEntry(String JavaDoc entry) {
896         if (this.existingImports.remove(entry)) {
897             if (this.addedImports != null) {
898                 if (this.addedImports.remove(entry)) {
899                     return true;
900                 }
901             }
902             if (this.removedImports == null) {
903                 this.removedImports= new ArrayList JavaDoc();
904             }
905             this.removedImports.add(entry);
906             return true;
907         }
908         return false;
909     }
910     
911     /**
912      * Records to remove a import. No remove is recorded if no such import exists or if such an import is recorded
913      * to be added. In that case the record of the addition is discarded.
914      * <p>
915      * The content of the compilation unit itself is actually not modified
916      * in any way by this method; rather, the rewriter just records that an import has been removed.
917      * </p>
918      * @param qualifiedName The import name to remove.
919      * @return <code>true</code> is returned of an import of the given name could be found.
920      */

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

935     public boolean removeStaticImport(String JavaDoc qualifiedName) {
936         return removeEntry(STATIC_PREFIX + qualifiedName);
937     }
938     
939     private static String JavaDoc getRawName(ITypeBinding normalizedBinding) {
940         return normalizedBinding.getTypeDeclaration().getName();
941     }
942
943     private static String JavaDoc getRawQualifiedName(ITypeBinding normalizedBinding) {
944         return normalizedBinding.getTypeDeclaration().getQualifiedName();
945     }
946     
947
948     /**
949      * Converts all modifications recorded by this rewriter into an object representing the corresponding text
950      * edits to the source code of the rewrite's compilation unit. The compilation unit itself is not modified.
951      * <p>
952      * Calling this methods does not discard the modifications on record. Subsequence modifications are added
953      * to the ones already on record. If this method is called again later, the resulting text edit object will accurately
954      * reflect the net cumulative affect of all those changes.
955      * </p>
956      * @param monitor the progress monitor or <code>null</code>
957      * @return text edit object describing the changes to the document corresponding to the changes
958      * recorded by this rewriter
959      * @throws CoreException the exception is thrown if the rewrite fails.
960      */

961     public final TextEdit rewriteImports(IProgressMonitor monitor) throws CoreException {
962         if (monitor == null) {
963             monitor= new NullProgressMonitor();
964         }
965         
966         try {
967             monitor.beginTask(Messages.bind(Messages.importRewrite_processDescription), 2);
968             if (!hasRecordedChanges()) {
969                 this.createdImports= CharOperation.NO_STRINGS;
970                 this.createdStaticImports= CharOperation.NO_STRINGS;
971                 return new MultiTextEdit();
972             }
973             
974             CompilationUnit usedAstRoot= this.astRoot;
975             if (usedAstRoot == null) {
976                 ASTParser parser= ASTParser.newParser(AST.JLS3);
977                 parser.setSource(this.compilationUnit);
978                 parser.setFocalPosition(0); // reduced AST
979
parser.setResolveBindings(false);
980                 usedAstRoot= (CompilationUnit) parser.createAST(new SubProgressMonitor(monitor, 1));
981             }
982                         
983             ImportRewriteAnalyzer computer= new ImportRewriteAnalyzer(this.compilationUnit, usedAstRoot, this.importOrder, this.importOnDemandThreshold, this.staticImportOnDemandThreshold, this.restoreExistingImports);
984             computer.setFilterImplicitImports(this.filterImplicitImports);
985             
986             if (this.addedImports != null) {
987                 for (int i= 0; i < this.addedImports.size(); i++) {
988                     String JavaDoc curr= (String JavaDoc) this.addedImports.get(i);
989                     computer.addImport(curr.substring(1), STATIC_PREFIX == curr.charAt(0));
990                 }
991             }
992             
993             if (this.removedImports != null) {
994                 for (int i= 0; i < this.removedImports.size(); i++) {
995                     String JavaDoc curr= (String JavaDoc) this.removedImports.get(i);
996                     computer.removeImport(curr.substring(1), STATIC_PREFIX == curr.charAt(0));
997                 }
998             }
999                 
1000            TextEdit result= computer.getResultingEdits(new SubProgressMonitor(monitor, 1));
1001            this.createdImports= computer.getCreatedImports();
1002            this.createdStaticImports= computer.getCreatedStaticImports();
1003            return result;
1004        } finally {
1005            monitor.done();
1006        }
1007    }
1008    
1009    /**
1010     * Returns all new non-static imports created by the last invocation of {@link #rewriteImports(IProgressMonitor)}
1011     * or <code>null</code> if these methods have not been called yet.
1012     * <p>
1013     * Note that this list doesn't need to be the same as the added imports (see {@link #getAddedImports()}) as
1014     * implicit imports are not created and some imports are represented by on-demand imports instead.
1015     * </p>
1016     * @return the created imports
1017     */

1018    public String JavaDoc[] getCreatedImports() {
1019        return this.createdImports;
1020    }
1021    
1022    /**
1023     * Returns all new static imports created by the last invocation of {@link #rewriteImports(IProgressMonitor)}
1024     * or <code>null</code> if these methods have not been called yet.
1025     * <p>
1026     * Note that this list doesn't need to be the same as the added static imports ({@link #getAddedStaticImports()}) as
1027     * implicit imports are not created and some imports are represented by on-demand imports instead.
1028     * </p
1029     * @return the created imports
1030     */

1031    public String JavaDoc[] getCreatedStaticImports() {
1032        return this.createdStaticImports;
1033    }
1034    
1035    /**
1036     * Returns all non-static imports that are recorded to be added.
1037     *
1038     * @return the imports recorded to be added.
1039     */

1040    public String JavaDoc[] getAddedImports() {
1041        return filterFromList(this.addedImports, NORMAL_PREFIX);
1042    }
1043    
1044    /**
1045     * Returns all static imports that are recorded to be added.
1046     *
1047     * @return the static imports recorded to be added.
1048     */

1049    public String JavaDoc[] getAddedStaticImports() {
1050        return filterFromList(this.addedImports, STATIC_PREFIX);
1051    }
1052    
1053    /**
1054     * Returns all non-static imports that are recorded to be removed.
1055     *
1056     * @return the imports recorded to be removed.
1057     */

1058    public String JavaDoc[] getRemovedImports() {
1059        return filterFromList(this.removedImports, NORMAL_PREFIX);
1060    }
1061    
1062    /**
1063     * Returns all static imports that are recorded to be removed.
1064     *
1065     * @return the static imports recorded to be removed.
1066     */

1067    public String JavaDoc[] getRemovedStaticImports() {
1068        return filterFromList(this.removedImports, STATIC_PREFIX);
1069    }
1070    
1071    /**
1072     * Returns <code>true</code> if imports have been recorded to be added or removed.
1073     * @return boolean returns if any changes to imports have been recorded.
1074     */

1075    public boolean hasRecordedChanges() {
1076        return !this.restoreExistingImports ||
1077            (this.addedImports != null && !this.addedImports.isEmpty()) ||
1078            (this.removedImports != null && !this.removedImports.isEmpty());
1079    }
1080    
1081    
1082    private static String JavaDoc[] filterFromList(List JavaDoc imports, char prefix) {
1083        if (imports == null) {
1084            return CharOperation.NO_STRINGS;
1085        }
1086        ArrayList JavaDoc res= new ArrayList JavaDoc();
1087        for (int i= 0; i < imports.size(); i++) {
1088            String JavaDoc curr= (String JavaDoc) imports.get(i);
1089            if (prefix == curr.charAt(0)) {
1090                res.add(curr.substring(1));
1091            }
1092        }
1093        return (String JavaDoc[]) res.toArray(new String JavaDoc[res.size()]);
1094    }
1095        
1096}
1097
Popular Tags