/** * Copyright (c) 2010, 2012 Darmstadt University of Technology. * 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: * Paul-Emmanuel Faidherbe - Completion generalization */ package org.eclipse.recommenders.completion.rcp.processable; import static org.apache.commons.lang3.StringUtils.startsWithAny; import static org.eclipse.recommenders.internal.completion.rcp.l10n.LogMessages.ERROR_UNEXPECTED_PROPOSAL_KIND; import static org.eclipse.recommenders.utils.Logs.log; import java.lang.reflect.Method; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.CompletionProposal; import org.eclipse.jdt.core.IField; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal; import org.eclipse.jdt.internal.ui.text.java.AnonymousTypeCompletionProposal; import org.eclipse.jdt.internal.ui.text.java.FilledArgumentNamesMethodProposal; import org.eclipse.jdt.internal.ui.text.java.GetterSetterCompletionProposal; import org.eclipse.jdt.internal.ui.text.java.JavaCompletionProposal; import org.eclipse.jdt.internal.ui.text.java.JavaFieldWithCastedReceiverCompletionProposal; import org.eclipse.jdt.internal.ui.text.java.JavaMethodCompletionProposal; import org.eclipse.jdt.internal.ui.text.java.LazyGenericTypeProposal; import org.eclipse.jdt.internal.ui.text.java.LazyJavaCompletionProposal; import org.eclipse.jdt.internal.ui.text.java.LazyJavaTypeCompletionProposal; import org.eclipse.jdt.internal.ui.text.java.LazyPackageCompletionProposal; import org.eclipse.jdt.internal.ui.text.java.MethodDeclarationCompletionProposal; import org.eclipse.jdt.internal.ui.text.java.OverrideCompletionProposal; import org.eclipse.jdt.internal.ui.text.java.ParameterGuessingProposal; import org.eclipse.jdt.internal.ui.text.java.ProposalInfo; import org.eclipse.jdt.internal.ui.text.javadoc.JavadocInlineTagCompletionProposal; import org.eclipse.jdt.internal.ui.text.javadoc.JavadocLinkTypeCompletionProposal; import org.eclipse.jdt.ui.PreferenceConstants; import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; import org.eclipse.recommenders.internal.completion.rcp.l10n.LogMessages; import org.eclipse.recommenders.utils.Checks; import org.eclipse.recommenders.utils.Logs; import org.eclipse.recommenders.utils.Reflections; import org.eclipse.recommenders.utils.Throws; import com.google.common.base.Throwables; /** * Creates more flexible completion proposals from original proposals */ @SuppressWarnings("restriction") public class ProcessableProposalFactory implements IProcessableProposalFactory { private static final String ORG_ECLIPSE_OBJECTTEAMS_OTDT = "org.eclipse.objectteams.otdt"; private static Class<JavaMethodCompletionProposal> javaMethodCompletionProposalClass; private static Class<JavaFieldWithCastedReceiverCompletionProposal> javaFieldWithCastedReceiverCompletionProposalClass; private static Class<OverrideCompletionProposal> overrideCompletionProposalClass; private static Class<AnonymousTypeCompletionProposal> anonymousTypeCompletionProposalClass; private static Class<JavaCompletionProposal> javaCompletionProposalClass; private static Class<LazyGenericTypeProposal> lazyGenericTypeProposalClass; private static Class<LazyJavaTypeCompletionProposal> lazyJavaTypeCompletionProposalClass; private static Class<LazyJavaCompletionProposal> lazyJavaCompletionProposaClass; private static Class<FilledArgumentNamesMethodProposal> filledArgumentNamesMethodProposalClass; private static Class<ParameterGuessingProposal> parameterGuessingProposalClass; private static Class<MethodDeclarationCompletionProposal> methodDeclarationCompletionProposalClass; private static Class<LazyPackageCompletionProposal> lazyPackageCompletionProposalClass; private static Class<GetterSetterCompletionProposal> getterSetterCompletionProposalClass; private static Class<JavadocLinkTypeCompletionProposal> javadocLinkTypeCompletionProposalClass; private static Class<JavadocInlineTagCompletionProposal> javadocInlineTagCompletionProposalClass; private static Method proposalInfoMethod = Reflections .getDeclaredMethod(true, AbstractJavaCompletionProposal.class, "getProposalInfo").orNull(); //$NON-NLS-1$ static { // No all versions of JDT offer all kinds of CompletionProposal. Probe using reflection. try { javaMethodCompletionProposalClass = JavaMethodCompletionProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { javaFieldWithCastedReceiverCompletionProposalClass = JavaFieldWithCastedReceiverCompletionProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { overrideCompletionProposalClass = OverrideCompletionProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { anonymousTypeCompletionProposalClass = AnonymousTypeCompletionProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { javaCompletionProposalClass = JavaCompletionProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { lazyGenericTypeProposalClass = LazyGenericTypeProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { lazyJavaCompletionProposaClass = LazyJavaCompletionProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { lazyJavaTypeCompletionProposalClass = LazyJavaTypeCompletionProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { filledArgumentNamesMethodProposalClass = FilledArgumentNamesMethodProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { parameterGuessingProposalClass = ParameterGuessingProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { methodDeclarationCompletionProposalClass = MethodDeclarationCompletionProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { methodDeclarationCompletionProposalClass = MethodDeclarationCompletionProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { lazyPackageCompletionProposalClass = LazyPackageCompletionProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { getterSetterCompletionProposalClass = GetterSetterCompletionProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { javadocLinkTypeCompletionProposalClass = JavadocLinkTypeCompletionProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } try { javadocInlineTagCompletionProposalClass = JavadocInlineTagCompletionProposal.class; } catch (NoClassDefFoundError e) { logWarning(e); } } private static void logWarning(NoClassDefFoundError e) { Logs.log(LogMessages.ERROR_FAILED_TO_LOAD_COMPLETION_PROPOSAL_CLASS, e); } public ProcessableProposalFactory() { } public static IJavaCompletionProposal create(CompletionProposal coreProposal, IJavaCompletionProposal uiProposal, JavaContentAssistInvocationContext context, IProcessableProposalFactory factory) { final Class<? extends IJavaCompletionProposal> c = uiProposal.getClass(); // TODO the handling of setProposalInfo should be improved soon. try { if (javaMethodCompletionProposalClass == c) { IProcessableProposal res = factory.newJavaMethodCompletionProposal(coreProposal, (JavaMethodCompletionProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } else if (javaFieldWithCastedReceiverCompletionProposalClass == c) { IProcessableProposal res = factory.newJavaFieldWithCastedReceiverCompletionProposal(coreProposal, (JavaFieldWithCastedReceiverCompletionProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } else if (overrideCompletionProposalClass == c) { IProcessableProposal res = factory.newOverrideCompletionProposal(coreProposal, (OverrideCompletionProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } else if (anonymousTypeCompletionProposalClass == c) { IProcessableProposal res = factory.newAnonymousTypeCompletionProposal(coreProposal, (AnonymousTypeCompletionProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } else if (javaCompletionProposalClass == c) { IProcessableProposal res = factory.newJavaCompletionProposal(coreProposal, (JavaCompletionProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } else if (lazyGenericTypeProposalClass == c) { IProcessableProposal res = factory.newLazyGenericTypeProposal(coreProposal, (LazyGenericTypeProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } else if (lazyJavaTypeCompletionProposalClass == c) { IProcessableProposal res = factory.newLazyJavaTypeCompletionProposal(coreProposal, (LazyJavaTypeCompletionProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } else if (filledArgumentNamesMethodProposalClass == c) { IProcessableProposal res = factory.newFilledArgumentNamesMethodProposal(coreProposal, (FilledArgumentNamesMethodProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } else if (parameterGuessingProposalClass == c) { IProcessableProposal res = factory.newParameterGuessingProposal(coreProposal, (ParameterGuessingProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } else if (methodDeclarationCompletionProposalClass == c) { IProcessableProposal res = factory.newMethodDeclarationCompletionProposal(coreProposal, (MethodDeclarationCompletionProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } else if (lazyPackageCompletionProposalClass == c) { IProcessableProposal res = factory.newLazyPackageCompletionProposal(coreProposal, (LazyPackageCompletionProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } else if (getterSetterCompletionProposalClass == c) { IProcessableProposal res = factory.newGetterSetterCompletionProposal(coreProposal, (GetterSetterCompletionProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } else if (javadocLinkTypeCompletionProposalClass == c) { IProcessableProposal res = factory.newJavadocLinkTypeCompletionProposal(coreProposal, (JavadocLinkTypeCompletionProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } else if (javadocInlineTagCompletionProposalClass == c) { IProcessableProposal res = factory.newJavadocInlineTagCompletionProposal(coreProposal, (JavadocInlineTagCompletionProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } else if (lazyJavaCompletionProposaClass == c) { IProcessableProposal res = factory.newLazyJavaCompletionProposa(coreProposal, (LazyJavaCompletionProposal) uiProposal, context); setProposalInfo(res, uiProposal); return res; } // Some plug-ins are known to add their own proposals to JDT's Java editor. // While we cannot make arbitrary proposals processable, this is likely to be fine and we should not // complain about such proposals. // See <https://bugs.eclipse.org/bugs/show_bug.cgi?id=497180> if (isWhitelisted(uiProposal, ORG_ECLIPSE_OBJECTTEAMS_OTDT)) { return uiProposal; } // log error and return the fallback proposal log(ERROR_UNEXPECTED_PROPOSAL_KIND, c, uiProposal.getDisplayString()); return uiProposal; } catch (final Exception e) { log(LogMessages.ERROR_FAILED_TO_WRAP_JDT_PROPOSAL, e, c, uiProposal.getDisplayString()); return uiProposal; } } private static boolean isWhitelisted(IJavaCompletionProposal uiProposal, String whitelistedPackage) { String uiProposalPackage = uiProposal.getClass().getPackage().getName(); if (uiProposalPackage.startsWith(whitelistedPackage)) { if (uiProposalPackage.length() == whitelistedPackage.length()) { return true; // in whitelisted package } else if (uiProposalPackage.charAt(whitelistedPackage.length()) == '.') { return true; // in subpackage of whitelisted package } else { return false; } } else { return false; } } private static void setProposalInfo(IProcessableProposal crProposal, IJavaCompletionProposal uiProposal) { // XXX this method should under no circumstances throw any exception if (Checks.anyIsNull(proposalInfoMethod, crProposal, uiProposal)) { return; } try { ProposalInfo info = (ProposalInfo) proposalInfoMethod.invoke(uiProposal); crProposal.setProposalInfo(info); } catch (Exception e) { Logs.log(LogMessages.ERROR_FAILED_TO_SET_PROPOSAL_INFO, e, crProposal); } } @Override public IProcessableProposal newLazyGenericTypeProposal(CompletionProposal coreProposal, LazyGenericTypeProposal uiProposal, JavaContentAssistInvocationContext context) { return postConstruct(new ProcessableLazyGenericTypeProposal(coreProposal, context), uiProposal); } @Override public IProcessableProposal newFilledArgumentNamesMethodProposal(CompletionProposal coreProposal, FilledArgumentNamesMethodProposal uiProposal, JavaContentAssistInvocationContext context) { return postConstruct(new ProcessableFilledArgumentNamesMethodProposal(coreProposal, context), uiProposal); } @Override public IProcessableProposal newParameterGuessingProposal(CompletionProposal coreProposal, ParameterGuessingProposal uiProposal, JavaContentAssistInvocationContext context) { final boolean fillBestGuess = shouldFillArgumentNames(); return postConstruct(new ProcessableParameterGuessingProposal(coreProposal, context, fillBestGuess), uiProposal); } private boolean shouldFillArgumentNames() { try { final boolean res = PreferenceConstants.getPreferenceStore() .getBoolean(PreferenceConstants.CODEASSIST_GUESS_METHOD_ARGUMENTS); return res; } catch (final Exception e) { } return false; } @Override public IProcessableProposal newAnonymousTypeCompletionProposal(CompletionProposal coreProposal, AnonymousTypeCompletionProposal uiProposal, JavaContentAssistInvocationContext context) throws JavaModelException { return postConstruct(new ProcessableAnonymousTypeCompletionProposal(coreProposal, uiProposal, context), uiProposal); } @Override public IProcessableProposal newJavaFieldWithCastedReceiverCompletionProposal(CompletionProposal coreProposal, JavaFieldWithCastedReceiverCompletionProposal uiProposal, JavaContentAssistInvocationContext context) { try { return postConstruct( new ProcessableJavaFieldWithCastedReceiverCompletionProposal(coreProposal, uiProposal, context), uiProposal); } catch (JavaModelException e) { throw Throwables.propagate(e); } } @Override public IProcessableProposal newJavaCompletionProposal(CompletionProposal coreProposal, JavaCompletionProposal uiProposal, JavaContentAssistInvocationContext context) { try { return postConstruct(new ProcessableJavaCompletionProposal(coreProposal, uiProposal, context), uiProposal); } catch (JavaModelException e) { throw Throwables.propagate(e); } } @Override public IProcessableProposal newJavadocLinkTypeCompletionProposal(CompletionProposal coreProposal, JavadocLinkTypeCompletionProposal uiProposal, JavaContentAssistInvocationContext context) { return postConstruct(new ProcessableJavadocLinkTypeCompletionProposal(coreProposal, context), uiProposal); } @Override public IProcessableProposal newJavadocInlineTagCompletionProposal(CompletionProposal coreProposal, JavadocInlineTagCompletionProposal uiProposal, JavaContentAssistInvocationContext context) { return postConstruct(new ProcessableJavadocInlineTagCompletionProposal(coreProposal, context), uiProposal); } @Override public IProcessableProposal newJavaMethodCompletionProposal(CompletionProposal coreProposal, JavaMethodCompletionProposal uiProposal, JavaContentAssistInvocationContext context) { return postConstruct(new ProcessableJavaMethodCompletionProposal(coreProposal, context), uiProposal); } @Override public IProcessableProposal newLazyJavaTypeCompletionProposal(CompletionProposal coreProposal, LazyJavaTypeCompletionProposal uiProposal, JavaContentAssistInvocationContext context) { return postConstruct(new ProcessableLazyJavaTypeCompletionProposal(coreProposal, context), uiProposal); } @Override public IProcessableProposal newOverrideCompletionProposal(CompletionProposal coreProposal, OverrideCompletionProposal uiProposal, JavaContentAssistInvocationContext context) { return postConstruct(new ProcessableOverrideCompletionProposal(coreProposal, uiProposal, context), uiProposal); } @Override public IProcessableProposal newMethodDeclarationCompletionProposal(CompletionProposal coreProposal, MethodDeclarationCompletionProposal uiProposal, JavaContentAssistInvocationContext context) { try { IJavaElement enclosingElement = context.getCoreContext().getEnclosingElement(); IType type = null; if (enclosingElement instanceof IType) { type = (IType) enclosingElement; } else if (enclosingElement instanceof IMember) { type = ((IMember) enclosingElement).getDeclaringType(); } if (type == null) { throw Throws.throwIllegalArgumentException("No type found for enclosing element %s", enclosingElement); //$NON-NLS-1$ } return postConstruct(ProcessableMethodDeclarationCompletionProposal.newProposal(coreProposal, type, uiProposal.getRelevance()), uiProposal); } catch (CoreException e) { throw Throwables.propagate(e); } } @Override public IProcessableProposal newGetterSetterCompletionProposal(CompletionProposal coreProposal, GetterSetterCompletionProposal uiProposal, JavaContentAssistInvocationContext context) { try { IField field = (IField) uiProposal.getJavaElement(); return postConstruct( new ProcessableGetterSetterCompletionProposal(coreProposal, field, startsWithAny(uiProposal.getDisplayString(), "get", "is"), uiProposal.getRelevance()), //$NON-NLS-1$ //$NON-NLS-2$ uiProposal); } catch (Exception e) { throw Throwables.propagate(e); } } @Override public IProcessableProposal newLazyPackageCompletionProposal(CompletionProposal coreProposal, LazyPackageCompletionProposal uiProposal, JavaContentAssistInvocationContext context) { return postConstruct(new ProcessableLazyPackageCompletionProposal(coreProposal, context), uiProposal); } @Override public IProcessableProposal newLazyJavaCompletionProposa(CompletionProposal coreProposal, LazyJavaCompletionProposal uiProposal, JavaContentAssistInvocationContext context) { return postConstruct(new ProcessableLazyJavaCompletionProposal(coreProposal, context), uiProposal); } /** * {@link AbstractJavaCompletionProposal#setTriggerCharacters(char[])} is called by * {@link org.eclipse.jdt.ui.text.java.CompletionProposalCollector} after a new completion proposal is created. We * must copy the trigger characters from the originalProposal to the processableProposal in order to mimic the JDT * behavior. * * @see <a href="https://bugs.eclipse.org/bugs/show_bug.cgi?id=471386">Bug 471386</a> */ protected <T extends AbstractJavaCompletionProposal & IProcessableProposal> T postConstruct(T processableProposal, AbstractJavaCompletionProposal originalProposal) { processableProposal.setProposalProcessorManager(new ProposalProcessorManager(processableProposal)); processableProposal.setTriggerCharacters(originalProposal.getTriggerCharacters()); return processableProposal; } }