/*******************************************************************************
* Copyright (c) 2016 Pivotal Software, Inc.
* 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:
* Pivotal Software, Inc. - initial API and implementation
*******************************************************************************/
package org.springsource.ide.eclipse.commons.completions.constructors;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.jdt.core.CompletionRequestor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.internal.codeassist.CompletionEngine;
import org.eclipse.jdt.internal.codeassist.complete.CompletionNodeFound;
import org.eclipse.jdt.internal.codeassist.complete.CompletionParser;
import org.eclipse.jdt.internal.codeassist.complete.InvalidCursorLocation;
import org.eclipse.jdt.internal.codeassist.impl.Engine;
import org.eclipse.jdt.internal.compiler.CompilationResult;
import org.eclipse.jdt.internal.compiler.ast.ASTNode;
import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
import org.eclipse.jdt.internal.compiler.lookup.Binding;
import org.eclipse.jdt.internal.compiler.lookup.Scope;
import org.eclipse.jdt.internal.compiler.problem.AbortCompilation;
import org.eclipse.jdt.internal.core.DefaultWorkingCopyOwner;
import org.eclipse.jdt.internal.core.JavaProject;
import org.eclipse.jdt.internal.core.SearchableEnvironment;
import org.eclipse.jdt.internal.core.search.BasicSearchEngine;
import org.eclipse.jdt.internal.core.search.HierarchyScope;
import org.eclipse.jdt.internal.core.search.IRestrictedAccessConstructorRequestor;
import org.springsource.ide.eclipse.commons.completions.CompletionsActivator;
/**
* Completion Engine that wraps JDT CompletionEngine to collect completion
* proposals for constructors of specific type. Reflection is heavily used to
* tweak wrapped JDT CompletionEngine to work with our Java index search
* results.
*
* @author Alex Boyko
*
*/
@SuppressWarnings("restriction")
public class ConstructorCompletionEngine {
/**
* Wrapped JDT completion engine
*/
private CompletionEngine engine;
public ConstructorCompletionEngine(ICompilationUnit compilationUnit,
CompletionRequestor requestor,
SearchableEnvironment searchableEnv,
JavaProject javaProject,
WorkingCopyOwner owner,
IProgressMonitor monitor) {
this.engine = new CompletionEngine(searchableEnv, requestor, javaProject.getOptions(true), javaProject, DefaultWorkingCopyOwner.PRIMARY,
monitor);
}
/**
* Collects constructor related completion proposals when there is no
* constructor prefix and expected type is set
*
* @param sourceUnit
* Compilation unit
* @param pos
* Content Assist invocation offset
* @param root
* Root type
* @param expectedType
* Expected type
*/
public void complete(ICompilationUnit sourceUnit, int pos, ITypeRoot root, final IType expectedType) {
getEngineFieldValue("requestor", CompletionRequestor.class).beginReporting();
try {
setEngineFieldValue("fileName", sourceUnit.getFileName());
setEngineFieldValue("offset", 0);
setEngineFieldValue("actualCompletionPosition", pos - 1);
setEngineFieldValue("typeRoot", root);
// for now until we can change the UI.
CompilationResult result = new CompilationResult(sourceUnit, 1, 1, engine.compilerOptions.maxProblemsPerUnit);
CompilationUnitDeclaration parsedUnit = ((CompletionParser)engine.getParser()).dietParse(sourceUnit, result, pos - 1);
/*
* Skip imports and package related code since it shouldn't be applicable in this use case.
* Perform parsing similarly to JDT CompletionEngine
*/
if (parsedUnit != null) {
if (parsedUnit.types != null) {
try {
engine.lookupEnvironment.buildTypeBindings(parsedUnit, null /*no access restriction*/);
setEngineFieldValue("unitScope", parsedUnit.scope);
if (parsedUnit.scope != null) {
setEngineFieldValue("source", sourceUnit.getContents());
engine.lookupEnvironment.completeTypeBindings(parsedUnit, true);
parsedUnit.scope.faultInTypes();
parseBlockStatements(parsedUnit, pos - 1);
parsedUnit.resolve();
}
} catch (CompletionNodeFound e) {
// completionNodeFound = true;
if (e.astNode != null) {
// if null then we found a problem in the completion node
engine.lookupEnvironment.unitBeingCompleted = parsedUnit; // better resilient to further error reporting
try {
// Completion proposals should be provided. Search for possible completions ourselves
complete(e.astNode, ((CompletionParser) engine.getParser()).assistNodeParent,
parsedUnit, e.qualifiedBinding, e.scope, expectedType,
getEngineFieldValue("monitor", IProgressMonitor.class));
} catch (JavaModelException e1) {
CompletionsActivator.log(e1.getStatus());
}
}
}
}
}
} catch (IndexOutOfBoundsException | InvalidCursorLocation | AbortCompilation | CompletionNodeFound e) { // work-around internal failure - 1GEMF6D
CompletionsActivator.log(e);
} finally {
getEngineFieldValue("requestor", CompletionRequestor.class).endReporting();
if (getEngineFieldValue("monitor", IProgressMonitor.class) != null) getEngineFieldValue("monitor", IProgressMonitor.class).done();
try {
Method method = CompletionEngine.class.getDeclaredMethod("reset");
method.setAccessible(true);
method.invoke(engine);
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException | IllegalAccessException | InvocationTargetException e) {
CompletionsActivator.log(e);
}
}
}
/**
* Sets the value on a specified field of JDT CompletionEngine
* @param name The name of the field
* @param value The value for the field
*/
private void setEngineFieldValue(String name, Object value) {
try {
Field field = null;
try {
field = engine.getClass().getDeclaredField(name);
} catch (NoSuchFieldException e) {
field = Engine.class.getDeclaredField(name);
}
field.setAccessible(true);
field.set(engine, value);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
CompletionsActivator.log(e);
}
}
/**
* Gets the value of a specified field of JDT CompletionEngine
* @param name The name of the field
* @param clazz The type of the value
* @return The value of the field
*/
@SuppressWarnings("unchecked")
private <T> T getEngineFieldValue(String name, Class<T> clazz) {
try {
Field field = engine.getClass().getDeclaredField(name);
field.setAccessible(true);
return (T) field.get(engine);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
CompletionsActivator.log(e);
}
return null;
}
/**
* Delegates to JDT CompletionEngine#parseBlockStatements() method via reflection
*/
private ASTNode parseBlockStatements(CompilationUnitDeclaration unit, int position) {
try {
Method method = Engine.class.getDeclaredMethod("parseBlockStatements", CompilationUnitDeclaration.class, int.class);
method.setAccessible(true);
return (ASTNode) method.invoke(engine, unit, position);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
CompletionsActivator.log(e);
}
return null;
}
/**
* Searches and collects completion proposals for constructors
*
* @param astNode
* @param astNodeParent
* @param compilationUnitDeclaration
* @param qualifiedBinding
* @param scope
* @param expectedType
* @param monitor
* @throws JavaModelException
*/
private void complete(ASTNode astNode,
ASTNode astNodeParent,
CompilationUnitDeclaration compilationUnitDeclaration,
Binding qualifiedBinding,
Scope scope,
IType expectedType,
IProgressMonitor monitor) throws JavaModelException {
try {
/*
* Perform setup procedure below to init various fields on the JDT
* CompletionEngine to be able to correctly collect the completion
* results. See same named method on JDT CompletionEngine
*/
setEngineFieldValue("completionToken", new char[0]);
Method m = engine.getClass().getDeclaredMethod("setSourceAndTokenRange", int.class, int.class);
m.setAccessible(true);
m.invoke(engine, astNode.sourceStart, astNode.sourceEnd);
m = engine.getClass().getDeclaredMethod("computeForbiddenBindings", ASTNode.class, ASTNode.class, Scope.class);
m.setAccessible(true);
scope = (Scope) m.invoke(engine, astNode, astNodeParent, scope);
m = engine.getClass().getDeclaredMethod("computeUninterestingBindings", ASTNode.class, ASTNode.class, Scope.class);
m.setAccessible(true);
m.invoke(engine, astNode, astNodeParent, scope);
m = engine.getClass().getDeclaredMethod("buildContext", ASTNode.class, ASTNode.class, CompilationUnitDeclaration.class, Binding.class, Scope.class);
m.setAccessible(true);
m.invoke(engine, astNode, astNodeParent, compilationUnitDeclaration, qualifiedBinding, scope);
/*
* Perform our own search for possible constructors on a hierarchy scope
*/
IJavaSearchScope hierarchyScope = new HierarchyScope(getEngineFieldValue("javaProject", IJavaProject.class), expectedType, DefaultWorkingCopyOwner.PRIMARY, true, false, false);
BasicSearchEngine basicEngine = new BasicSearchEngine();
/*
* Search term is '*' meaning everything, i.e. any prefix
*/
basicEngine.searchAllConstructorDeclarations(null, "*".toCharArray(), SearchPattern.R_PATTERN_MATCH,
hierarchyScope, new IRestrictedAccessConstructorRequestor() {
@Override
public void acceptConstructor(int modifiers, char[] simpleTypeName, int parameterCount,
char[] signature, char[][] parameterTypes, char[][] parameterNames, int typeModifiers,
char[] packageName, int extraFlags, String path, AccessRestriction access) {
engine.acceptConstructor(modifiers, simpleTypeName, parameterCount, signature, parameterTypes, parameterNames, typeModifiers, packageName, extraFlags, path, access);
}
}, IJavaSearchConstants.FORCE_IMMEDIATE_SEARCH, monitor);
/*
* Use JDT CompletionEngine to process our search results, create core completion proposals and then collect them
*/
m = engine.getClass().getDeclaredMethod("acceptConstructors", Scope.class);
if (m != null) {
m.setAccessible(true);
m.invoke(engine, scope);
}
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException e) {
CompletionsActivator.log(e);
} catch (InvocationTargetException e) {
if (e.getCause() instanceof OperationCanceledException) {
throw (OperationCanceledException) e.getCause();
} else {
CompletionsActivator.log(e);
}
}
}
}