/*
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.eclipse.codeassist.requestor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import org.codehaus.groovy.eclipse.GroovyLogManager;
import org.codehaus.groovy.eclipse.TraceCategory;
import org.codehaus.groovy.eclipse.codeassist.DocumentSourceBuffer;
import org.codehaus.groovy.eclipse.codeassist.GroovyContentAssist;
import org.codehaus.groovy.eclipse.codeassist.factories.AnnotationCollectorTypeCompletionProcessorFactory;
import org.codehaus.groovy.eclipse.codeassist.factories.AnnotationMemberValueCompletionProcessorFactory;
import org.codehaus.groovy.eclipse.codeassist.factories.ConstructorCompletionProcessorFactory;
import org.codehaus.groovy.eclipse.codeassist.factories.ExpressionCompletionProcessorFactory;
import org.codehaus.groovy.eclipse.codeassist.factories.GetSetMethodCompletionProcessorFactory;
import org.codehaus.groovy.eclipse.codeassist.factories.IGroovyCompletionProcessorFactory;
import org.codehaus.groovy.eclipse.codeassist.factories.LocalVariableCompletionProcessorFactory;
import org.codehaus.groovy.eclipse.codeassist.factories.ModifiersCompletionProcessorFactory;
import org.codehaus.groovy.eclipse.codeassist.factories.NewFieldCompletionProcessorFactory;
import org.codehaus.groovy.eclipse.codeassist.factories.NewMethodCompletionProcessorFactory;
import org.codehaus.groovy.eclipse.codeassist.factories.NewVariableCompletionProcessorFactory;
import org.codehaus.groovy.eclipse.codeassist.factories.PackageCompletionProcessorFactory;
import org.codehaus.groovy.eclipse.codeassist.factories.TypeCompletionProcessorFactory;
import org.codehaus.groovy.eclipse.codeassist.processors.IGroovyCompletionProcessor;
import org.codehaus.groovy.eclipse.codeassist.processors.IProposalFilter;
import org.codehaus.groovy.eclipse.codeassist.processors.IProposalFilterExtension;
import org.codehaus.groovy.eclipse.codeassist.processors.ProposalProviderRegistry;
import org.codehaus.groovy.eclipse.core.ISourceBuffer;
import org.codehaus.groovy.eclipse.core.util.ExpressionFinder;
import org.codehaus.groovy.eclipse.core.util.ParseException;
import org.codehaus.jdt.groovy.model.GroovyCompilationUnit;
import org.codehaus.jdt.groovy.model.ModuleNodeMapper.ModuleNodeInfo;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.groovy.search.ITypeResolver;
import org.eclipse.jdt.internal.core.JavaProject;
import org.eclipse.jdt.internal.core.SearchableEnvironment;
import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer;
import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContextInformation;
public class GroovyCompletionProposalComputer implements IJavaCompletionProposalComputer {
private static final Map<ContentAssistLocation, List<IGroovyCompletionProcessorFactory>> LOCATION_FACTORIES;
static {
Map<ContentAssistLocation, List<IGroovyCompletionProcessorFactory>> locationFactories =
new EnumMap<ContentAssistLocation, List<IGroovyCompletionProcessorFactory>>(ContentAssistLocation.class);
locationFactories.put(ContentAssistLocation.ANNOTATION, Collections.unmodifiableList(Arrays.asList(
new TypeCompletionProcessorFactory(),
new PackageCompletionProcessorFactory(),
new ConstructorCompletionProcessorFactory(),
new AnnotationCollectorTypeCompletionProcessorFactory()
)));
locationFactories.put(ContentAssistLocation.ANNOTATION_BODY, Collections.unmodifiableList(Arrays.<IGroovyCompletionProcessorFactory>asList(
new AnnotationMemberValueCompletionProcessorFactory()
)));
locationFactories.put(ContentAssistLocation.CLASS_BODY, Collections.unmodifiableList(Arrays.asList(
new ModifiersCompletionProcessorFactory(),
new NewMethodCompletionProcessorFactory(),
new GetSetMethodCompletionProcessorFactory(),
new NewFieldCompletionProcessorFactory(),
new TypeCompletionProcessorFactory(),
new PackageCompletionProcessorFactory(),
new NewVariableCompletionProcessorFactory()
)));
locationFactories.put(ContentAssistLocation.CONSTRUCTOR, Collections.unmodifiableList(Arrays.asList(
new TypeCompletionProcessorFactory(),
new PackageCompletionProcessorFactory(),
new ConstructorCompletionProcessorFactory()
)));
locationFactories.put(ContentAssistLocation.EXCEPTIONS, Collections.unmodifiableList(Arrays.asList(
new TypeCompletionProcessorFactory(),
new PackageCompletionProcessorFactory(),
new ConstructorCompletionProcessorFactory()
)));
locationFactories.put(ContentAssistLocation.EXPRESSION, Collections.unmodifiableList(Arrays.asList(
new ExpressionCompletionProcessorFactory(),
new PackageCompletionProcessorFactory()
)));
locationFactories.put(ContentAssistLocation.EXTENDS, Collections.unmodifiableList(Arrays.asList(
new TypeCompletionProcessorFactory(),
new PackageCompletionProcessorFactory(),
new ConstructorCompletionProcessorFactory()
)));
locationFactories.put(ContentAssistLocation.IMPLEMENTS, Collections.unmodifiableList(Arrays.asList(
new TypeCompletionProcessorFactory(),
new PackageCompletionProcessorFactory(),
new ConstructorCompletionProcessorFactory()
)));
locationFactories.put(ContentAssistLocation.IMPORT, Collections.unmodifiableList(Arrays.asList(
new TypeCompletionProcessorFactory(),
new PackageCompletionProcessorFactory(),
new ConstructorCompletionProcessorFactory(),
new ExpressionCompletionProcessorFactory()
)));
locationFactories.put(ContentAssistLocation.METHOD_CONTEXT, Collections.unmodifiableList(Arrays.asList(
new ExpressionCompletionProcessorFactory(),
new ConstructorCompletionProcessorFactory()
)));
locationFactories.put(ContentAssistLocation.PACKAGE, Collections.<IGroovyCompletionProcessorFactory>singletonList(
new PackageCompletionProcessorFactory()
));
locationFactories.put(ContentAssistLocation.PARAMETER, Collections.unmodifiableList(Arrays.asList(
new TypeCompletionProcessorFactory(),
new PackageCompletionProcessorFactory(),
new ConstructorCompletionProcessorFactory()
)));
locationFactories.put(ContentAssistLocation.SCRIPT, Collections.unmodifiableList(Arrays.asList(
new ModifiersCompletionProcessorFactory(),
new NewMethodCompletionProcessorFactory(),
new GetSetMethodCompletionProcessorFactory(),
new NewFieldCompletionProcessorFactory(),
new TypeCompletionProcessorFactory(),
new ExpressionCompletionProcessorFactory(),
new LocalVariableCompletionProcessorFactory(),
new PackageCompletionProcessorFactory(),
new NewVariableCompletionProcessorFactory()
)));
locationFactories.put(ContentAssistLocation.STATEMENT, Collections.unmodifiableList(Arrays.asList(
new TypeCompletionProcessorFactory(),
new ExpressionCompletionProcessorFactory(),
new LocalVariableCompletionProcessorFactory(),
new PackageCompletionProcessorFactory(),
new NewVariableCompletionProcessorFactory()
)));
LOCATION_FACTORIES = Collections.unmodifiableMap(locationFactories);
}
public List<ICompletionProposal> computeCompletionProposals(ContentAssistInvocationContext context, IProgressMonitor monitor) {
if (!(context instanceof JavaContentAssistInvocationContext)) {
return Collections.EMPTY_LIST;
}
JavaContentAssistInvocationContext javaContext = (JavaContentAssistInvocationContext) context;
ICompilationUnit unit = javaContext.getCompilationUnit();
if (!(unit instanceof GroovyCompilationUnit)) {
return Collections.EMPTY_LIST;
}
String event = null;
if (GroovyLogManager.manager.hasLoggers()) {
GroovyLogManager.manager.log(TraceCategory.CONTENT_ASSIST, "Starting content assist for " + unit.getElementName());
event = "Content assist for " + unit.getElementName();
GroovyLogManager.manager.logStart(event);
}
GroovyCompilationUnit gunit = (GroovyCompilationUnit) unit;
ModuleNodeInfo moduleInfo = gunit.getModuleInfo(true);
if (moduleInfo == null) {
if (GroovyLogManager.manager.hasLoggers()) {
GroovyLogManager.manager.log(TraceCategory.CONTENT_ASSIST, "Null module node for " + gunit.getElementName());
}
return Collections.EMPTY_LIST;
}
IDocument document = context.getDocument();
ContentAssistContext assistContext = createContentAssistContext(gunit, context.getInvocationOffset(), document);
List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
if (assistContext != null) {
List<IGroovyCompletionProcessorFactory> factories = LOCATION_FACTORIES.get(assistContext.location);
if (factories != null) {
SearchableEnvironment nameEnvironment = createSearchableEnvironment(javaContext);
try {
for (IGroovyCompletionProcessorFactory factory : factories) {
IGroovyCompletionProcessor processor =
factory.createProcessor(assistContext, javaContext, nameEnvironment);
if (processor != null) {
if (processor instanceof ITypeResolver) {
((ITypeResolver) processor).setResolverInformation(moduleInfo.module, moduleInfo.resolver);
}
proposals.addAll(processor.generateProposals(monitor));
}
}
} finally {
if (nameEnvironment != null) {
nameEnvironment.cleanup();
}
}
}
// extra filtering and sorting provided by third parties
try {
List<IProposalFilter> filters = ProposalProviderRegistry.getRegistry().getFiltersFor(assistContext.unit);
for (IProposalFilter filter : filters) {
try {
if (filter instanceof IProposalFilterExtension) {
List<ICompletionProposal> newProposals = ((IProposalFilterExtension) filter).filterExtendedProposals(proposals, assistContext, javaContext);
proposals = newProposals == null ? proposals : newProposals;
}
} catch (Exception e) {
GroovyContentAssist.logError("Exception when using third party proposal filter: " + filter.getClass().getCanonicalName(), e);
}
}
} catch (CoreException e) {
GroovyContentAssist.logError("Exception accessing proposal provider registry", e);
}
}
if (event != null) {
GroovyLogManager.manager.logEnd(event, TraceCategory.CONTENT_ASSIST);
}
return proposals;
}
// visible for testing
public ContentAssistContext createContentAssistContext(GroovyCompilationUnit gunit, int invocationOffset, IDocument document) {
String fullCompletionText = findCompletionText(document, invocationOffset);
String[] completionExpressions = findCompletionExpression(fullCompletionText);
final String completionExpression;
if (completionExpressions == null || "@".equals(fullCompletionText)) {
completionExpression = "";
} else if (completionExpressions[1] == null) {
completionExpression = completionExpressions[0];
} else {
completionExpression = completionExpressions[1];
}
int completionEnd = findCompletionEnd(document, invocationOffset);
int supportingNodeEnd = findSupportingNodeEnd(gunit, invocationOffset, fullCompletionText);
CompletionNodeFinder finder = new CompletionNodeFinder(
invocationOffset,
completionEnd,
supportingNodeEnd,
completionExpression,
fullCompletionText);
ContentAssistContext context = finder.findContentAssistContext(gunit);
return context;
}
private SearchableEnvironment createSearchableEnvironment(JavaContentAssistInvocationContext javaContext) {
try {
return ((JavaProject) javaContext.getProject()).newSearchableNameEnvironment(javaContext.getCompilationUnit().getOwner());
} catch (JavaModelException e) {
GroovyContentAssist.logError("Exception creating searchable environment for " + javaContext.getCompilationUnit(), e);
return null;
}
}
private String[] findCompletionExpression(String completionText) {
return new ExpressionFinder().splitForCompletion(completionText);
}
protected String findCompletionText(IDocument doc, int offset) {
try {
if (offset > 0) {
ISourceBuffer buffer = new DocumentSourceBuffer(doc);
return new ExpressionFinder().findForCompletions(buffer, offset - 1);
}
} catch (ParseException e) {
// can ignore; probably just invalid code that is being completed at
if (GroovyLogManager.manager.hasLoggers()) {
GroovyLogManager.manager.log(TraceCategory.CONTENT_ASSIST, "Cannot complete code: " + e.getMessage());
}
}
return "";
}
private int findCompletionEnd(IDocument doc, int offset) {
ISourceBuffer buffer = new DocumentSourceBuffer(doc);
return new ExpressionFinder().findTokenEnd(buffer, offset);
}
protected int findSupportingNodeEnd(GroovyCompilationUnit gunit, int invocationOffset, String fullCompletionText) {
String[] completionExpressions = new ExpressionFinder().splitForCompletionNoTrim(fullCompletionText);
// if second part of completion expression is null, then there is no supporting node (ie- no '.')
if (completionExpressions[1] == null) {
return -1;
}
int end = invocationOffset - fullCompletionText.length() + completionExpressions[0].length();
return end;
}
public List<IContextInformation> computeContextInformation(ContentAssistInvocationContext context, IProgressMonitor monitor) {
List<ICompletionProposal> proposals = computeCompletionProposals(context, monitor);
List<IContextInformation> contexts = new ArrayList<IContextInformation>(proposals.size());
for (ICompletionProposal proposal : proposals) {
if (proposal.getContextInformation() != null) {
contexts.add(proposal.getContextInformation());
}
}
return contexts;
}
public String getErrorMessage() {
return "";
}
public void sessionStarted() {
}
public void sessionEnded() {
}
}