/* * 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.resources.ProjectScope; 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.core.runtime.preferences.DefaultScope; import org.eclipse.core.runtime.preferences.InstanceScope; 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.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; } /** * org.eclipse.gmf.common should not depend on org.eclipse.jdt.ui */ private static class PreferenceConstants { public static final String JDT_UI = "org.eclipse.jdt.ui"; //$NON-NLS-1$ public static final String ORGIMPORTS_IMPORTORDER= "org.eclipse.jdt.ui.importorder"; //$NON-NLS-1$ public static final String ORGIMPORTS_ONDEMANDTHRESHOLD= "org.eclipse.jdt.ui.ondemandthreshold"; //$NON-NLS-1$ public static final String ORGIMPORTS_STATIC_ONDEMANDTHRESHOLD= "org.eclipse.jdt.ui.staticondemandthreshold"; //$NON-NLS-1$ public static String getPreference(String key, IJavaProject project) { String val; if (project != null) { val= new ProjectScope(project.getProject()).getNode(JDT_UI).get(key, null); if (val != null) { return val; } } val= InstanceScope.INSTANCE.getNode(JDT_UI).get(key, null); if (val != null) { return val; } return DefaultScope.INSTANCE.getNode(JDT_UI).get(key, null); } } }