/** * Copyright (c) 2010 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: * Marcel Bruch - initial API and implementation. */ package org.eclipse.recommenders.completion.rcp.processable; import static com.google.common.base.Preconditions.checkNotNull; import static org.apache.commons.lang3.ArrayUtils.subarray; import static org.eclipse.jdt.core.CompletionProposal.*; import static org.eclipse.recommenders.internal.completion.rcp.l10n.LogMessages.ERROR_EXCEPTION_DURING_CODE_COMPLETION; import static org.eclipse.recommenders.utils.Checks.cast; import static org.eclipse.recommenders.utils.Logs.log; import java.lang.reflect.Field; import java.util.List; import java.util.Map; import org.apache.commons.lang3.ArrayUtils; import org.eclipse.jdt.core.CompletionContext; import org.eclipse.jdt.core.CompletionProposal; import org.eclipse.jdt.core.CompletionRequestor; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.internal.codeassist.InternalCompletionContext; import org.eclipse.jdt.internal.ui.text.java.FillArgumentNamesCompletionProposalCollector; import org.eclipse.jdt.ui.PreferenceConstants; import org.eclipse.jdt.ui.text.java.CompletionProposalCollector; import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal; import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; import org.eclipse.jface.text.ITextViewer; import org.eclipse.recommenders.internal.completion.rcp.Constants; import org.eclipse.recommenders.internal.completion.rcp.l10n.LogMessages; import org.eclipse.recommenders.utils.Logs; import org.eclipse.recommenders.utils.Reflections; import org.eclipse.swt.graphics.Point; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; @SuppressWarnings("restriction") public class ProposalCollectingCompletionRequestor extends CompletionRequestor { private static final Field F_PROPOSALS = Reflections .getDeclaredField(true, CompletionProposalCollector.class, "fJavaProposals").orNull(); //$NON-NLS-1$ private final Map<IJavaCompletionProposal, CompletionProposal> proposals = Maps.newIdentityHashMap(); private JavaContentAssistInvocationContext jdtuiContext; private CompletionProposalCollector collector; private InternalCompletionContext compilerContext; public ProposalCollectingCompletionRequestor(final JavaContentAssistInvocationContext ctx) { super(false); checkNotNull(ctx); jdtuiContext = ctx; initalizeCollector(); } private void initalizeCollector() { if (shouldFillArgumentNames()) { collector = new FillArgumentNamesCompletionProposalCollector(jdtuiContext); } else { collector = new CompletionProposalCollector(jdtuiContext.getCompilationUnit(), false); } configureInterestedProposalTypes(); adjustProposalReplacementLength(); } /** * Configures the delegate collector by calling a series of setters. * * Important: For this to work, this {@code CompletionRequestor} must then delegate all corresponding getters to * {@code collector}. */ private void configureInterestedProposalTypes() { String[] excludes = PreferenceConstants.getExcludedCompletionProposalCategories(); if (doesJdtProposeTypesOnly(excludes)) { setIgnoreNonTypes(true); } else { setIgnoreNonTypes(false); } if (doesJdtProposeNonTypesOnly(excludes)) { setIgnoreTypes(true); } else { setIgnoreTypes(false); } collector.setIgnored(JAVADOC_BLOCK_TAG, false); collector.setIgnored(JAVADOC_FIELD_REF, false); collector.setIgnored(JAVADOC_INLINE_TAG, false); collector.setIgnored(JAVADOC_METHOD_REF, false); collector.setIgnored(JAVADOC_PARAM_REF, false); collector.setIgnored(JAVADOC_TYPE_REF, false); collector.setIgnored(JAVADOC_VALUE_REF, false); collector.setAllowsRequiredProposals(FIELD_REF, TYPE_REF, true); collector.setAllowsRequiredProposals(FIELD_REF, TYPE_IMPORT, true); collector.setAllowsRequiredProposals(FIELD_REF, FIELD_IMPORT, true); collector.setAllowsRequiredProposals(METHOD_REF, TYPE_REF, true); collector.setAllowsRequiredProposals(METHOD_REF, TYPE_IMPORT, true); collector.setAllowsRequiredProposals(METHOD_REF, METHOD_IMPORT, true); collector.setAllowsRequiredProposals(CONSTRUCTOR_INVOCATION, TYPE_REF, true); collector.setAllowsRequiredProposals(ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION, TYPE_REF, true); collector.setAllowsRequiredProposals(ANONYMOUS_CLASS_DECLARATION, TYPE_REF, true); collector.setAllowsRequiredProposals(TYPE_REF, TYPE_REF, true); collector.setFavoriteReferences(getFavoriteStaticMembers()); collector.setRequireExtendedContext(true); } private boolean doesJdtProposeTypesOnly(String[] excludes) { return !ArrayUtils.contains(excludes, Constants.JDT_TYPE_CATEGORY) && ArrayUtils.contains(excludes, Constants.JDT_ALL_CATEGORY) && ArrayUtils.contains(excludes, Constants.JDT_NON_TYPE_CATEGORY); } private boolean doesJdtProposeNonTypesOnly(String[] excludes) { return !ArrayUtils.contains(excludes, Constants.JDT_NON_TYPE_CATEGORY) && ArrayUtils.contains(excludes, Constants.JDT_ALL_CATEGORY) && ArrayUtils.contains(excludes, Constants.JDT_TYPE_CATEGORY); } private void setIgnoreNonTypes(boolean ignored) { collector.setIgnored(ANNOTATION_ATTRIBUTE_REF, ignored); collector.setIgnored(ANONYMOUS_CLASS_DECLARATION, ignored); collector.setIgnored(ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION, ignored); collector.setIgnored(FIELD_REF, ignored); collector.setIgnored(FIELD_REF_WITH_CASTED_RECEIVER, ignored); collector.setIgnored(KEYWORD, ignored); collector.setIgnored(LABEL_REF, ignored); collector.setIgnored(LOCAL_VARIABLE_REF, ignored); collector.setIgnored(METHOD_DECLARATION, ignored); collector.setIgnored(METHOD_NAME_REFERENCE, ignored); collector.setIgnored(METHOD_REF, ignored); collector.setIgnored(CONSTRUCTOR_INVOCATION, ignored); collector.setIgnored(METHOD_REF_WITH_CASTED_RECEIVER, ignored); collector.setIgnored(PACKAGE_REF, ignored); collector.setIgnored(POTENTIAL_METHOD_DECLARATION, ignored); collector.setIgnored(VARIABLE_DECLARATION, ignored); } private void setIgnoreTypes(boolean ignored) { collector.setIgnored(TYPE_REF, ignored); } @Override public boolean isIgnored(final int completionProposalKind) { return collector.isIgnored(completionProposalKind); } @Override public boolean isAllowingRequiredProposals(final int proposalKind, final int requiredProposalKind) { return collector.isAllowingRequiredProposals(proposalKind, requiredProposalKind); } @Override public boolean isExtendedContextRequired() { return collector.isExtendedContextRequired(); } @Override public String[] getFavoriteReferences() { return collector.getFavoriteReferences(); } private void adjustProposalReplacementLength() { ITextViewer viewer = jdtuiContext.getViewer(); Point selection = viewer.getSelectedRange(); if (selection.y > 0) { collector.setReplacementLength(selection.y); } } @VisibleForTesting protected boolean shouldFillArgumentNames() { try { // when running a test suite this throws a NPE return PreferenceConstants.getPreferenceStore() .getBoolean(PreferenceConstants.CODEASSIST_FILL_ARGUMENT_NAMES); } catch (final Exception e) { return true; } } @Override public void acceptContext(final CompletionContext context) { compilerContext = cast(context); collector.acceptContext(context); } private String[] getFavoriteStaticMembers() { final String serializedFavorites = PreferenceConstants.getPreferenceStore() .getString(PreferenceConstants.CODEASSIST_FAVORITE_STATIC_MEMBERS); if (serializedFavorites != null && serializedFavorites.length() > 0) { return serializedFavorites.split(";"); //$NON-NLS-1$ } return CharOperation.NO_STRINGS; } @Override public void accept(final CompletionProposal compilerProposal) { for (final IJavaCompletionProposal uiProposal : createJdtProposals(compilerProposal)) { proposals.put(uiProposal, compilerProposal); } } private IJavaCompletionProposal[] createJdtProposals(final CompletionProposal proposal) { if (F_PROPOSALS != null) { try { @SuppressWarnings("unchecked") List<IJavaCompletionProposal> list = (List<IJavaCompletionProposal>) F_PROPOSALS.get(collector); // call order (size, accept, size, get) matters. // First get the old amount of proposals. than add the new one. Then check how many new proposals // are actually added (it may be more than one). These new proposals are then returned: int oldSize = list.size(); collector.accept(proposal); int newSize = list.size(); List<IJavaCompletionProposal> res = list.subList(oldSize, newSize); return Iterables.toArray(res, IJavaCompletionProposal.class); } catch (Exception e) { // log and use the fallback mechanism log(ERROR_EXCEPTION_DURING_CODE_COMPLETION, e); } } // fallback if the above code fails (that's the old code). We may remove this later if we now it works reliably. // Error reporting will tell us. final int oldSize = collector.getJavaCompletionProposals().length; collector.accept(proposal); final IJavaCompletionProposal[] jdtProposals = collector.getJavaCompletionProposals(); return subarray(jdtProposals, oldSize, jdtProposals.length); } public void setReplacementLength(final int y) { collector.setReplacementLength(y); } public InternalCompletionContext getCoreContext() { return compilerContext; } public Map<IJavaCompletionProposal, CompletionProposal> getProposals() { return proposals; } @Override public void completionFailure(IProblem problem) { if (Constants.DEBUG) { Logs.log(LogMessages.ERROR_COMPLETION_FAILURE_DURING_DEBUG_MODE, problem.toString()); } } }