KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > internal > corext > codemanipulation > OrganizeImportsOperation


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 package org.eclipse.jdt.internal.corext.codemanipulation;
12
13 import java.util.ArrayList JavaDoc;
14 import java.util.Arrays JavaDoc;
15 import java.util.HashMap JavaDoc;
16 import java.util.HashSet JavaDoc;
17 import java.util.Iterator JavaDoc;
18 import java.util.List JavaDoc;
19 import java.util.Map JavaDoc;
20 import java.util.Set JavaDoc;
21
22 import org.eclipse.text.edits.TextEdit;
23
24 import org.eclipse.core.runtime.CoreException;
25 import org.eclipse.core.runtime.IProgressMonitor;
26 import org.eclipse.core.runtime.NullProgressMonitor;
27 import org.eclipse.core.runtime.OperationCanceledException;
28 import org.eclipse.core.runtime.SubProgressMonitor;
29 import org.eclipse.core.runtime.jobs.ISchedulingRule;
30
31 import org.eclipse.core.resources.IWorkspaceRunnable;
32 import org.eclipse.core.resources.ResourcesPlugin;
33
34 import org.eclipse.jdt.core.Flags;
35 import org.eclipse.jdt.core.ICompilationUnit;
36 import org.eclipse.jdt.core.IJavaElement;
37 import org.eclipse.jdt.core.IJavaProject;
38 import org.eclipse.jdt.core.IPackageFragment;
39 import org.eclipse.jdt.core.ISourceRange;
40 import org.eclipse.jdt.core.JavaCore;
41 import org.eclipse.jdt.core.JavaModelException;
42 import org.eclipse.jdt.core.compiler.IProblem;
43 import org.eclipse.jdt.core.dom.ASTNode;
44 import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
45 import org.eclipse.jdt.core.dom.CompilationUnit;
46 import org.eclipse.jdt.core.dom.IBinding;
47 import org.eclipse.jdt.core.dom.ITypeBinding;
48 import org.eclipse.jdt.core.dom.ImportDeclaration;
49 import org.eclipse.jdt.core.dom.Modifier;
50 import org.eclipse.jdt.core.dom.Name;
51 import org.eclipse.jdt.core.dom.SimpleName;
52 import org.eclipse.jdt.core.dom.Type;
53 import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
54 import org.eclipse.jdt.core.search.IJavaSearchConstants;
55 import org.eclipse.jdt.core.search.IJavaSearchScope;
56 import org.eclipse.jdt.core.search.SearchEngine;
57 import org.eclipse.jdt.core.search.TypeNameMatch;
58
59 import org.eclipse.jdt.internal.corext.SourceRange;
60 import org.eclipse.jdt.internal.corext.dom.Bindings;
61 import org.eclipse.jdt.internal.corext.dom.ScopeAnalyzer;
62 import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
63 import org.eclipse.jdt.internal.corext.util.Messages;
64 import org.eclipse.jdt.internal.corext.util.Strings;
65 import org.eclipse.jdt.internal.corext.util.TypeNameMatchCollector;
66
67 import org.eclipse.jdt.internal.ui.javaeditor.ASTProvider;
68 import org.eclipse.jdt.internal.ui.text.correction.ASTResolving;
69 import org.eclipse.jdt.internal.ui.text.correction.SimilarElementsRequestor;
70
71 public class OrganizeImportsOperation implements IWorkspaceRunnable {
72     public static interface IChooseImportQuery {
73         /**
74          * Selects imports from a list of choices.
75          * @param openChoices From each array, a type reference has to be selected
76          * @param ranges For each choice the range of the corresponding type reference.
77          * @return Returns <code>null</code> to cancel the operation, or the
78          * selected imports.
79          */

80         TypeNameMatch[] chooseImports(TypeNameMatch[][] openChoices, ISourceRange[] ranges);
81     }
82     
83     
84     private static class TypeReferenceProcessor {
85         
86         private static class UnresolvedTypeData {
87             final SimpleName ref;
88             final int typeKinds;
89             final List JavaDoc foundInfos;
90
91             public UnresolvedTypeData(SimpleName ref) {
92                 this.ref= ref;
93                 this.typeKinds= ASTResolving.getPossibleTypeKinds(ref, true);
94                 this.foundInfos= new ArrayList JavaDoc(3);
95             }
96             
97             public void addInfo(TypeNameMatch info) {
98                 for (int i= this.foundInfos.size() - 1; i >= 0; i--) {
99                     TypeNameMatch curr= (TypeNameMatch) this.foundInfos.get(i);
100                     if (curr.getTypeContainerName().equals(info.getTypeContainerName())) {
101                         return; // not added. already contains type with same name
102
}
103                 }
104                 foundInfos.add(info);
105             }
106         }
107         
108         private Set JavaDoc fOldSingleImports;
109         private Set JavaDoc fOldDemandImports;
110         
111         private Set JavaDoc fImplicitImports;
112         
113         private ImportRewrite fImpStructure;
114         
115         private boolean fDoIgnoreLowerCaseNames;
116         
117         private IPackageFragment fCurrPackage;
118         
119         private ScopeAnalyzer fAnalyzer;
120         private boolean fAllowDefaultPackageImports;
121         
122         private Map JavaDoc fUnresolvedTypes;
123         private Set JavaDoc fImportsAdded;
124         private TypeNameMatch[][] fOpenChoices;
125         private SourceRange[] fSourceRanges;
126         
127         
128         public TypeReferenceProcessor(Set JavaDoc oldSingleImports, Set JavaDoc oldDemandImports, CompilationUnit root, ImportRewrite impStructure, boolean ignoreLowerCaseNames) {
129             fOldSingleImports= oldSingleImports;
130             fOldDemandImports= oldDemandImports;
131             fImpStructure= impStructure;
132             fDoIgnoreLowerCaseNames= ignoreLowerCaseNames;
133             
134             ICompilationUnit cu= impStructure.getCompilationUnit();
135             
136             fImplicitImports= new HashSet JavaDoc(3);
137             fImplicitImports.add(""); //$NON-NLS-1$
138
fImplicitImports.add("java.lang"); //$NON-NLS-1$
139
fImplicitImports.add(cu.getParent().getElementName());
140             
141             fAnalyzer= new ScopeAnalyzer(root);
142             
143             fCurrPackage= (IPackageFragment) cu.getParent();
144             
145             fAllowDefaultPackageImports= cu.getJavaProject().getOption(JavaCore.COMPILER_COMPLIANCE, true).equals(JavaCore.VERSION_1_3);
146             
147             fImportsAdded= new HashSet JavaDoc();
148             fUnresolvedTypes= new HashMap JavaDoc();
149         }
150         
151         private boolean needsImport(ITypeBinding typeBinding, SimpleName ref) {
152             if (!typeBinding.isTopLevel() && !typeBinding.isMember() || typeBinding.isRecovered()) {
153                 return false; // no imports for anonymous, local, primitive types or parameters types
154
}
155             int modifiers= typeBinding.getModifiers();
156             if (Modifier.isPrivate(modifiers)) {
157                 return false; // imports for privates are not required
158
}
159             ITypeBinding currTypeBinding= Bindings.getBindingOfParentType(ref);
160             if (currTypeBinding == null) {
161                 return false; // not in a type
162
}
163             if (!Modifier.isPublic(modifiers)) {
164                 if (!currTypeBinding.getPackage().getName().equals(typeBinding.getPackage().getName())) {
165                     return false; // not visible
166
}
167             }
168             
169             ASTNode parent= ref.getParent();
170             while (parent instanceof Type) {
171                 parent= parent.getParent();
172             }
173             if (parent instanceof AbstractTypeDeclaration && parent.getParent() instanceof CompilationUnit) {
174                 return true;
175             }
176             
177             if (typeBinding.isMember()) {
178                 if (fAnalyzer.isDeclaredInScope(typeBinding, ref, ScopeAnalyzer.TYPES | ScopeAnalyzer.CHECK_VISIBILITY))
179                     return false;
180             }
181             return true;
182         }
183         
184         
185         /**
186          * Tries to find the given type name and add it to the import structure.
187          * @param ref the name node
188          */

189         public void add(SimpleName ref) {
190             String JavaDoc typeName= ref.getIdentifier();
191             
192             if (fImportsAdded.contains(typeName)) {
193                 return;
194             }
195             
196             IBinding binding= ref.resolveBinding();
197             if (binding != null) {
198                 if (binding.getKind() != IBinding.TYPE) {
199                     return;
200                 }
201                 ITypeBinding typeBinding= (ITypeBinding) binding;
202                 if (typeBinding.isArray()) {
203                     typeBinding= typeBinding.getElementType();
204                 }
205                 typeBinding= typeBinding.getTypeDeclaration();
206                 if (!typeBinding.isRecovered()) {
207                     if (needsImport(typeBinding, ref)) {
208                         fImpStructure.addImport(typeBinding);
209                         fImportsAdded.add(typeName);
210                     }
211                     return;
212                 }
213             } else {
214                 if (fDoIgnoreLowerCaseNames && typeName.length() > 0) {
215                     char ch= typeName.charAt(0);
216                     if (Strings.isLowerCase(ch) && Character.isLetter(ch)) {
217                         return;
218                     }
219                 }
220             }
221             fImportsAdded.add(typeName);
222             fUnresolvedTypes.put(typeName, new UnresolvedTypeData(ref));
223         }
224             
225         public boolean process(IProgressMonitor monitor) throws JavaModelException {
226             try {
227                 int nUnresolved= fUnresolvedTypes.size();
228                 if (nUnresolved == 0) {
229                     return false;
230                 }
231                 char[][] allTypes= new char[nUnresolved][];
232                 int i= 0;
233                 for (Iterator JavaDoc iter= fUnresolvedTypes.keySet().iterator(); iter.hasNext();) {
234                     allTypes[i++]= ((String JavaDoc) iter.next()).toCharArray();
235                 }
236                 final ArrayList JavaDoc typesFound= new ArrayList JavaDoc();
237                 final IJavaProject project= fCurrPackage.getJavaProject();
238                 IJavaSearchScope scope= SearchEngine.createJavaSearchScope(new IJavaElement[] { project });
239                 TypeNameMatchCollector collector= new TypeNameMatchCollector(typesFound);
240                 new SearchEngine().searchAllTypeNames(null, allTypes, scope, collector, IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH, monitor);
241
242                 boolean is50OrHigher= JavaModelUtil.is50OrHigher(project);
243                 
244                 for (i= 0; i < typesFound.size(); i++) {
245                     TypeNameMatch curr= (TypeNameMatch) typesFound.get(i);
246                     UnresolvedTypeData data= (UnresolvedTypeData) fUnresolvedTypes.get(curr.getSimpleTypeName());
247                     if (data != null && isVisible(curr) && isOfKind(curr, data.typeKinds, is50OrHigher)) {
248                         if (fAllowDefaultPackageImports || curr.getPackageName().length() > 0) {
249                             data.addInfo(curr);
250                         }
251                     }
252                 }
253                 
254                 ArrayList JavaDoc openChoices= new ArrayList JavaDoc(nUnresolved);
255                 ArrayList JavaDoc sourceRanges= new ArrayList JavaDoc(nUnresolved);
256                 for (Iterator JavaDoc iter= fUnresolvedTypes.values().iterator(); iter.hasNext();) {
257                     UnresolvedTypeData data= (UnresolvedTypeData) iter.next();
258                     TypeNameMatch[] openChoice= processTypeInfo(data.foundInfos);
259                     if (openChoice != null) {
260                         openChoices.add(openChoice);
261                         sourceRanges.add(new SourceRange(data.ref.getStartPosition(), data.ref.getLength()));
262                     }
263                 }
264                 if (openChoices.isEmpty()) {
265                     return false;
266                 }
267                 fOpenChoices= (TypeNameMatch[][]) openChoices.toArray(new TypeNameMatch[openChoices.size()][]);
268                 fSourceRanges= (SourceRange[]) sourceRanges.toArray(new SourceRange[sourceRanges.size()]);
269                 return true;
270             } finally {
271                 monitor.done();
272             }
273         }
274         
275         private TypeNameMatch[] processTypeInfo(List JavaDoc typeRefsFound) {
276             int nFound= typeRefsFound.size();
277             if (nFound == 0) {
278                 // nothing found
279
return null;
280             } else if (nFound == 1) {
281                 TypeNameMatch typeRef= (TypeNameMatch) typeRefsFound.get(0);
282                 fImpStructure.addImport(typeRef.getFullyQualifiedName());
283                 return null;
284             } else {
285                 String JavaDoc typeToImport= null;
286                 boolean ambiguousImports= false;
287                 
288                 // multiple found, use old imports to find an entry
289
for (int i= 0; i < nFound; i++) {
290                     TypeNameMatch typeRef= (TypeNameMatch) typeRefsFound.get(i);
291                     String JavaDoc fullName= typeRef.getFullyQualifiedName();
292                     String JavaDoc containerName= typeRef.getTypeContainerName();
293                     if (fOldSingleImports.contains(fullName)) {
294                         // was single-imported
295
fImpStructure.addImport(fullName);
296                         return null;
297                     } else if (fOldDemandImports.contains(containerName) || fImplicitImports.contains(containerName)) {
298                         if (typeToImport == null) {
299                             typeToImport= fullName;
300                         } else { // more than one import-on-demand
301
ambiguousImports= true;
302                         }
303                     }
304                 }
305                 
306                 if (typeToImport != null && !ambiguousImports) {
307                     fImpStructure.addImport(typeToImport);
308                     return null;
309                 }
310                 // return the open choices
311
return (TypeNameMatch[]) typeRefsFound.toArray(new TypeNameMatch[nFound]);
312             }
313         }
314         
315         private boolean isOfKind(TypeNameMatch curr, int typeKinds, boolean is50OrHigher) {
316             int flags= curr.getModifiers();
317             if (Flags.isAnnotation(flags)) {
318                 return is50OrHigher && ((typeKinds & SimilarElementsRequestor.ANNOTATIONS) != 0);
319             }
320             if (Flags.isEnum(flags)) {
321                 return is50OrHigher && ((typeKinds & SimilarElementsRequestor.ENUMS) != 0);
322             }
323             if (Flags.isInterface(flags)) {
324                 return (typeKinds & SimilarElementsRequestor.INTERFACES) != 0;
325             }
326             return (typeKinds & SimilarElementsRequestor.CLASSES) != 0;
327         }
328
329         private boolean isVisible(TypeNameMatch curr) {
330             int flags= curr.getModifiers();
331             if (Flags.isPrivate(flags)) {
332                 return false;
333             }
334             if (Flags.isPublic(flags) || Flags.isProtected(flags)) {
335                 return true;
336             }
337             return curr.getPackageName().equals(fCurrPackage.getElementName());
338         }
339
340         public TypeNameMatch[][] getChoices() {
341             return fOpenChoices;
342         }
343
344         public ISourceRange[] getChoicesSourceRanges() {
345             return fSourceRanges;
346         }
347     }
348
349     private boolean fDoSave;
350     
351     private boolean fIgnoreLowerCaseNames;
352     
353     private IChooseImportQuery fChooseImportQuery;
354     
355     private int fNumberOfImportsAdded;
356     private int fNumberOfImportsRemoved;
357
358     private IProblem fParsingError;
359     private ICompilationUnit fCompilationUnit;
360     
361     private CompilationUnit fASTRoot;
362
363     private final boolean fAllowSyntaxErrors;
364     
365     public OrganizeImportsOperation(ICompilationUnit cu, CompilationUnit astRoot, boolean ignoreLowerCaseNames, boolean save, boolean allowSyntaxErrors, IChooseImportQuery chooseImportQuery) throws CoreException {
366         fCompilationUnit= cu;
367         fASTRoot= astRoot;
368
369         fDoSave= save;
370         fIgnoreLowerCaseNames= ignoreLowerCaseNames;
371         fAllowSyntaxErrors= allowSyntaxErrors;
372         fChooseImportQuery= chooseImportQuery;
373
374         fNumberOfImportsAdded= 0;
375         fNumberOfImportsRemoved= 0;
376         
377         fParsingError= null;
378     }
379
380     /**
381      * Runs the operation.
382      * @param monitor the progress monitor
383      * @throws CoreException thrown when the operation failed
384      * @throws OperationCanceledException Runtime error thrown when operation is canceled.
385      */

386     public void run(IProgressMonitor monitor) throws CoreException, OperationCanceledException {
387         if (monitor == null) {
388             monitor= new NullProgressMonitor();
389         }
390         try {
391             monitor.beginTask(Messages.format(CodeGenerationMessages.OrganizeImportsOperation_description, fCompilationUnit.getElementName()), 10);
392             
393             TextEdit edit= createTextEdit(new SubProgressMonitor(monitor, 9));
394             if (edit == null)
395                 return;
396             
397             JavaModelUtil.applyEdit(fCompilationUnit, edit, fDoSave, new SubProgressMonitor(monitor, 1));
398         } finally {
399             monitor.done();
400         }
401     }
402     
403     public TextEdit createTextEdit(IProgressMonitor monitor) throws CoreException, OperationCanceledException {
404         if (monitor == null) {
405             monitor= new NullProgressMonitor();
406         }
407         try {
408             fNumberOfImportsAdded= 0;
409             fNumberOfImportsRemoved= 0;
410             
411             monitor.beginTask(Messages.format(CodeGenerationMessages.OrganizeImportsOperation_description, fCompilationUnit.getElementName()), 9);
412             
413             CompilationUnit astRoot= fASTRoot;
414             if (astRoot == null) {
415                 astRoot= ASTProvider.getASTProvider().getAST(fCompilationUnit, ASTProvider.WAIT_YES, new SubProgressMonitor(monitor, 2));
416                 if (monitor.isCanceled())
417                     throw new OperationCanceledException();
418             } else {
419                 monitor.worked(2);
420             }
421             
422             ImportRewrite importsRewrite= StubUtility.createImportRewrite(astRoot, false);
423
424             Set JavaDoc/*<String>*/ oldSingleImports= new HashSet JavaDoc();
425             Set JavaDoc/*<String>*/ oldDemandImports= new HashSet JavaDoc();
426             List JavaDoc/*<SimpleName>*/ typeReferences= new ArrayList JavaDoc();
427             List JavaDoc/*<SimpleName>*/ staticReferences= new ArrayList JavaDoc();
428             
429             if (!collectReferences(astRoot, typeReferences, staticReferences, oldSingleImports, oldDemandImports))
430                 return null;
431                         
432             monitor.worked(1);
433         
434             TypeReferenceProcessor processor= new TypeReferenceProcessor(oldSingleImports, oldDemandImports, astRoot, importsRewrite, fIgnoreLowerCaseNames);
435             
436             Iterator JavaDoc refIterator= typeReferences.iterator();
437             while (refIterator.hasNext()) {
438                 SimpleName typeRef= (SimpleName) refIterator.next();
439                 processor.add(typeRef);
440             }
441             
442             boolean hasOpenChoices= processor.process(new SubProgressMonitor(monitor, 3));
443             addStaticImports(staticReferences, importsRewrite);
444             
445             if (hasOpenChoices && fChooseImportQuery != null) {
446                 TypeNameMatch[][] choices= processor.getChoices();
447                 ISourceRange[] ranges= processor.getChoicesSourceRanges();
448                 TypeNameMatch[] chosen= fChooseImportQuery.chooseImports(choices, ranges);
449                 if (chosen == null) {
450                     // cancel pressed by the user
451
throw new OperationCanceledException();
452                 }
453                 for (int i= 0; i < chosen.length; i++) {
454                     TypeNameMatch typeInfo= chosen[i];
455                     importsRewrite.addImport(typeInfo.getFullyQualifiedName());
456                 }
457             }
458
459             TextEdit result= importsRewrite.rewriteImports(new SubProgressMonitor(monitor, 3));
460             
461             determineImportDifferences(importsRewrite, oldSingleImports, oldDemandImports);
462             
463             return result;
464         } finally {
465             monitor.done();
466         }
467     }
468     
469     private void determineImportDifferences(ImportRewrite importsStructure, Set JavaDoc oldSingleImports, Set JavaDoc oldDemandImports) {
470         ArrayList JavaDoc importsAdded= new ArrayList JavaDoc();
471         importsAdded.addAll(Arrays.asList(importsStructure.getCreatedImports()));
472         importsAdded.addAll(Arrays.asList(importsStructure.getCreatedStaticImports()));
473         
474         Object JavaDoc[] content= oldSingleImports.toArray();
475         for (int i= 0; i < content.length; i++) {
476             String JavaDoc importName= (String JavaDoc) content[i];
477             if (importsAdded.remove(importName))
478                 oldSingleImports.remove(importName);
479         }
480         content= oldDemandImports.toArray();
481         for (int i= 0; i < content.length; i++) {
482             String JavaDoc importName= (String JavaDoc) content[i];
483             if (importsAdded.remove(importName + ".*")) //$NON-NLS-1$
484
oldDemandImports.remove(importName);
485         }
486         fNumberOfImportsAdded= importsAdded.size();
487         fNumberOfImportsRemoved= oldSingleImports.size() + oldDemandImports.size();
488     }
489     
490     
491     private void addStaticImports(List JavaDoc/*<SimpleName>*/ staticReferences, ImportRewrite importsStructure) {
492         for (int i= 0; i < staticReferences.size(); i++) {
493             Name name= (Name) staticReferences.get(i);
494             IBinding binding= name.resolveBinding();
495             if (binding != null) { // paranoia check
496
importsStructure.addStaticImport(binding);
497             }
498         }
499     }
500
501     
502     // find type references in a compilation unit
503
private boolean collectReferences(CompilationUnit astRoot, List JavaDoc typeReferences, List JavaDoc staticReferences, Set JavaDoc oldSingleImports, Set JavaDoc oldDemandImports) {
504         if (!fAllowSyntaxErrors) {
505             IProblem[] problems= astRoot.getProblems();
506             for (int i= 0; i < problems.length; i++) {
507                 IProblem curr= problems[i];
508                 if (curr.isError() && (curr.getID() & IProblem.Syntax) != 0) {
509                     fParsingError= problems[i];
510                     return false;
511                 }
512             }
513         }
514         List JavaDoc imports= astRoot.imports();
515         for (int i= 0; i < imports.size(); i++) {
516             ImportDeclaration curr= (ImportDeclaration) imports.get(i);
517             String JavaDoc id= ASTResolving.getFullName(curr.getName());
518             if (curr.isOnDemand()) {
519                 oldDemandImports.add(id);
520             } else {
521                 oldSingleImports.add(id);
522             }
523         }
524         
525         IJavaProject project= fCompilationUnit.getJavaProject();
526         ImportReferencesCollector.collect(astRoot, project, null, typeReferences, staticReferences);
527
528         return true;
529     }
530     
531     /**
532      * After executing the operation, returns <code>null</code> if the operation has been executed successfully or
533      * the range where parsing failed.
534      * @return returns the parse error
535      */

536     public IProblem getParseError() {
537         return fParsingError;
538     }
539     
540     public int getNumberOfImportsAdded() {
541         return fNumberOfImportsAdded;
542     }
543     
544     public int getNumberOfImportsRemoved() {
545         return fNumberOfImportsRemoved;
546     }
547
548     /**
549      * @return Returns the scheduling rule for this operation
550      */

551     public ISchedulingRule getScheduleRule() {
552         return ResourcesPlugin.getWorkspace().getRoot();
553     }
554     
555 }
556
Popular Tags