/** * Copyright (C) 2005 - 2016 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.complete; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; import org.eclim.annotation.Command; import org.eclim.command.CommandLine; import org.eclim.logging.Logger; import org.eclim.plugin.core.command.complete.AbstractCodeCompleteCommand; import org.eclim.plugin.core.command.complete.CodeCompleteResult; import org.eclim.plugin.jdt.util.JavaUtils; import org.eclipse.jdt.core.CompletionProposal; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal; import org.eclipse.jdt.internal.ui.text.java.JavaCompletionProposal; import org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal; import org.eclipse.jdt.internal.ui.text.java.ProposalInfo; import org.eclipse.jdt.internal.ui.viewsupport.JavaElementLinks; import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; /** * Command to handle java code completion requests. * * @author Eric Van Dewoestine */ @Command( name = "java_complete", options = "REQUIRED p project ARG," + "REQUIRED f file ARG," + "REQUIRED o offset ARG," + "REQUIRED e encoding ARG," + "REQUIRED l layout ARG" ) public class CodeCompleteCommand extends AbstractCodeCompleteCommand { private static final Logger logger = Logger.getLogger(CodeCompleteCommand.class); protected static final Comparator<CodeCompleteResult> COMPLETION_COMPARATOR = new CompletionComparator(); private ThreadLocal<CompletionProposalCollector> collector = new ThreadLocal<CompletionProposalCollector>(); @Override protected Object getResponse(List<CodeCompleteResult> results) { CompletionProposalCollector collector = this.collector.get(); return new CodeCompleteResponse( results, collector.getError(), collector.getImports()); } @Override protected List<CodeCompleteResult> getCompletionResults( CommandLine commandLine, String project, String file, int offset) throws Exception { ICompilationUnit src = JavaUtils.getCompilationUnit(project, file); CompletionProposalCollector collector = new CompletionProposalCollector(src); src.codeComplete(offset, collector); IJavaCompletionProposal[] proposals = collector.getJavaCompletionProposals(); ArrayList<CodeCompleteResult> results = new ArrayList<CodeCompleteResult>(); for(IJavaCompletionProposal proposal : proposals){ results.add(createCompletionResult(proposal)); } Collections.sort(results, COMPLETION_COMPARATOR); this.collector.set(collector); return results; } /** * Create a CodeCompleteResult from the supplied CompletionProposal. * * @param proposal The proposal. * * @return The result. */ protected CodeCompleteResult createCompletionResult( IJavaCompletionProposal proposal) throws Exception { return createCompletionResult(proposal, false); } /** * Create a CodeCompleteResult from the supplied CompletionProposal. * * @param proposal * The proposal. * @param javaDocEnabled * Flag if a javaDoc URI should be added to the CodeCompleteResult. If * {@code javaDocEnabled} is true, the java doc URI corresponding to * the proposal will be added to the result. If {code javaDocEnabled} * is false the java doc URI is set to "". * @return The result. */ protected CodeCompleteResult createCompletionResult( IJavaCompletionProposal proposal, boolean javaDocEnabled) throws Exception { String completion = null; String menu = proposal.getDisplayString(); Integer offset = null; String javaDocURI = null; int kind = -1; if(proposal instanceof JavaCompletionProposal){ JavaCompletionProposal lazy = (JavaCompletionProposal)proposal; completion = lazy.getReplacementString(); offset = lazy.getReplacementOffset(); if(javaDocEnabled){ javaDocURI = getJavaDocLink(proposal); } }else if(proposal instanceof LazyJavaCompletionProposal){ LazyJavaCompletionProposal lazy = (LazyJavaCompletionProposal)proposal; completion = lazy.getReplacementString(); offset = lazy.getReplacementOffset(); Method getProposal = LazyJavaCompletionProposal.class .getDeclaredMethod("getProposal"); getProposal.setAccessible(true); CompletionProposal cproposal = (CompletionProposal)getProposal.invoke(lazy); if(javaDocEnabled){ javaDocURI = getJavaDocLink(proposal); } if (cproposal != null){ kind = cproposal.getKind(); } } switch(kind){ case CompletionProposal.METHOD_REF: int length = completion.length(); if (length == 0){ break; } if (completion.charAt(length - 1) == ';'){ completion = completion.substring(0, length - 1); length--; } // trim off the trailing paren if the method takes any arguments. // Note: using indexOf instead of lastIndexOf to account for groovy // completion menu text. if (menu.indexOf(')') > menu.indexOf('(') + 1 && completion.charAt(length - 1) == ')') { completion = completion.substring(0, completion.lastIndexOf('(') + 1); } break; case CompletionProposal.TYPE_REF: // trim off package info. int idx = completion.lastIndexOf('.'); if(idx != -1){ completion = completion.substring(idx + 1); } break; } if("class".equals(completion)){ kind = CompletionProposal.KEYWORD; } String type = ""; switch(kind){ case CompletionProposal.TYPE_REF: type = CodeCompleteResult.TYPE; break; case CompletionProposal.FIELD_REF: case CompletionProposal.LOCAL_VARIABLE_REF: type = CodeCompleteResult.VARIABLE; type = CodeCompleteResult.VARIABLE; break; case CompletionProposal.METHOD_REF: type = CodeCompleteResult.FUNCTION; break; case CompletionProposal.KEYWORD: type = CodeCompleteResult.KEYWORD; break; } // TODO: // hopefully Bram will take my advice to add lazy retrieval of // completion 'info' so that I can provide this text without the // overhead involved with retrieving it for every completion regardless // of whether the user ever views it. /*return new CodeCompleteResult( kind, completion, menu, proposal.getAdditionalProposalInfo());*/ return new CodeCompleteResult(completion, menu, menu, type, offset, javaDocURI); } private String getJavaDocLink(IJavaCompletionProposal proposal) { try { IJavaElement javaElement = getJavaElement(proposal); if (javaElement == null) { return ""; } return JavaElementLinks.createURI( JavaElementLinks.JAVADOC_SCHEME, javaElement); } catch (Exception e) { logger.error("Could not calculate the javaDoc link", e); return ""; } } /** * Gets the {@code IJavaElement} which is behind the {@code proposal}. If the * {@code proposal} does not contain an {@code IJavaElement} null will be * returned. * * @param proposal The proposal from which we want the IJavaElement * @return IJavaElement The IJavaElement which is behind the {@code proposal} * if there is one. * @throws NoSuchMethodException * @throws IllegalAccessException * @throws InvocationTargetException * @throws JavaModelException */ private IJavaElement getJavaElement(IJavaCompletionProposal proposal) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, JavaModelException { if (!(AbstractJavaCompletionProposal.class .isAssignableFrom(proposal.getClass()))) { // We do not throw an exception here since it may be that only some // elements cannot create a javaDocLink and not all of them. logger.error("The proposal " + proposal.toString() + " does not inherit from class AbstractJavaCompletionProposal," + " so a javaDoc link cannot be created."); return null; } Method getProposal = AbstractJavaCompletionProposal.class .getDeclaredMethod("getProposalInfo"); getProposal.setAccessible(true); ProposalInfo proposalInfo = (ProposalInfo)getProposal .invoke((AbstractJavaCompletionProposal) proposal); if (proposalInfo != null) { return proposalInfo.getJavaElement(); } return null; } }