/** * Copyright (C) 2005 - 2015 Eric Van Dewoestine * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.eclim.plugin.jdt.command.include; import java.util.ArrayList; import java.util.Collections; import org.eclim.annotation.Command; import org.eclim.command.CommandLine; import org.eclim.command.Options; import org.eclim.plugin.core.command.AbstractCommand; import org.eclim.plugin.core.util.ProjectUtils; import org.eclim.plugin.jdt.util.JavaUtils; import org.eclim.util.file.Position; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ImportDeclaration; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.rewrite.ImportRewrite; import org.eclipse.jdt.core.dom.rewrite.ImportRewrite.ImportRewriteContext; import org.eclipse.jdt.core.search.TypeNameMatch; import org.eclipse.jdt.internal.corext.codemanipulation.AddImportsOperation; import org.eclipse.jdt.internal.corext.codemanipulation.CodeGenerationMessages; import org.eclipse.jdt.internal.corext.codemanipulation.ContextSensitiveImportRewriteContext; import org.eclipse.jdt.internal.corext.codemanipulation.StubUtility; import org.eclipse.jdt.internal.corext.util.JavaModelUtil; import org.eclipse.jdt.ui.SharedASTProvider; import org.eclipse.text.edits.InsertEdit; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.TextEdit; /** * Command to add an import to a java source file. * * @author Eric Van Dewoestine */ @Command( name = "java_import", options = "REQUIRED p project ARG," + "REQUIRED f file ARG," + "REQUIRED o offset ARG," + "REQUIRED e encoding ARG," + "OPTIONAL t type ARG" ) public class ImportCommand extends AbstractCommand { @Override public Object execute(CommandLine commandLine) throws Exception { String file = commandLine.getValue(Options.FILE_OPTION); String projectName = commandLine.getValue(Options.PROJECT_OPTION); String type = commandLine.getValue(Options.TYPE_OPTION); int offset = getOffset(commandLine); ICompilationUnit src = JavaUtils.getCompilationUnit(projectName, file); IProject project = src.getJavaProject().getProject(); TextEdit edits = null; int oldLength = src.getBuffer().getLength(); if (type != null){ CompilationUnit astRoot = SharedASTProvider .getAST(src, SharedASTProvider.WAIT_YES, null); edits = new MultiTextEdit(); ImportRewrite importRewrite = StubUtility.createImportRewrite(astRoot, true); ImportRewriteContext context = new ContextSensitiveImportRewriteContext( astRoot, offset, importRewrite); String res = importRewrite.addImport(type, context); if (type.equals(res)){ return CodeGenerationMessages.AddImportsOperation_error_importclash; } TextEdit rewrite = importRewrite.rewriteImports(null); edits.addChild(rewrite); JavaModelUtil.applyEdit(src, edits, true, null); }else{ ChooseImport query = new ChooseImport(project, type); try{ AddImportsOperation op = new AddImportsOperation( src, offset, 1, query, true /* save */, true /* apply */); op.run(null); edits = op.getResultingEdit(); if (edits == null){ IStatus status = op.getStatus(); return status.getSeverity() != IStatus.OK ? status.getMessage() : null; } }catch(OperationCanceledException oce){ return query.choices; } } TextEdit groupingEdit = importGroupingEdit(src, edits.getOffset() + 1); if (groupingEdit != null){ JavaModelUtil.applyEdit(src, groupingEdit, true, null); } if (src.isWorkingCopy()) { src.commitWorkingCopy(false, null); } if (edits.getOffset() < offset){ offset += src.getBuffer().getLength() - oldLength; } return Position.fromOffset( ProjectUtils.getFilePath(projectName, file), null, offset, 0); } private TextEdit importGroupingEdit(ICompilationUnit src, int offset) throws Exception { int separationLevel = getPreferences().getIntValue( src.getJavaProject().getProject(), "org.eclim.java.import.package_separation_level"); String lineDelim = src.findRecommendedLineSeparator(); CompilationUnit astRoot = SharedASTProvider .getAST(src, SharedASTProvider.WAIT_YES, null); ASTNode node = NodeFinder.perform(astRoot, offset, 1); MultiTextEdit edit = new MultiTextEdit(); if (node != null && node.getNodeType() == ASTNode.IMPORT_DECLARATION){ ImportDeclaration imprt = (ImportDeclaration)node; ASTNode next = getNext(astRoot, node, lineDelim); while (next != null && next.getNodeType() == ASTNode.IMPORT_DECLARATION){ ImportDeclaration nextImprt = (ImportDeclaration)next; if (!ImportUtils.importsInSameGroup(separationLevel, imprt, nextImprt)){ int end = imprt.getStartPosition() + imprt.getLength() + lineDelim.length(); addLineDelim(astRoot, edit, end, lineDelim); } next = getNext(astRoot, next, lineDelim); imprt = nextImprt; } // reset imprt ref back to the one we are importing. imprt = (ImportDeclaration)node; ASTNode prev = getPrev(astRoot, node, lineDelim); if (prev != null && prev.getNodeType() == ASTNode.IMPORT_DECLARATION){ ImportDeclaration prevImprt = (ImportDeclaration)prev; if (!ImportUtils.importsInSameGroup(separationLevel, imprt, prevImprt)){ int end = prev.getStartPosition() + prev.getLength() + lineDelim.length(); addLineDelim(astRoot, edit, end, lineDelim); } } } return edit.getChildrenSize() > 0 ? edit : null; } private ASTNode getNext(CompilationUnit astRoot, ASTNode node, String lineDelim) { int offset = node.getStartPosition() + node.getLength() + lineDelim.length(); ASTNode next = NodeFinder.perform(astRoot, offset, 1); // if the offset is on a blank line, then the compilation unit will be // returned as the node, so advance 1 and try again. while (next != null && next.getNodeType() == ASTNode.COMPILATION_UNIT){ next = NodeFinder.perform(astRoot, ++offset, 1); } return next; } private ASTNode getPrev(CompilationUnit astRoot, ASTNode node, String lineDelim) { int offset = node.getStartPosition() - (lineDelim.length() + 1); return NodeFinder.perform(astRoot, offset, 1); } /** * Add an InsertEdit to add a new blank line if one doesn't already exist. * * @param astRoot The CompilationUnit to modify. * @param edit The MultiTextEdit to add the new InsertEdit to. * @param offset The offset to add the new blank line at. * @param lineDelim The line delimiter to use. */ private void addLineDelim( CompilationUnit astRoot, MultiTextEdit edit, int offset, String lineDelim) { ASTNode node = NodeFinder.perform(astRoot, offset, 1); if (node != null && node.getNodeType() == ASTNode.IMPORT_DECLARATION){ edit.addChild(new InsertEdit(offset, lineDelim)); } } private class ChooseImport implements AddImportsOperation.IChooseImportQuery { public ArrayList<String> choices; private IProject project; private String type; public ChooseImport(IProject project, String type) { this.project = project; this.type = type; } @Override public TypeNameMatch chooseImport(TypeNameMatch[] choices, String name) { if (type != null){ for (TypeNameMatch match : choices){ if (type.equals(match.getFullyQualifiedName())){ return match; } } } if (this.choices == null){ // just in case to prevent infinite recursive loop try{ this.choices = new ArrayList<String>(choices.length); for (TypeNameMatch match : choices){ String fqn = match.getFullyQualifiedName(); if (!ImportUtils.isImportExcluded(project, fqn) && !this.choices.contains(fqn)) { this.choices.add(fqn); } } if (this.choices.size() == 1){ type = this.choices.get(0); return chooseImport(choices, name); } Collections.sort(this.choices); }catch(Exception e){ throw new RuntimeException(e); } } return null; } } }