| /******************************************************************************* |
| * 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; |
| } |
| } |
| } |