blob: 77803d9c1459df651296e62dd73c23fe760cdf6d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006 Oracle Corporation.
* 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:
* Cameron Bateman/Oracle - initial API and implementation
*
********************************************************************************/
package org.eclipse.jst.jsf.core.internal.contentassist.el;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.eclipse.emf.edit.provider.ComposedAdapterFactory;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jst.jsf.common.internal.types.CompositeType;
import org.eclipse.jst.jsf.common.internal.types.IAssignable;
import org.eclipse.jst.jsf.context.resolver.structureddocument.IDOMContextResolver;
import org.eclipse.jst.jsf.context.resolver.structureddocument.IStructuredDocumentContextResolverFactory;
import org.eclipse.jst.jsf.context.resolver.structureddocument.ITaglibContextResolver;
import org.eclipse.jst.jsf.context.resolver.structureddocument.internal.ITextRegionContextResolver;
import org.eclipse.jst.jsf.context.structureddocument.IStructuredDocumentContext;
import org.eclipse.jst.jsf.context.symbol.IInstanceSymbol;
import org.eclipse.jst.jsf.context.symbol.IMethodSymbol;
import org.eclipse.jst.jsf.context.symbol.IObjectSymbol;
import org.eclipse.jst.jsf.context.symbol.ISymbol;
import org.eclipse.jst.jsf.context.symbol.provider.IContentProposalProvider;
import org.eclipse.jst.jsf.context.symbol.provider.ProposalCreationFactoryAdapter;
import org.eclipse.jst.jsf.context.symbol.provider.IContentProposalProvider.IProposalCreationFactory;
import org.eclipse.jst.jsf.designtime.resolver.ISymbolContextResolver;
import org.eclipse.jst.jsf.designtime.resolver.StructuredDocumentSymbolResolverFactory;
import org.eclipse.jst.jsf.metadataprocessors.MetaDataEnabledProcessingFactory;
import org.eclipse.jst.jsf.metadataprocessors.features.ELIsNotValidException;
import org.eclipse.jst.jsf.metadataprocessors.features.IValidELValues;
import org.eclipse.jst.jsp.core.internal.regions.DOMJSPRegionContexts;
import org.eclipse.swt.graphics.Image;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
/**
* A completion strategy for function completions like:
*
* v a r .
* ^
*
* @author cbateman
*
*/
public class FunctionCompletionStrategy extends ContentAssistStrategy
{
/**
* @param value
* @param proposalStart
*/
public FunctionCompletionStrategy(String value, String proposalStart)
{
super(ContentAssistStrategy.PREFIX_TYPE_DOT_COMPLETION, value, proposalStart);
}
public List getProposals(IStructuredDocumentContext context)
{
final List completionList = new ArrayList();
String[] ids = getValue().split("\\.");
// if no suffixes, only one id
if (ids.length < 1)
{
ids = new String[] {getValue()};
}
final ISymbolContextResolver symbolResolver =
StructuredDocumentSymbolResolverFactory.getInstance().
getSymbolContextResolver(context);
ISymbol symbol = null;
if (symbolResolver != null)
{
symbol = symbolResolver.getVariable(ids[0]);
}
if (symbol instanceof IInstanceSymbol
&& ((IInstanceSymbol)symbol).isTypeResolved())
{
int curSuffixIdx = 1;
while
(curSuffixIdx < ids.length
&& symbol != null)
{
final ISymbol[] properties =
symbolResolver.getProperties(symbol);
// set symbol to null because hasn't been found yet
symbol = null;
SEARCH_SYMBOL_NAME:for
(int i = 0; i < properties.length; i++)
{
final ISymbol element = properties[i];
if (ids[curSuffixIdx].equals(element.getName()))
{
symbol = element;
break SEARCH_SYMBOL_NAME;
}
}
curSuffixIdx++;
}
// if we get a completion symbol, get it's proposals
if (symbol instanceof IObjectSymbol)
{
final List expectedMethodBindings = new ArrayList();
ISymbol[] suffixes = getSymbols((IObjectSymbol) symbol,
context,
symbolResolver,
expectedMethodBindings);
final ComposedAdapterFactory factory =
new ComposedAdapterFactory(
ComposedAdapterFactory.Descriptor.Registry.INSTANCE);
final IProposalCreationFactory creationInfo =
new MyProposalFactory(context, getProposalStart().length(),
expectedMethodBindings);
for (int i = 0; i < suffixes.length; i++)
{
final ISymbol propSymbol = suffixes[i];
final Object provider =
factory.adapt(propSymbol, IContentProposalProvider.class);
if (provider instanceof IContentProposalProvider)
{
final ICompletionProposal[] proposal =
((IContentProposalProvider) provider).
getProposals(propSymbol, creationInfo);
if (proposal != null)
{
addProposalsMatchingProposalStart(completionList,
proposal);
}
}
}
}
}
return completionList;
}
private ISymbol[] getSymbols(IObjectSymbol symbol,
IStructuredDocumentContext context,
ISymbolContextResolver symbolResolver,
List expectedMethodBindings)
{
List symbols = new ArrayList();
if (isMethodBindingExpected(context, expectedMethodBindings))
{
symbols.addAll(Arrays.asList(
symbolResolver.getMethods(symbol)));
}
symbols.addAll(Arrays.asList(symbolResolver.getProperties(symbol)));
return (ISymbol[]) symbols.toArray(ISymbol.EMPTY_SYMBOL_ARRAY);
}
private boolean isMethodBindingExpected(IStructuredDocumentContext context,
List expectedBindings)
{
boolean isMBExpected = false; // assume false until we find it true
final IDOMContextResolver domResolver =
IStructuredDocumentContextResolverFactory.INSTANCE.
getDOMContextResolver(context);
final Node curNode = domResolver.getNode();
if (curNode instanceof Attr)
{
final Attr attr = (Attr) curNode;
final Element element = attr.getOwnerElement();
final ITaglibContextResolver taglibResolver =
IStructuredDocumentContextResolverFactory.INSTANCE.
getTaglibContextResolver(context);
final String uri = taglibResolver.getTagURIForNodeName(element);
final List elVals =
MetaDataEnabledProcessingFactory.getInstance()
.getAttributeValueRuntimeTypeFeatureProcessors
(IValidELValues.class, context, uri,
element.getLocalName(), attr.getLocalName());
for (final Iterator it = elVals.iterator(); it.hasNext();)
{
final IValidELValues validValues = (IValidELValues) it.next();
try
{
CompositeType type = validValues.getExpectedRuntimeType();
if (type != null
&& type.getAssignmentTypeMask()
== IAssignable.ASSIGNMENT_TYPE_NONE)
{
isMBExpected = true;
expectedBindings.addAll(
Arrays.asList(
validValues.
getExpectedRuntimeType().
getSignatures()));
}
}
catch (ELIsNotValidException e)
{
// do nothing
}
}
}
// default condition is no method binding
return isMBExpected;
}
private static class MyProposalFactory extends ProposalCreationFactoryAdapter
{
private final static int DEFAULT_RELEVANCE = 1;
private final static int HIGH_RELEVANCE = 2;
private final static int NORMAL_RELEVANCE = 1;
private final static int LOW_RELEVANCE = 0;
private final List _expectedMethodBindings;
private final IStructuredDocumentContext _context;
/**
* @param context
* @param replacementLength
* @param expectedMethodBindings
*/
public MyProposalFactory(IStructuredDocumentContext context, int replacementLength,
List expectedMethodBindings) {
/*TODO I changed the meaning of "replacementLength" from "number of chars AFTER cursor to be
* replaced" to "number of chars BEFORE cursor to be replaced. Since "replacementLength"
* has always been 0 (constructor is only called by FunctionCompletionStrategy.getProposals()),
* this should not change anything, but I don't know if there have been different plans
* for "replacementLength".
* TODO Maybe this change should be done in the super class instead?
*/
super(context.getDocumentPosition() - replacementLength, replacementLength);
_context = context;
_expectedMethodBindings = expectedMethodBindings;
}
public ICompletionProposal createProposal(String replacementText,
String displayText,
String additionalText,
Image displayImage,
Object target)
{
int replacementOffset = _replacementOffset;
int replacementLength = _replacementLength;
// TODO: I regard this as a bit of hack, but until we write our
// proposal implementation, it's basically the only way I can
// see to do this
// if it's an array, we must check if we need to replace a
// preceding '.'
if (replacementText.startsWith("["))
{
ITextRegionContextResolver textResolver =
IStructuredDocumentContextResolverFactory.INSTANCE.getTextRegionResolver(_context);
if (textResolver.getRegionType().equals(DOMJSPRegionContexts.JSP_VBL_CLOSE))
{
textResolver =
IStructuredDocumentContextResolverFactory.
INSTANCE.getTextRegionResolver(textResolver.getPreviousContext());
}
String regionText = textResolver.getRegionText();
int regionStart = textResolver.getStartOffset();
if (DOMJSPRegionContexts.JSP_VBL_CONTENT.equals(textResolver.getRegionType())
&& regionText != null
&& regionStart != -1
&& regionStart < _context.getDocumentPosition())
{
int relativeOffset = _context.getDocumentPosition() - regionStart - 1;
if (regionText.charAt(relativeOffset) == '.')
{
// we must replace a length of 1 (the dot)
// at an offset on prior
replacementOffset--;
replacementLength = 1;
}
}
}
return createDefaultProposal(replacementText,
replacementOffset,
replacementLength,
replacementText.length(),
displayImage,
displayText,
null,
additionalText,
getRelevance(target, DEFAULT_RELEVANCE));
}
private int getRelevance(final Object target, final int defaultRelevance)
{
// if method bindings are expected, then list exact signature
// matches top most. Still list non-matching methods, but put
// them at the bottom
if (_expectedMethodBindings.size() > 0)
{
if (target instanceof IMethodSymbol)
{
final IMethodSymbol methodSymbol = (IMethodSymbol) target;
for (final Iterator it = _expectedMethodBindings.iterator();
it.hasNext();)
{
final String methodType = (String) it.next();
// we have a match, so push to the top
if (methodType.equals(methodSymbol.getSignature()))
{
return HIGH_RELEVANCE;
}
}
// if we get out of the loop, then this method doesn't
// match the expected signature
return LOW_RELEVANCE;
}
// non-method targets have normal relevance when mb expected
return NORMAL_RELEVANCE;
}
// otherwise, simply return the default for all
return defaultRelevance;
}
}
}