/*******************************************************************************
* Copyright (c) 2000, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Martin Taal - reworked for simpler use case (see class comments)
*******************************************************************************/
package org.eclipse.emf.texo.generator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.Name;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.ui.CodeStyleConfiguration;
import org.eclipse.text.edits.TextEdit;
/**
* Is initially copied from
* org.eclipse.jdt.internal.corext.codemanipulation.OrganizeImportsOperation and
* then adapted to work specifically for code generation where the code
* generation contains fully qualified type names together with type names which
* already have an import declaration. Other changes compared to the original
* class:
* <ul>
* <li>static imports are not supported</li>
* <li>type references are not checked against the available names in the
* project, all names are assumed to be fully qualified or already imported</li>
* </ul>
*
* @author <a href="mtaal@elver.org">Martin Taal</a>
*/
public class ImportOrganizer {
// public static interface IChooseImportQuery {
// /**
// * Selects imports from a list of choices.
// *
// * @param openChoices
// * From each array, a type reference has to be selected
// * @param ranges
// * For each choice the range of the corresponding type
// * reference.
// * @return Returns <code>null</code> to cancel the operation, or the
// * selected imports.
// */
// TypeNameMatch[] chooseImports(TypeNameMatch[][] openChoices,
// ISourceRange[] ranges);
// }
//
// private static class TypeReferenceProcessor {
//
// private static class UnresolvedTypeData {
// final SimpleName ref;
// final int typeKinds;
// final List foundInfos;
//
// public UnresolvedTypeData(final SimpleName ref) {
// this.ref = ref;
// this.typeKinds = ASTResolving.getPossibleTypeKinds(ref, true);
// this.foundInfos = new ArrayList(3);
// }
//
// public void addInfo(final TypeNameMatch info) {
// for (int i = this.foundInfos.size() - 1; i >= 0; i--) {
// TypeNameMatch curr = (TypeNameMatch) this.foundInfos.get(i);
// if (curr.getTypeContainerName().equals(
// info.getTypeContainerName())) {
// return; // not added. already contains type with same
// // name
// }
// }
// foundInfos.add(info);
// }
// }
//
// private Set fOldSingleImports;
// private Set fOldDemandImports;
//
// private Set fImplicitImports;
//
// private ImportRewrite fImpStructure;
//
// private boolean fDoIgnoreLowerCaseNames;
//
// private IPackageFragment fCurrPackage;
//
// private ScopeAnalyzer fAnalyzer;
// // private boolean fAllowDefaultPackageImports;
//
// private Map fUnresolvedTypes;
// private Set fImportsAdded;
// private TypeNameMatch[][] fOpenChoices;
// private SourceRange[] fSourceRanges;
//
// public TypeReferenceProcessor(final Set oldSingleImports,
// final Set oldDemandImports, final CompilationUnit root,
// final ImportRewrite impStructure,
// final boolean ignoreLowerCaseNames) {
// fOldSingleImports = oldSingleImports;
// fOldDemandImports = oldDemandImports;
// fImpStructure = impStructure;
// fDoIgnoreLowerCaseNames = ignoreLowerCaseNames;
//
// ICompilationUnit cu = impStructure.getCompilationUnit();
//
// fImplicitImports = new HashSet(3);
// fImplicitImports.add(""); //$NON-NLS-1$
// fImplicitImports.add("java.lang"); //$NON-NLS-1$
// fImplicitImports.add(cu.getParent().getElementName());
//
// fAnalyzer = new ScopeAnalyzer(root);
//
// fCurrPackage = (IPackageFragment) cu.getParent();
//
// // fAllowDefaultPackageImports = cu.getJavaProject().getOption(
// // JavaCore.COMPILER_SOURCE, true)
// // .equals(JavaCore.VERSION_1_3);
//
// fImportsAdded = new HashSet();
// fUnresolvedTypes = new HashMap();
// }
//
// // private boolean needsImport(final ITypeBinding typeBinding,
// // final SimpleName ref) {
// // if (!typeBinding.isTopLevel() && !typeBinding.isMember()
// // || typeBinding.isRecovered()) {
// // return false; // no imports for anonymous, local, primitive
// // // types or parameters types
// // }
// // int modifiers = typeBinding.getModifiers();
// // if (Modifier.isPrivate(modifiers)) {
// // return false; // imports for privates are not required
// // }
// // ITypeBinding currTypeBinding = Bindings.getBindingOfParentType(ref);
// // if (currTypeBinding == null) {
// // if (ASTNodes.getParent(ref, ASTNode.PACKAGE_DECLARATION) != null) {
// // return true; // reference in package-info.java
// // }
// // return false; // not in a type
// // }
// // if (!Modifier.isPublic(modifiers)) {
// // if (!currTypeBinding.getPackage().getName().equals(
// // typeBinding.getPackage().getName())) {
// // return false; // not visible
// // }
// // }
// //
// // ASTNode parent = ref.getParent();
// // while (parent instanceof Type) {
// // parent = parent.getParent();
// // }
// // if (parent instanceof AbstractTypeDeclaration
// // && parent.getParent() instanceof CompilationUnit) {
// // return true;
// // }
// //
// // if (typeBinding.isMember()) {
// // if (fAnalyzer.isDeclaredInScope(typeBinding, ref,
// // ScopeAnalyzer.TYPES | ScopeAnalyzer.CHECK_VISIBILITY)) {
// // return false;
// // }
// // }
// // return true;
// // }
//
// /**
// * Tries to find the given type name and add it to the import structure.
// *
// * @param ref
// * the name node
// */
// public void add(final SimpleName ref) {
// String typeName = ref.getIdentifier();
//
// if (fImportsAdded.contains(typeName)) {
// return;
// }
//
// IBinding binding = ref.resolveBinding();
// if (binding != null) {
// if (binding.getKind() != IBinding.TYPE) {
// return;
// }
// ITypeBinding typeBinding = (ITypeBinding) binding;
// if (typeBinding.isArray()) {
// typeBinding = typeBinding.getElementType();
// }
// typeBinding = typeBinding.getTypeDeclaration();
// if (!typeBinding.isRecovered()) {
// if (needsImport(typeBinding, ref)) {
// fImpStructure.addImport(typeBinding);
// fImportsAdded.add(typeName);
// }
// return;
// }
// } else {
// if (fDoIgnoreLowerCaseNames && typeName.length() > 0) {
// char ch = typeName.charAt(0);
// if (Strings.isLowerCase(ch) && Character.isLetter(ch)) {
// return;
// }
// }
// }
// fImportsAdded.add(typeName);
// fUnresolvedTypes.put(typeName, new UnresolvedTypeData(ref));
// }
//
// public boolean process(final IProgressMonitor monitor)
// throws JavaModelException {
// try {
// int nUnresolved = fUnresolvedTypes.size();
// if (nUnresolved == 0) {
// return false;
// }
// char[][] allTypes = new char[nUnresolved][];
// int i = 0;
// for (Iterator iter = fUnresolvedTypes.keySet().iterator(); iter
// .hasNext();) {
// allTypes[i++] = ((String) iter.next()).toCharArray();
// }
// final ArrayList typesFound = new ArrayList();
// final IJavaProject project = fCurrPackage.getJavaProject();
// IJavaSearchScope scope = SearchEngine
// .createJavaSearchScope(new IJavaElement[] { project });
// TypeNameMatchCollector collector = new TypeNameMatchCollector(
// typesFound);
// new SearchEngine().searchAllTypeNames(null, allTypes, scope,
// collector,
// IJavaSearchConstants.WAIT_UNTIL_READY_TO_SEARCH,
// monitor);
//
// boolean is50OrHigher = JavaModelUtil.is50OrHigher(project);
//
// for (i = 0; i < typesFound.size(); i++) {
// TypeNameMatch curr = (TypeNameMatch) typesFound.get(i);
// UnresolvedTypeData data = (UnresolvedTypeData) fUnresolvedTypes
// .get(curr.getSimpleTypeName());
// if (data != null && isVisible(curr)
// && isOfKind(curr, data.typeKinds, is50OrHigher)) {
// if (fAllowDefaultPackageImports
// || curr.getPackageName().length() > 0) {
// data.addInfo(curr);
// }
// }
// }
//
// ArrayList openChoices = new ArrayList(nUnresolved);
// ArrayList sourceRanges = new ArrayList(nUnresolved);
// for (Iterator iter = fUnresolvedTypes.values().iterator(); iter
// .hasNext();) {
// UnresolvedTypeData data = (UnresolvedTypeData) iter.next();
// TypeNameMatch[] openChoice = processTypeInfo(data.foundInfos);
// if (openChoice != null) {
// openChoices.add(openChoice);
// sourceRanges.add(new SourceRange(data.ref
// .getStartPosition(), data.ref.getLength()));
// }
// }
// if (openChoices.isEmpty()) {
// return false;
// }
// fOpenChoices = (TypeNameMatch[][]) openChoices
// .toArray(new TypeNameMatch[openChoices.size()][]);
// fSourceRanges = (SourceRange[]) sourceRanges
// .toArray(new SourceRange[sourceRanges.size()]);
// return true;
// } finally {
// monitor.done();
// }
// }
//
// private TypeNameMatch[] processTypeInfo(final List typeRefsFound) {
// int nFound = typeRefsFound.size();
// if (nFound == 0) {
// // nothing found
// return null;
// } else if (nFound == 1) {
// TypeNameMatch typeRef = (TypeNameMatch) typeRefsFound.get(0);
// fImpStructure.addImport(typeRef.getFullyQualifiedName());
// return null;
// } else {
// String typeToImport = null;
// boolean ambiguousImports = false;
//
// // multiple found, use old imports to find an entry
// for (int i = 0; i < nFound; i++) {
// TypeNameMatch typeRef = (TypeNameMatch) typeRefsFound
// .get(i);
// String fullName = typeRef.getFullyQualifiedName();
// String containerName = typeRef.getTypeContainerName();
// if (fOldSingleImports.contains(fullName)) {
// // was single-imported
// fImpStructure.addImport(fullName);
// return null;
// } else if (fOldDemandImports.contains(containerName)
// || fImplicitImports.contains(containerName)) {
// if (typeToImport == null) {
// typeToImport = fullName;
// } else { // more than one import-on-demand
// ambiguousImports = true;
// }
// }
// }
//
// if (typeToImport != null && !ambiguousImports) {
// fImpStructure.addImport(typeToImport);
// return null;
// }
// // return the open choices
// return (TypeNameMatch[]) typeRefsFound
// .toArray(new TypeNameMatch[nFound]);
// }
// }
//
// private boolean isOfKind(final TypeNameMatch curr, final int typeKinds,
// final boolean is50OrHigher) {
// int flags = curr.getModifiers();
// if (Flags.isAnnotation(flags)) {
// return is50OrHigher
// && (typeKinds & SimilarElementsRequestor.ANNOTATIONS) != 0;
// }
// if (Flags.isEnum(flags)) {
// return is50OrHigher
// && (typeKinds & SimilarElementsRequestor.ENUMS) != 0;
// }
// if (Flags.isInterface(flags)) {
// return (typeKinds & SimilarElementsRequestor.INTERFACES) != 0;
// }
// return (typeKinds & SimilarElementsRequestor.CLASSES) != 0;
// }
//
// private boolean isVisible(final TypeNameMatch curr) {
// int flags = curr.getModifiers();
// if (Flags.isPrivate(flags)) {
// return false;
// }
// if (Flags.isPublic(flags) || Flags.isProtected(flags)) {
// return true;
// }
// return curr.getPackageName().equals(fCurrPackage.getElementName());
// }
//
// public TypeNameMatch[][] getChoices() {
// return fOpenChoices;
// }
//
// public ISourceRange[] getChoicesSourceRanges() {
// return fSourceRanges;
// }
// }
// private IChooseImportQuery fChooseImportQuery;
private int fNumberOfImportsAdded;
private int fNumberOfImportsRemoved;
private IProblem fParsingError;
private IJavaProject javaProject;
private CompilationUnit fASTRoot;
// private final boolean fAllowSyntaxErrors;
public ImportOrganizer(final IJavaProject project,
final CompilationUnit astRoot) {
javaProject = project;
fASTRoot = astRoot;
// fAllowSyntaxErrors = allowSyntaxErrors;
// fChooseImportQuery = chooseImportQuery;
fNumberOfImportsAdded = 0;
fNumberOfImportsRemoved = 0;
fParsingError = null;
}
// /**
// * Runs the operation.
// *
// * @param monitor
// * the progress monitor
// * @throws CoreException
// * thrown when the operation failed
// * @throws OperationCanceledException
// * Runtime error thrown when operation is canceled.
// */
// public void run(IProgressMonitor monitor) throws CoreException,
// OperationCanceledException {
// if (monitor == null) {
// monitor = new NullProgressMonitor();
// }
// try {
// monitor
// .beginTask(
// Messages
// .format(
// CodeGenerationMessages.OrganizeImportsOperation_description,
// BasicElementLabels
// .getFileName(fCompilationUnit)),
// 10);
//
// TextEdit edit = createTextEdit(new SubProgressMonitor(monitor, 9));
// if (edit == null) {
// return;
// }
//
// JavaModelUtil.applyEdit(fCompilationUnit, edit, fDoSave,
// new SubProgressMonitor(monitor, 1));
// } finally {
// monitor.done();
// }
// }
public TextEdit createTextEdit() throws CoreException,
OperationCanceledException {
fNumberOfImportsAdded = 0;
fNumberOfImportsRemoved = 0;
// monitor
// .beginTask(
// Messages
// .format(
// CodeGenerationMessages.OrganizeImportsOperation_description,
// BasicElementLabels
// .getFileName(fCompilationUnit)),
// 9);
CompilationUnit astRoot = fASTRoot;
// if (astRoot == null) {
// astRoot = SharedASTProvider.getAST(fCompilationUnit,
// SharedASTProvider.WAIT_YES, new SubProgressMonitor(
// monitor, 2));
// if (monitor.isCanceled()) {
// throw new OperationCanceledException();
// }
// } else {
// monitor.worked(2);
// }
ImportRewrite importsRewrite = CodeStyleConfiguration
.createImportRewrite(astRoot, false);
Set<String> oldSingleImports = new HashSet<String>();
Set<String> oldDemandImports = new HashSet<String>();
List<Name> typeReferences = new ArrayList<Name>();
List<SimpleName> staticReferences = new ArrayList<SimpleName>();
if (!collectReferences(astRoot, typeReferences, staticReferences,
oldSingleImports, oldDemandImports)) {
return null;
}
Iterator<Name> refIterator = typeReferences.iterator();
while (refIterator.hasNext()) {
Name typeRef = refIterator.next();
if (needsImporting(typeRef)) {
importsRewrite.addImport(typeRef.getFullyQualifiedName());
}
}
// TypeReferenceProcessor processor = new TypeReferenceProcessor(
// oldSingleImports, oldDemandImports, astRoot,
// importsRewrite, fIgnoreLowerCaseNames);
// Iterator refIterator = typeReferences.iterator();
// while (refIterator.hasNext()) {
// SimpleName typeRef = (SimpleName) refIterator.next();
// processor.add(typeRef);
// }
// boolean hasOpenChoices = processor.process(new
// SubProgressMonitor(
// monitor, 3));
// addStaticImports(staticReferences, importsRewrite);
//
// if (hasOpenChoices && fChooseImportQuery != null) {
// TypeNameMatch[][] choices = processor.getChoices();
// ISourceRange[] ranges = processor.getChoicesSourceRanges();
// TypeNameMatch[] chosen = fChooseImportQuery.chooseImports(
// choices, ranges);
// if (chosen == null) {
// // cancel pressed by the user
// throw new OperationCanceledException();
// }
// for (int i = 0; i < chosen.length; i++) {
// TypeNameMatch typeInfo = chosen[i];
// importsRewrite.addImport(typeInfo.getFullyQualifiedName());
// }
// }
TextEdit result = importsRewrite.rewriteImports(null);
determineImportDifferences(importsRewrite, oldSingleImports,
oldDemandImports);
return result;
}
// TODO: compare against the current package
private boolean needsImporting(final Name typeRef) {
return true;
}
private void determineImportDifferences(
final ImportRewrite importsStructure,
final Set<String> oldSingleImports,
final Set<String> oldDemandImports) {
ArrayList<String> importsAdded = new ArrayList<String>();
importsAdded
.addAll(Arrays.asList(importsStructure.getCreatedImports()));
importsAdded.addAll(Arrays.asList(importsStructure
.getCreatedStaticImports()));
Object[] content = oldSingleImports.toArray();
for (int i = 0; i < content.length; i++) {
String importName = (String) content[i];
if (importsAdded.remove(importName)) {
oldSingleImports.remove(importName);
}
}
content = oldDemandImports.toArray();
for (int i = 0; i < content.length; i++) {
String importName = (String) content[i];
if (importsAdded.remove(importName + ".*")) {
oldDemandImports.remove(importName);
}
}
fNumberOfImportsAdded = importsAdded.size();
fNumberOfImportsRemoved = oldSingleImports.size()
+ oldDemandImports.size();
}
// private void addStaticImports(final List/* <SimpleName>
// */staticReferences,
// final ImportRewrite importsStructure) {
// for (int i = 0; i < staticReferences.size(); i++) {
// Name name = (Name) staticReferences.get(i);
// IBinding binding = name.resolveBinding();
// if (binding != null) { // paranoia check
// importsStructure.addStaticImport(binding);
// }
// }
// }
// find type references in a compilation unit
private boolean collectReferences(final CompilationUnit astRoot,
final List<Name> typeReferences,
final List<SimpleName> staticReferences,
final Set<String> oldSingleImports,
final Set<String> oldDemandImports) {
// if (!fAllowSyntaxErrors) {
// IProblem[] problems = astRoot.getProblems();
// for (int i = 0; i < problems.length; i++) {
// IProblem curr = problems[i];
// if (curr.isError() && (curr.getID() & IProblem.Syntax) != 0) {
// fParsingError = problems[i];
// return false;
// }
// }
// }
List<?> imports = astRoot.imports();
for (int i = 0; i < imports.size(); i++) {
ImportDeclaration curr = (ImportDeclaration) imports.get(i);
String id = curr.getName().getFullyQualifiedName();
if (curr.isOnDemand()) {
oldDemandImports.add(id);
} else {
oldSingleImports.add(id);
}
}
ImportReferencesCollector.collect(astRoot, javaProject, typeReferences);
return true;
}
/**
* After executing the operation, returns <code>null</code> if the operation
* has been executed successfully or the range where parsing failed.
*
* @return returns the parse error
*/
public IProblem getParseError() {
return fParsingError;
}
public int getNumberOfImportsAdded() {
return fNumberOfImportsAdded;
}
public int getNumberOfImportsRemoved() {
return fNumberOfImportsRemoved;
}
}