blob: 8277118ff840c754f18d85a41f17c4664e0d9435 [file] [log] [blame]
/*
* Copyright (c) 2006, 2007 Borland Software Corporation
*
* 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:
* Anna Karjakina (Borland) - initial API and implementation
*/
package org.eclipse.gmf.internal.common.codegen;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
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.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.gmf.internal.common.Activator;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.compiler.IProblem;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTParser;
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.QualifiedName;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.rewrite.ImportRewrite;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.text.edits.MalformedTreeException;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
public class OrganizeImportsPostprocessor {
private final boolean myRestoreExistingImports;
public OrganizeImportsPostprocessor() {
this(true);
}
/**
* @param restoreExistingImports
* specifies if the existing imports should be kept or removed,
* see {@link ImportRewrite#create(CompilationUnit, boolean)} for
* details.
*/
public OrganizeImportsPostprocessor(boolean restoreExistingImports) {
myRestoreExistingImports = restoreExistingImports;
}
/**
* Organizes qualified names in document.
* <p>
* Traverses the ast tree and extracts any full qualified names to be import
* statements, with respect to the name conflicts possible.
* </p>
* <p>
* Afterwards these imports are organized with the help of
* {@link ImportRewrite} manager.
* </p>
* <p>
* Changes are applied directly to the given ICompilationUnit's buffer,
* using code formatting settings as specified in the JDT UI preferences.
* Saving of the unit is left for the user consideration.
* </p>
*
* @param icu
* the compilation unit containing <b>valid</b> code
* @param declaredImportsAsStrings
* imports added in previous file revision (default is null)
* @param monitor
* the progress monitor used to report progress and request
* cancelation, or <code>null</code> if none
*
* @throws CoreException
* the exception is thrown if the rewrite fails.
*
* @see ImportRewrite
*/
public void organizeImports(ICompilationUnit icu, IProgressMonitor progress) throws CoreException {
organizeImports(icu, null, progress);
}
public void organizeImports(ICompilationUnit icu, String[] declaredImportsAsStrings, IProgressMonitor progress) throws CoreException {
IDocument document = new Document(icu.getBuffer().getContents());
ASTParser parser = ASTParser.newParser(AST.JLS4);
parser.setSource(icu);
parser.setCompilerOptions(createCompilerOptions("1.5"));
CompilationUnit cu = (CompilationUnit) parser.createAST(progress);
TextEdit importsEdit = organizeImports(cu, declaredImportsAsStrings, progress);
try {
importsEdit.apply(document);
} catch (BadLocationException e) {
throw new CoreException(new Status(IStatus.ERROR, Activator.getID(), 0, "Unable to apply imports text changes", e));
} catch (MalformedTreeException ex) {
throw new CoreException(new Status(IStatus.ERROR, Activator.getID(), 0, "Unable to apply imports text changes", ex));
}
icu.getBuffer().setContents(document.get());
}
/**
* Organizes qualified names in document.
* <p>
* Traverses the ast tree and extracts any full qualified names to be import
* statements, with respect to the name conflicts possible.
* </p>
* <p>
* Afterwards these imports are organized with the help of
* {@link ImportRewrite} manager.
* </p>
*
* @param astRoot
* the parsed traversable ast tree, should contain no errors
* @param declaredImports
* imports added in previous file revision (default is null)
* @param monitor
* the progress monitor used to report progress and request
* cancelation, or <code>null</code> if none
*
* @return text edit object describing the changes to the document
* corresponding to the changes recorded by rewriter
*
* @throws CoreException
* the exception is thrown if the rewrite fails.
*
* @see ImportRewrite
*/
public TextEdit organizeImports(CompilationUnit astRoot, IProgressMonitor progress) throws CoreException {
return organizeImports(astRoot, null, progress);
}
public TextEdit organizeImports(CompilationUnit astRoot, String[] declaredImports, IProgressMonitor progress) throws CoreException {
MultiTextEdit result = new MultiTextEdit();
HashSet<String> oldSingleImports = new HashSet<String>();
HashSet<String> oldDemandImports = new HashSet<String>();
@SuppressWarnings("unchecked")
final List<ImportDeclaration> importDeclarations = astRoot.imports();
String[] customImports = substract(declaredImports, importDeclarations);
if (isDebug()) {
collectExistingImports(astRoot, oldSingleImports, oldDemandImports, customImports);
}
if (!checkForNoSyntaxErrors(astRoot)) {
String location = astRoot.getJavaElement() == null ? "<undefined>" : astRoot.getJavaElement().getElementName();
throw new CoreException(new Status(IStatus.ERROR, Activator.getID(), 0, Messages.bind(Messages.organizeImportsFail, location), null));
}
ArrayList<Name> qualifiedTypeReferences = new ArrayList<Name>();
ArrayList<SimpleName> simpleTypeReferences = new ArrayList<SimpleName>();
ArrayList<String> importsAdded = new ArrayList<String>();
PackageReferencesCollector.collect(astRoot, qualifiedTypeReferences, simpleTypeReferences, importsAdded);
ImportRewrite importRewrite = createImportRewrite(astRoot);
copyImports(importRewrite, customImports);
ImportRewrite.ImportRewriteContext context = new ReferencedTypesAwareImportRewriteContext(simpleTypeReferences, importRewrite);
Iterator<Name> refIterator = qualifiedTypeReferences.iterator();
while (refIterator.hasNext()) {
Name typeRef = refIterator.next();
if (typeRef.isQualifiedName()) {
QualifiedName qualifiedName = (QualifiedName) typeRef;
SimpleName simpleName = qualifiedName.getName();
boolean added = addImport(simpleName.getIdentifier(), qualifiedName.getFullyQualifiedName(), importRewrite, context, importsAdded);
if (added) {
Name qualifier = qualifiedName.getQualifier();
int qualifierStart = qualifier.getStartPosition();
int simpleNameStart = simpleName.getStartPosition();
try {
result.addChild(new ReplaceEdit(qualifierStart, simpleNameStart - qualifierStart, ""));
} catch (MalformedTreeException e) {
throw new CoreException(new Status(IStatus.ERROR, Activator.getID(), 0, "Unable to produce correct text changes for replacing full name: " + qualifiedName, e));
}
}
} else {
SimpleName simpleName = (SimpleName) typeRef;
addImport(simpleName.getIdentifier(), simpleName.getFullyQualifiedName(), importRewrite, context, importsAdded);
}
}
TextEdit edit = importRewrite.rewriteImports(progress);
try {
result.addChild(edit);
} catch (MalformedTreeException e) {
throw new CoreException(new Status(IStatus.ERROR, Activator.getID(), 0, "Text changes conflict while organizing imports", e));
}
if (isDebug()) {
determineImportDifferences(importRewrite, oldSingleImports, oldDemandImports);
}
return result;
}
/*
* Since we do organizeImports prior to merge, we must ensure imports added
* manually are known to OrganizeImportsProcessor
*/
private static void copyImports(ImportRewrite importRewrite, String[] importsToCopy) {
if (importsToCopy == null || importsToCopy.length == 0) {
return;
}
for (int i = 0; i < importsToCopy.length; i++) {
importRewrite.addImport(importsToCopy[i]);
}
}
private String[] substract(String[] declaredImports, List<ImportDeclaration> list) {
if (declaredImports == null || declaredImports.length == 0) {
return declaredImports;
}
ArrayList<String> result = new ArrayList<String>(Arrays.asList(declaredImports));
for (int i = 0; i < list.size(); i++) {
result.remove(list.get(i).getName().getFullyQualifiedName());
}
return result.toArray(new String[result.size()]);
}
private boolean addImport(String typeName, String fullName, ImportRewrite importRewrite, ImportRewrite.ImportRewriteContext context, Collection<String> importsAdded) {
boolean resultIsOk = importRewrite.addImport(fullName, context).equals(typeName);
if (resultIsOk && !importsAdded.contains(fullName)) {
importsAdded.add(fullName);
}
return resultIsOk;
}
private void collectExistingImports(CompilationUnit astRoot, Set<String> oldSingleImports, Set<String> oldDemandImports, String[] declaredImports) {
if (declaredImports != null && declaredImports.length > 0) {
for (int i = 0; i < declaredImports.length; i++) {
String curr = declaredImports[i];
if (curr.endsWith("*")) {
oldDemandImports.add(curr);
} else {
oldSingleImports.add(curr);
}
}
}
@SuppressWarnings("unchecked")
final List<ImportDeclaration> imports = astRoot.imports();
for (int i = 0; i < imports.size(); i++) {
ImportDeclaration curr = imports.get(i);
String id = curr.getName().getFullyQualifiedName();
if (curr.isOnDemand()) {
oldDemandImports.add(id);
} else {
oldSingleImports.add(id);
}
}
}
private boolean checkForNoSyntaxErrors(CompilationUnit astRoot) {
IProblem[] problems = astRoot.getProblems();
for (int i = 0; i < problems.length; i++) {
IProblem curr = problems[i];
if (curr.isError() && (curr.getID() & IProblem.Syntax) != 0) {
return false;
}
}
return true;
}
private class ReferencedTypesAwareImportRewriteContext extends ImportRewrite.ImportRewriteContext {
private Collection<SimpleName> mySimpleTypesReferenced;
private ImportRewrite myImportRewrite;
public ReferencedTypesAwareImportRewriteContext(Collection<SimpleName> simpleTypesReferenced, ImportRewrite importRewrite) {
mySimpleTypesReferenced = simpleTypesReferenced;
myImportRewrite = importRewrite;
}
public int findInContext(String qualifier, String name, int kind) {
int result = myImportRewrite.getDefaultImportRewriteContext().findInContext(qualifier, name, kind);
if (result == RES_NAME_UNKNOWN) {
for (Iterator<SimpleName> it = mySimpleTypesReferenced.iterator(); it.hasNext();) {
SimpleName next = it.next();
if (name.equals(next.getIdentifier())) {
return RES_NAME_CONFLICT;
}
}
}
return result;
}
};
/**
* Returns a {@link ImportRewrite} using
* {@link ImportRewrite#create(CompilationUnit, boolean)} and configures the
* rewriter with the settings as specified in the JDT UI preferences.
*
* @param astRoot
* the AST root to create the rewriter on
* @return the new rewriter configured with the settings as specified in the
* JDT UI preferences.
*
* @see ImportRewrite#create(CompilationUnit, boolean)
*/
public ImportRewrite createImportRewrite(CompilationUnit astRoot) {
return configureImportRewrite(ImportRewrite.create(astRoot, myRestoreExistingImports));
}
private static ImportRewrite configureImportRewrite(ImportRewrite rewrite) {
IJavaProject project = rewrite.getCompilationUnit().getJavaProject();
String order = PreferenceConstants.getPreference(PreferenceConstants.ORGIMPORTS_IMPORTORDER, project);
rewrite.setImportOrder(order.split(";", 0)); //$NON-NLS-1$
String thres = PreferenceConstants.getPreference(PreferenceConstants.ORGIMPORTS_ONDEMANDTHRESHOLD, project);
try {
int num = Integer.parseInt(thres);
if (num == 0) {
num = 1;
}
rewrite.setOnDemandImportThreshold(num);
} catch (NumberFormatException e) {
// ignore
}
String thresStatic = PreferenceConstants.getPreference(PreferenceConstants.ORGIMPORTS_STATIC_ONDEMANDTHRESHOLD, project);
try {
int num = Integer.parseInt(thresStatic);
if (num == 0) {
num = 1;
}
rewrite.setStaticOnDemandImportThreshold(num);
} catch (NumberFormatException e) {
// ignore
}
return rewrite;
}
private void determineImportDifferences(ImportRewrite importsStructure, Set<String> oldSingleImports, 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 + ".*")) { //$NON-NLS-1$
oldDemandImports.remove(importName);
}
}
int fNumberOfImportsAdded = importsAdded.size();
int fNumberOfImportsRemoved = oldSingleImports.size() + oldDemandImports.size();
Activator.log(new Status(IStatus.INFO, Activator.getID(), 0, "[imports added]: " + fNumberOfImportsAdded, null));
Activator.log(new Status(IStatus.INFO, Activator.getID(), 0, "[imports removed]: " + fNumberOfImportsRemoved, null));
}
private static boolean isDebug() {
return Boolean.parseBoolean(Platform.getDebugOption(Activator.getID() + "/debug/organizeImports"));//$NON-NLS-1$
}
@SuppressWarnings("unchecked")
private final static HashMap<String, String> createCompilerOptions(String targetLevel) {
HashMap<String, String> result = new HashMap<String, String>(JavaCore.getOptions());
result.put(JavaCore.COMPILER_COMPLIANCE, targetLevel);
result.put(JavaCore.COMPILER_SOURCE, targetLevel);
result.put(JavaCore.COMPILER_CODEGEN_TARGET_PLATFORM, targetLevel);
return result;
}
}