/** * <copyright> * * Copyright (c) 2008,2010 Eclipse Modeling Project 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 * * Contributors: * E.D.Willink - initial API and implementation * * </copyright> * * $Id: CommonContentProposals.java,v 1.1 2010/03/11 14:51:20 ewillink Exp $ */ package org.eclipse.ocl.examples.editor.ui.imp; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import lpg.runtime.ErrorToken; import lpg.runtime.IToken; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.ENamedElement; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.EcorePackage; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.imp.parser.ISourcePositionLocator; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.ocl.cst.CSTNode; import org.eclipse.ocl.cst.PathNameCS; import org.eclipse.ocl.cst.SimpleNameCS; import org.eclipse.ocl.examples.common.utils.EcoreUtils; import org.eclipse.ocl.examples.common.utils.TracingOption; import org.eclipse.ocl.examples.editor.ui.OCLExamplesEditorPlugin; import org.eclipse.ocl.examples.editor.ui.imp.ICommonParseController.TokenKind; import org.eclipse.ocl.examples.parser.environment.IHasName; import org.eclipse.ocl.expressions.StringLiteralExp; import org.eclipse.ocl.lpg.AbstractParser; import org.eclipse.ocl.lpg.DerivedPrsStream; import org.eclipse.swt.graphics.Image; public class CommonContentProposals { public static TracingOption proposalDebug = new TracingOption(OCLExamplesEditorPlugin.PLUGIN_ID, "proposal/debug"); protected final ICommonParseResult parseResult; protected final int offset; protected final Map<Object, ICommonProposal> map; protected CSTNode cstRoot; protected IToken tokenAtOffset; protected String prefixAtOffset; public CommonContentProposals(ICommonParseResult parseResult, int offset) { this.parseResult = parseResult; this.offset = offset; this.map = new HashMap<Object, ICommonProposal>(); } /** * Accumulate the candidate if it represents a suitable proposal. * @param cstNode */ protected void addIdentifierProposalCandidate(Map<EClassifier, List<EStructuralFeature>> usages, EObject proposal, CSTNode cstNode) { if (checkName(usages, proposal, cstNode) && checkType(usages, proposal, cstNode) && !map.containsKey(proposal)) { ILabelProvider labelProvider = parseResult.getParseController().getLabelProvider(); String newText = getProposalReplacementText(proposal); String displayText = getProposalDisplayText(labelProvider, proposal, newText); Image image = getProposalDisplayImage(labelProvider, proposal); String oldText = getTokenAtOffsetString(); map.put(proposal, new CommonProposal(displayText, tokenAtOffset.getStartOffset(), newText, oldText, offset, image)); } } protected void addIdentifierProposals(CSTNode cstNode) { Object astNode = getAst(cstNode); if (astNode == null) { map.put(null, new CommonNonProposal("Internal error: no AST node to select completion proposal for " + cstNode.getClass().getSimpleName(), "", offset)); return; } if (proposalDebug.isActive()) proposalDebug.println("Proposal for '" + prefixAtOffset + "' " + cstNode.getClass().getSimpleName() + " " + astNode.getClass().getSimpleName()); if (astNode instanceof EObject) { Map<EClassifier, List<EStructuralFeature>> usages = computeUsage((EObject) astNode); for (Resource resource : getResources(usages, (EObject) astNode)) for (TreeIterator<EObject> i = resource.getAllContents(); i.hasNext(); ) addIdentifierProposalCandidate(usages, i.next(), cstNode); } } protected void addIdentifierKeywordProposals(CSTNode cstNode) { for (ICommonKeyword keyword : parseResult.getKeywords()) { if (keyword.isIdentifier(cstNode)) { String keywordText = keyword.getText(); if (offset < tokenAtOffset.getStartOffset()) map.put(keyword, new CommonProposal(keywordText, offset, keywordText, "", offset, null)); else if (keywordText.startsWith(prefixAtOffset)) map.put(keyword, new CommonProposal(keywordText, tokenAtOffset.getStartOffset(), keywordText, getTokenAtOffsetString(), offset, null)); } } } protected void addKeywordProposals() { for (ICommonKeyword keyword : parseResult.getKeywords()) { String keywordText = keyword.getText(); if (offset < tokenAtOffset.getStartOffset()) map.put(keyword, new CommonProposal(keywordText, offset, keywordText, "", offset, null)); else if (keywordText.startsWith(prefixAtOffset)) map.put(keyword, new CommonProposal(keywordText, tokenAtOffset.getStartOffset(), keywordText, getTokenAtOffsetString(), offset, null)); } } protected void addStringProposals() { Collection<Resource> resources = parseResult.getResourcesVisibleAt(null); for (Resource resource : resources) for (TreeIterator<EObject> i = resource.getAllContents(); i.hasNext(); ) addStringProposalCandidate(i.next()); } protected void addStringProposalCandidate(EObject candidate) { if (candidate instanceof StringLiteralExp<?>) { String string = ((StringLiteralExp<?>)candidate).getStringSymbol(); if (!map.containsKey(string) && string.startsWith(prefixAtOffset.length() > 0 ? prefixAtOffset.substring(1) : "")) { String newText = "'" + string + "'"; String displayText = string; // ILabelProvider labelProvider = commonParseController.getLabelProvider(); Image image = null; // FIXME labelProvider.geImage(string); map.put(string, new CommonProposal(displayText, tokenAtOffset.getStartOffset(), newText, getTokenAtOffsetString(), offset, image)); } } } /** * Add the usage of the target of feature from source to the map of all usages. * * @param usages * @param source * @param feature */ protected void addUsage(Map<EClassifier, List<EStructuralFeature>> usages, EObject source, EStructuralFeature feature) { EClassifier type = EcoreUtils.getEType(source, feature); // Resolves type parameters List<EStructuralFeature> usageList = usages.get(type); if (usageList == null) { usageList = new ArrayList<EStructuralFeature>(); usages.put(type, usageList); } if (!usageList.contains(feature)) usageList.add(feature); } /** * Return true if the name of eObject is suitable for the usages with prefixAtOffset. */ protected boolean checkName(Map<EClassifier, List<EStructuralFeature>> usages, EObject eObject, CSTNode cstNode) { String name = getName(eObject); if (name == null) return false; return name.startsWith(prefixAtOffset); } /** * Return true if the type of eObject is suitable as the target of the usages. */ protected boolean checkType(Map<EClassifier, List<EStructuralFeature>> usages, EObject eObject, CSTNode cstNode) { if (usages.isEmpty()) return false; for (Map.Entry<EClassifier, List<EStructuralFeature>> requiredUsage : usages.entrySet()) { EClassifier requiredType = requiredUsage.getKey(); Set<EClass> completableClasses = getCompletableTypes(requiredType, cstNode); if (completableClasses == null) return false; EClass eClass = eObject.eClass(); boolean completable = false; for (EClass completableClass : completableClasses) { if ((eClass == completableClass) || completableClass.isSuperTypeOf(eClass)) { completable = true; break; } } if (!completable) return false; } return true; } public void computeProposals() { cstRoot = parseResult.getCST(); if (cstRoot == null) { if (proposalDebug.isActive()) proposalDebug.println("No CST"); map.put(null, new CommonNonProposal("no info available due to Syntax error(s)", "", offset)); return; } tokenAtOffset = getToken(); prefixAtOffset = getPrefix(); TokenKind tokenKind = parseResult.getTokenKind(tokenAtOffset.getKind()); switch (tokenKind) { case IDENTIFIER: { ISourcePositionLocator locator = parseResult.getSourcePositionLocator(); CSTNode node = (CSTNode) locator.findNode(cstRoot, tokenAtOffset.getStartOffset(), tokenAtOffset.getEndOffset()); if (node == null) { if (proposalDebug.isActive()) proposalDebug.println("No CST node"); map.put(null, new CommonNonProposal("no info available due to Syntax error(s)", "", offset)); } else { addIdentifierProposals(node); addIdentifierKeywordProposals(node); if (map.isEmpty()) { Object astNode = getAst(node); if (astNode != null) // Fix for Bug 277746 map.put(null, new CommonNonProposal("no completion exists for '" + prefixAtOffset + "' " + node.getClass().getSimpleName() + " " + astNode.getClass().getSimpleName(), "", offset)); else map.put(null, new CommonNonProposal("no completion exists for '" + prefixAtOffset + "' " + node.getClass().getSimpleName(), "", offset)); } } break; } case ERROR: { ISourcePositionLocator locator = parseResult.getSourcePositionLocator(); CSTNode node = (CSTNode) locator.findNode(cstRoot, tokenAtOffset.getStartOffset(), tokenAtOffset.getEndOffset()); addIdentifierProposals(node); addIdentifierKeywordProposals(node); addKeywordProposals(); if (map.isEmpty()) map.put(null, new CommonNonProposal("no completion exists for keyword: " + prefixAtOffset, "", offset)); break; } case KEYWORD: { ISourcePositionLocator locator = parseResult.getSourcePositionLocator(); CSTNode node = (CSTNode) locator.findNode(cstRoot, tokenAtOffset.getStartOffset(), tokenAtOffset.getEndOffset()); if ((node instanceof IHasName) || (node instanceof SimpleNameCS)) { addIdentifierProposals(node); addIdentifierKeywordProposals(node); } else addKeywordProposals(); if (map.isEmpty()) map.put(null, new CommonNonProposal("no completion exists for keyword: " + prefixAtOffset, "", offset)); break; } case STRING: { addStringProposals(); if (map.isEmpty()) map.put(null, new CommonNonProposal("no completion exists for string: " + prefixAtOffset, "", offset)); break; } default: { map.put(null, new CommonNonProposal("no completion exists for " + tokenKind + ": " + prefixAtOffset, "", offset)); } } } /** * Return the feature target types for which astNode is or could be the target. * The return value is a map of which the keys are probably all that is useful. * The list of features that require that key provides an opportunity to filter * the usages, noting that the features may well involve generic types, which * have been resolved for use as a key. */ protected Map<EClassifier, List<EStructuralFeature>> computeUsage(EObject astNode) { Map<EClassifier, List<EStructuralFeature>> usages = new HashMap<EClassifier, List<EStructuralFeature>>(); Object rootAst = cstRoot.getAst(); Resource resource = rootAst instanceof Resource ? (Resource)rootAst : null; if (resource == null) resource = astNode.eResource(); Collection<EStructuralFeature.Setting> settings = EcoreUtil.UsageCrossReferencer.find(astNode, resource); for (EStructuralFeature.Setting setting : settings) addUsage(usages, setting.getEObject(), setting.getEStructuralFeature()); EStructuralFeature containingFeature = astNode.eContainingFeature(); if ((containingFeature == null) && (astNode instanceof EPackage)) // Provide a plausible containment feature containingFeature = EcorePackage.Literals.EPACKAGE__ESUBPACKAGES; // for nodes at the root of the resource if (containingFeature != null) addUsage(usages, astNode.eContainer(), containingFeature); return usages; } protected Object getAst(CSTNode cstNode) { if (cstNode == null) return null; Object astNode = cstNode.getAst(); if ((astNode == null) && ((cstNode instanceof SimpleNameCS) || (cstNode instanceof PathNameCS))) { astNode = ((CSTNode) cstNode.eContainer()).getAst(); if ((astNode != null) && proposalDebug.isActive()) proposalDebug.println("Missing astNode deduced for " + astNode.getClass().getSimpleName()); } return astNode; } public Set<EClass> getCompletableTypes(EClassifier requiredType, CSTNode cstNode) { Set<EClass> completableClasses = new HashSet<EClass>(); if (requiredType instanceof EClass) completableClasses.add((EClass)requiredType); return completableClasses; } protected String getName(EObject eObject) { if (eObject instanceof ENamedElement) return ((ENamedElement) eObject).getName(); else if (eObject instanceof IHasName) return ((IHasName)eObject).getName(); else return null; } protected String getPrefix() { if (parseResult.isCompleteable(tokenAtOffset.getKind())) if ((tokenAtOffset.getStartOffset() <= offset) && (offset <= tokenAtOffset.getEndOffset() + 1)) return getTokenAtOffsetString().substring(0, offset - tokenAtOffset.getStartOffset()); return ""; } /** * Return the image to appear in the list of proposals for proposal. * * @param labelProvider * @param proposal * @return */ protected Image getProposalDisplayImage(ILabelProvider labelProvider, EObject proposal) { return labelProvider.getImage(proposal); } /** * Return the text to appear in the list of proposals for proposal for which newText is the replacement. * * @param labelProvider * @param proposal * @param newText * @return */ protected String getProposalDisplayText(ILabelProvider labelProvider, EObject proposal, String newText) { EObject container = proposal.eContainer(); String containerText = EcoreUtils.qualifiedNameFor(container); // String containerText = labelProvider.getText(container); return newText + " - " + containerText; } /** * Return the replacement text for proposal. * * @param labelProvider * @param proposal * @return */ protected String getProposalReplacementText(EObject proposal) { return EcoreUtils.simpleNameFor(proposal); } /** * Return the resources that may provide definitions to use in place of astNode * as the target of requiredUsage. */ protected Collection<Resource> getResources(Map<EClassifier, List<EStructuralFeature>> usages, EObject astNode) { return parseResult.getResourcesVisibleAt(astNode); } protected IToken getToken() { AbstractParser parser = parseResult.getParser(); DerivedPrsStream stream = parser.getIPrsStream(); IToken errorToken = stream.getErrorTokenAtCharacter(offset); if (errorToken != null) return errorToken; int index = stream.getTokenIndexAtCharacter(offset); int tokenIndex = (index < 0 ? -(index - 1) : index); IToken token = stream.getIToken(tokenIndex); int previousIndex = stream.getPrevious(tokenIndex); IToken previousToken = stream.getIToken(previousIndex); int previousIndexKind = previousToken.getKind(); boolean isIdentifier = parseResult.isIdentifier(previousIndexKind); boolean isKeyword = parseResult.isKeyword(previousIndexKind); boolean atEnd = offset == previousToken.getEndOffset() + 1; return ((isIdentifier || isKeyword) && atEnd) ? previousToken : token; } protected String getTokenAtOffsetString() { return tokenAtOffset instanceof ErrorToken ? "" : tokenAtOffset.toString(); } /** * Sort the computed proposals into the order in which they appear to the user. */ public ICompletionProposal[] sortProposals() { List<ICommonProposal> list = new ArrayList<ICommonProposal>(map.values()); Collections.sort(list); return list.toArray(new ICommonProposal[list.size()]); } }