/******************************************************************************* * Copyright (c) 2004, 2007 IBM Corporation and others. * 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 * *******************************************************************************/ package org.eclipse.dltk.core; import org.eclipse.core.runtime.Assert; import org.eclipse.dltk.codeassist.RelevanceConstants; import org.eclipse.dltk.compiler.problem.IProblem; /** * Abstract base class for a completion requestor which is passed completion * proposals as they are generated in response to a code assist request. * <p> * This class is intended to be subclassed by clients. * </p> * <p> * The code assist engine normally invokes methods on completion requestors in * the following sequence: * * <pre> * requestor.beginReporting(); * requestor.acceptContext(context); * requestor.accept(proposal_1); * requestor.accept(proposal_2); * ... * requestor.endReporting(); * </pre> * * If, however, the engine is unable to offer completion proposals for whatever * reason, <code>completionFailure</code> is called with a problem object * describing why completions were unavailable. In this case, the sequence of * calls is: * * <pre> * requestor.beginReporting(); * requestor.acceptContext(context); * requestor.completionFailure(problem); * requestor.endReporting(); * </pre> * * In either case, the bracketing <code>beginReporting</code> * <code>endReporting</code> calls are always made as well as * <code>acceptContext</code> call. * </p> * <p> * The class was introduced in 3.0 as a more evolvable replacement for the * <code>ICompletionRequestor</code> interface. * </p> * * @see ICodeAssist * */ public abstract class CompletionRequestor { /** * The set of CompletionProposal kinds that this requestor ignores; * <code>0</code> means the set is empty. 1 << completionProposalKind */ private int ignoreSet = 0; /** * Creates a new completion requestor. The requestor is interested in all * kinds of completion proposals; none will be ignored. */ public CompletionRequestor() { // do nothing } public static final int ALL = 1 << 31; private static final int ALL_BITSET = Integer.MAX_VALUE; /** * Returns whether the given kind of completion proposal is ignored. * * @param completionProposalKind * one of the kind constants declared on * <code>CompletionProposal</code> or {@link #ALL} * @return <code>true</code> if the given kind of completion proposal is * ignored by this requestor, and <code>false</code> if it is of * interest * @see #setIgnored(int, boolean) * @see CompletionProposal#getKind() */ public final boolean isIgnored(int completionProposalKind) { if (completionProposalKind == ALL) { return this.ignoreSet == ALL_BITSET; } if (completionProposalKind < CompletionProposal.FIRST_KIND || completionProposalKind > CompletionProposal.LAST_KIND) { throw new IllegalArgumentException( "Unknown kind of completion proposal: " + completionProposalKind); //$NON-NLS-1$ } return 0 != (this.ignoreSet & (1 << completionProposalKind)); } /** * Sets whether the given kind of completion proposal is ignored. * * @param completionProposalKind * one of the kind constants declared on * <code>CompletionProposal</code> or {@link #ALL} * @param ignore * <code>true</code> if the given kind of completion proposal is * ignored by this requestor, and <code>false</code> if it is of * interest * @see #isIgnored(int) * @see CompletionProposal#getKind() */ public final void setIgnored(int completionProposalKind, boolean ignore) { if (completionProposalKind == ALL) { this.ignoreSet = ignore ? ALL_BITSET : 0; return; } if (completionProposalKind < CompletionProposal.FIRST_KIND || completionProposalKind > CompletionProposal.LAST_KIND) { throw new IllegalArgumentException( "Unknown kind of completion proposal: " + completionProposalKind); //$NON-NLS-1$ } if (ignore) { this.ignoreSet |= (1 << completionProposalKind); } else { this.ignoreSet &= ~(1 << completionProposalKind); } } /** * Pro forma notification sent before reporting a batch of completion * proposals. * <p> * The default implementation of this method does nothing. Clients may * override. * </p> */ public void beginReporting() { // do nothing } /** * Pro forma notification sent after reporting a batch of completion * proposals. * <p> * The default implementation of this method does nothing. Clients may * override. * </p> */ public void endReporting() { // do nothing } /** * Notification of failure to produce any completions. The problem object * explains what prevented completing. * <p> * The default implementation of this method does nothing. Clients may * override to receive this kind of notice. * </p> * * @param problem * the problem object */ public void completionFailure(IProblem problem) { // default behavior is to ignore } /** * Proposes a completion. Has no effect if the kind of proposal is being * ignored by this requestor. Callers should consider checking * {@link #isIgnored(int)} before avoid creating proposal objects that would * only be ignored. * <p> * Similarly, implementers should check {@link #isIgnored(int) * isIgnored(proposal.getKind())} and ignore proposals that have been * declared as uninteresting. The proposal object passed is only valid for * the duration of completion operation. * * @param proposal * the completion proposal * @exception IllegalArgumentException * if the proposal is null */ public abstract void accept(CompletionProposal proposal); /** * Propose the context in which the completion occurs. * <p> * This method is called one and only one time before any call to * {@link #accept(CompletionProposal)}. The default implementation of this * method does nothing. Clients may override. * </p> * * @param context * the completion context * * */ public void acceptContext(CompletionContext context) { // do nothing } /** * Checks if the current call is made to display context information. * * @return */ public boolean isContextInformationMode() { return false; } /** * Interface for the filtering out or changing the relevance of the * completion proposals. * * @since 4.1 */ public static interface CompletionProposalFilter { int DEFAULT = 0; int IGNORE = -1000; int DISCOURAGED = -50; /** * Evaluates the relevance of specified proposal. Possible return values * are: * <ul> * <li>{@link #DEFAULT} to continue without any changes * <li>{@link #IGNORE} to skip the proposal * <li>{@link #DISCOURAGED} to mark the proposal as * <em>not recommended</em> * <li>constants from {@link RelevanceConstants} to increase the * relevance of the proposal * </ul> */ int evaluate(CompletionProposal proposal); } private CompletionProposalFilter[] filters; /** * Adds the given filter to this requestor. * * @since 4.1 */ public void addFilter(CompletionProposalFilter filter) { Assert.isNotNull(filter); if (filters == null) { filters = new CompletionProposalFilter[] { filter }; } else { final CompletionProposalFilter[] newFilters = new CompletionProposalFilter[filters.length + 1]; System.arraycopy(filters, 0, newFilters, 0, filters.length); newFilters[filters.length] = filter; filters = newFilters; } } /** * Returns the result of filtering for the given completion proposal. * * @see #addFilter(CompletionProposalFilter) * @since 4.1 */ protected int evaluateFilters(CompletionProposal completionProposal) { int result = CompletionProposalFilter.DEFAULT; if (filters != null) { try { for (CompletionProposalFilter filter : filters) { int value = filter.evaluate(completionProposal); if (value == CompletionProposalFilter.IGNORE) { return value; } if (value > 0 && value > result || value < 0 && value < result) { result = value; } } } catch (RuntimeException e) { DLTKCore.error( "Error while evaluating CompletionProposalFilter, continue without filters", e); filters = null; result = CompletionProposalFilter.DEFAULT; } } return result; } }