/**
* Copyright (c) 2010, 2013 Darmstadt University of Technology.
* 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:
* Marcel Bruch - initial API and implementation.
*/
package org.eclipse.recommenders.completion.rcp;
import static com.google.common.base.Optional.*;
import static org.apache.commons.lang3.StringUtils.substringBeforeLast;
import static org.eclipse.recommenders.completion.rcp.CompletionContextFunctions.defaultFunctions;
import static org.eclipse.recommenders.completion.rcp.CompletionContextKey.*;
import static org.eclipse.recommenders.internal.completion.rcp.l10n.LogMessages.ERROR_FAILED_TO_PARSE_TYPE_NAME;
import static org.eclipse.recommenders.utils.Checks.*;
import static org.eclipse.recommenders.utils.Logs.log;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.core.CompletionProposal;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.internal.codeassist.InternalCompletionContext;
import org.eclipse.jdt.internal.codeassist.complete.CompletionOnMemberAccess;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.AllocationExpression;
import org.eclipse.jdt.internal.compiler.ast.MessageSend;
import org.eclipse.jdt.internal.compiler.lookup.MethodBinding;
import org.eclipse.jdt.internal.compiler.lookup.MissingTypeBinding;
import org.eclipse.jdt.internal.compiler.lookup.TypeBinding;
import org.eclipse.jdt.ui.text.java.IJavaCompletionProposal;
import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext;
import org.eclipse.jface.text.Region;
import org.eclipse.recommenders.rcp.IAstProvider;
import org.eclipse.recommenders.rcp.utils.CompilerBindings;
import org.eclipse.recommenders.rcp.utils.JdtUtils;
import org.eclipse.recommenders.utils.names.IMethodName;
import org.eclipse.recommenders.utils.names.ITypeName;
import org.eclipse.recommenders.utils.names.VmTypeName;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
@SuppressWarnings({ "restriction", "rawtypes", "unchecked" })
public class RecommendersCompletionContext implements IRecommendersCompletionContext {
@VisibleForTesting
public static Set<ITypeName> createTypeNamesFromSignatures(final char[][] sigs) {
if (sigs == null) {
return Collections.emptySet();
}
if (sigs.length < 1) {
return Collections.emptySet();
}
Set<ITypeName> res = Sets.newHashSet();
// JDT signatures contain '.' instead of '/' and may end with ';'
for (char[] sig : sigs) {
try {
String descriptor = new String(sig).replace('.', '/');
descriptor = substringBeforeLast(descriptor, ";"); //$NON-NLS-1$
res.add(VmTypeName.get(descriptor));
} catch (Exception e) {
// this fails sometimes on method argument completion.
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=396595
log(ERROR_FAILED_TO_PARSE_TYPE_NAME, e, String.valueOf(sig));
}
}
return res;
}
private Map<CompletionContextKey, Object> data = Maps.newHashMap();
private Map<CompletionContextKey, ICompletionContextFunction> functions;
public RecommendersCompletionContext(final JavaContentAssistInvocationContext jdtContext,
final IAstProvider astProvider) {
this(jdtContext, astProvider, defaultFunctions());
}
public RecommendersCompletionContext(final JavaContentAssistInvocationContext jdtContext,
final IAstProvider astProvider, Map<CompletionContextKey, ICompletionContextFunction> functions) {
set(JAVA_CONTENTASSIST_CONTEXT, jdtContext);
set(AST_PROVIDER, astProvider);
this.functions = functions;
}
@Override
public Optional<CompilationUnit> getAST() {
IAstProvider astProvider = get(AST_PROVIDER, null);
CompilationUnit ast = astProvider != null ? astProvider.get(getCompilationUnit()) : null;
return Optional.fromNullable(ast);
}
@Override
public ICompilationUnit getCompilationUnit() {
return getJavaContext().getCompilationUnit();
}
@Override
public Optional<ASTNode> getCompletionNode() {
InternalCompletionContext ctx = doGetCoreContext();
ASTNode res = ctx != null ? ctx.getCompletionNode() : null;
return Optional.fromNullable(res);
}
@Override
public Optional<ASTNode> getCompletionNodeParent() {
InternalCompletionContext ctx = doGetCoreContext();
ASTNode res = ctx != null ? ctx.getCompletionNodeParent() : null;
return Optional.fromNullable(res);
}
public Optional<InternalCompletionContext> getCoreContext() {
return fromNullable(doGetCoreContext());
}
private InternalCompletionContext doGetCoreContext() {
return get(INTERNAL_COMPLETIONCONTEXT, null);
}
@Override
public Optional<IJavaElement> getEnclosingElement() {
IJavaElement enclosing = get(ENCLOSING_ELEMENT, null);
return fromNullable(enclosing);
}
@Override
public Optional<IMethod> getEnclosingMethod() {
IMethod enclosing = get(ENCLOSING_METHOD, null);
return fromNullable(enclosing);
}
@Override
public Optional<IType> getEnclosingType() {
IType enclosing = get(ENCLOSING_TYPE, null);
return fromNullable(enclosing);
}
@Override
public Optional<IType> getExpectedType() {
IType res = get(EXPECTED_TYPE, null);
return fromNullable(res);
}
@Override
public Set<ITypeName> getExpectedTypeNames() {
Set<ITypeName> res = get(EXPECTED_TYPENAMES, null);
return res == null ? Sets.<ITypeName>newHashSet() : res;
}
@Override
public Optional<String> getExpectedTypeSignature() {
InternalCompletionContext coreContext = doGetCoreContext();
if (coreContext == null) {
return absent();
}
// keys contain '/' instead of dots and may end with ';'
final char[][] keys = coreContext.getExpectedTypesSignatures();
if (keys == null) {
return absent();
}
if (keys.length < 1) {
return absent();
}
final String res = new String(keys[0]);
return of(res);
}
@Override
public Optional<IType> getClosestEnclosingType() {
IJavaElement enclosing = get(ENCLOSING_ELEMENT, null);
if (enclosing == null) {
return absent();
}
if (enclosing instanceof IType) {
return of((IType) enclosing);
} else {
final IType type = (IType) enclosing.getAncestor(IJavaElement.TYPE);
return fromNullable(type);
}
}
@Override
public int getInvocationOffset() {
return getJavaContext().getInvocationOffset();
}
@Override
public JavaContentAssistInvocationContext getJavaContext() {
return get(JAVA_CONTENTASSIST_CONTEXT, null);
}
@Override
public Optional<IMethodName> getMethodDef() {
final ASTNode node = getCompletionNode().orNull();
if (node == null) {
return absent();
}
if (node instanceof CompletionOnMemberAccess) {
final CompletionOnMemberAccess n = cast(node);
if (n.receiver instanceof MessageSend) {
final MessageSend receiver = (MessageSend) n.receiver;
final MethodBinding binding = receiver.binding;
return CompilerBindings.toMethodName(binding);
} else if (n.receiver instanceof AllocationExpression) {
AllocationExpression receiver = (AllocationExpression) n.receiver;
MethodBinding binding = receiver.binding;
return CompilerBindings.toMethodName(binding);
}
}
return absent();
}
@Override
public String getPrefix() {
return get(COMPLETION_PREFIX, ""); //$NON-NLS-1$
}
@Override
public IJavaProject getProject() {
return getJavaContext().getProject();
}
@Override
public Map<IJavaCompletionProposal, CompletionProposal> getProposals() {
return get(JAVA_PROPOSALS, Maps.<IJavaCompletionProposal, CompletionProposal>newHashMap());
}
@Override
public String getReceiverName() {
return get(RECEIVER_NAME, ""); //$NON-NLS-1$
}
@Override
public Optional<IType> getReceiverType() {
TypeBinding b = get(RECEIVER_TYPEBINDING, null);
if (b == null || b instanceof MissingTypeBinding) {
return absent();
}
return JdtUtils.createUnresolvedType(b.erasure());
}
@Override
public Optional<String> getReceiverTypeSignature() {
TypeBinding b = get(RECEIVER_TYPEBINDING, null);
if (b == null) {
return absent();
}
final String res = new String(b.signature());
return of(res);
}
@Override
public Region getReplacementRange() {
final int offset = getInvocationOffset();
final int length = getPrefix().length();
return new Region(offset, length);
}
@Override
public List<IField> getVisibleFields() {
return get(VISIBLE_FIELDS, Collections.<IField>emptyList());
}
@Override
public List<ILocalVariable> getVisibleLocals() {
return get(VISIBLE_LOCALS, Collections.<ILocalVariable>emptyList());
}
@Override
public List<IMethod> getVisibleMethods() {
return get(VISIBLE_METHODS, Collections.<IMethod>emptyList());
}
@Override
public boolean hasEnclosingElement() {
return getEnclosingElement().isPresent();
}
@Override
public boolean isCompletionInMethodBody() {
return getEnclosingMethod().isPresent();
}
@Override
public boolean isCompletionInTypeBody() {
return getEnclosingType().isPresent();
}
@Override
public <T> void set(CompletionContextKey<T> key, T value) {
ensureIsNotNull(key);
data.put(key, value);
}
@Override
public ImmutableMap<CompletionContextKey, Object> values() {
return ImmutableMap.copyOf(data);
}
@Override
public <T> Optional<T> get(CompletionContextKey<T> key) {
// if the key is in already, the value was already computed. May be null though:
if (data.containsKey(key)) { // data.remove(key)
return fromNullable((T) data.get(key));
}
// if it's not yet in, try computing it using a context-function
ICompletionContextFunction<T> function = functions.get(key);
if (function != null) {
T res = function.compute(this, key);
return fromNullable(res);
}
return absent();
}
@Override
public <T> T get(CompletionContextKey<T> key, T defaultValue) {
T res = get(key).orNull();
return res != null ? res : defaultValue;
}
}