/*******************************************************************************
* Copyright (c) 2012 VMware, 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:
* VMware, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.eclipse.quickfix.jdt.computers;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.CompletionProposal;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.Signature;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Block;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.SingleVariableDeclaration;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.internal.ui.text.correction.AssistContext;
import org.eclipse.jdt.internal.ui.text.java.JavaCompletionProposalComputer;
import org.eclipse.jdt.internal.ui.text.java.LazyJavaTypeCompletionProposal;
import org.eclipse.jdt.ui.SharedASTProvider;
import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext;
import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.source.SourceViewer;
import org.springframework.ide.eclipse.quickfix.Activator;
import org.springframework.ide.eclipse.quickfix.jdt.util.ProposalCalculatorUtil;
import org.springframework.ui.Model;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springsource.ide.eclipse.commons.core.SpringCoreUtils;
import org.springsource.ide.eclipse.commons.core.StatusHandler;
/**
* Content assist proposal computer for showing user a list of allowable type
* for a
* @RequestMapping method parameter. See {@link RequestMapping}
*
* @author Terry Denney
* @author Martin Lippert
* @since 2.6
*/
public class RequestMappingParamTypeProposalComputer extends JavaCompletionProposalComputer {
// TODO: add javax.portlet.PortletRequest
// TODO: add javax.portlet.ActionRequest
// TODO: add javax.portlet.RenderRequest
// TODO: add javax.portlet.PortletSession
// TODO: add @RequestParam
private static Class<?>[] PARAM_TYPE_CLASSES = new Class<?>[] { ServletRequest.class, HttpServletRequest.class,
HttpSession.class, WebRequest.class, NativeWebRequest.class, Locale.class, InputStream.class, Reader.class,
OutputStream.class, Writer.class, Map.class, Model.class, ModelMap.class, Errors.class,
BindingResult.class, SessionStatus.class };
@Override
public List<ICompletionProposal> computeCompletionProposals(ContentAssistInvocationContext context,
IProgressMonitor monitor) {
if (context instanceof JavaContentAssistInvocationContext) {
JavaContentAssistInvocationContext javaContext = (JavaContentAssistInvocationContext) context;
// check if project is a spring project
if (SpringCoreUtils.isSpringProject(javaContext.getProject().getProject())) {
ICompilationUnit cu = javaContext.getCompilationUnit();
if (ProposalCalculatorUtil.hasAnnotationOnType(cu, "Controller")) {
ITextViewer viewer = javaContext.getViewer();
if (viewer instanceof SourceViewer) {
SourceViewer sourceViewer = (SourceViewer) viewer;
int invocationOffset = context.getInvocationOffset();
AssistContext assistContext = new AssistContext(cu, sourceViewer, invocationOffset, 0, SharedASTProvider.WAIT_NO);
ASTNode node = assistContext.getCoveringNode();
// cursor is at the beginning of an empty param list
// [method(^)}
if (node instanceof MethodDeclaration) {
MethodDeclaration methodDecl = (MethodDeclaration) node;
if (ProposalCalculatorUtil.hasAnnotation("RequestMapping", methodDecl)) {
try {
// We need to discover if we're actually
// inside the parameter declaration -- node
// is reported as MethodDeclaration when
// we're inside annotation
IDocument document = sourceViewer.getDocument();
int relativeOffset = invocationOffset - methodDecl.getStartPosition();
String methodText = document.get(methodDecl.getStartPosition(),
methodDecl.getLength());
String methodName = methodDecl.getName().getFullyQualifiedName();
int getterLocation = methodText.indexOf(methodName) + methodName.length();
if (getterLocation < relativeOffset) {
return getProposals(methodDecl, "", invocationOffset, null, javaContext);
}
}
catch (BadLocationException e) {
StatusHandler
.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));
}
}
}
else if (node instanceof SimpleName) {
SimpleName name = (SimpleName) node;
ASTNode parentNode = name.getParent();
// cursor is at the start of a param at the end of
// the
// param
// list [metohd(param, ^)]
if (parentNode instanceof VariableDeclarationFragment) {
parentNode = parentNode.getParent();
if (parentNode instanceof VariableDeclarationStatement) {
VariableDeclarationStatement varDeclStmt = (VariableDeclarationStatement) parentNode;
Type varDeclType = varDeclStmt.getType();
if (varDeclType instanceof SimpleType) {
SimpleType sType = (SimpleType) varDeclType;
parentNode = parentNode.getParent();
if (parentNode instanceof Block) {
Block block = (Block) parentNode;
try {
if (viewer.getDocument().getChar(block.getStartPosition()) != '{') {
parentNode = parentNode.getParent();
if (parentNode instanceof MethodDeclaration) {
MethodDeclaration methodDecl = (MethodDeclaration) parentNode;
return getProposals(methodDecl, sType.getName()
.getFullyQualifiedName(), invocationOffset,
varDeclStmt, javaContext);
}
}
}
catch (BadLocationException e) {
StatusHandler.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e
.getMessage(), e));
}
}
}
}
}
// cursor is at the start of a param type
else if (parentNode instanceof SimpleType) {
SimpleType sType = (SimpleType) parentNode;
parentNode = sType.getParent();
if (parentNode instanceof SingleVariableDeclaration) {
SingleVariableDeclaration varDecl = (SingleVariableDeclaration) parentNode;
parentNode = varDecl.getParent();
if (parentNode instanceof MethodDeclaration) {
MethodDeclaration methodDecl = (MethodDeclaration) parentNode;
return getProposals(methodDecl, sType.getName().getFullyQualifiedName(),
invocationOffset, sType, javaContext);
}
}
}
}
// param at the end of a method param list
// [method(param,
// w^)]
else if (node instanceof Block) {
Block block = (Block) node;
ASTNode parentNode = block.getParent();
if (parentNode instanceof MethodDeclaration) {
MethodDeclaration methodDecl = (MethodDeclaration) parentNode;
try {
String blockContent = viewer.getDocument().get(block.getStartPosition(),
block.getLength());
if (blockContent.startsWith(",")) {
blockContent = blockContent.substring(1);
boolean isAnnotation = false;
while (blockContent.length() > 0) {
char currChar = blockContent.charAt(0);
if (Character.isWhitespace(currChar)) {
blockContent = blockContent.substring(1);
isAnnotation = false;
}
else if (currChar == '@') {
blockContent = blockContent.substring(1);
isAnnotation = true;
}
else if (Character.isLetter(currChar)) {
if (!isAnnotation) {
break;
}
else {
blockContent = blockContent.substring(1);
}
}
else {
break;
}
}
// skip annotations
if (blockContent.length() > 0) {
if (blockContent.charAt(0) == '@') {
blockContent = blockContent.substring(1);
}
}
StringBuilder filter = new StringBuilder();
while (blockContent.length() > 0) {
char currChar = blockContent.charAt(0);
if (Character.isLetter(currChar)) {
filter.append(currChar);
blockContent = blockContent.substring(1);
}
else {
break;
}
}
return getProposals(methodDecl, filter.toString(), invocationOffset, block,
javaContext);
}
}
catch (BadLocationException e) {
StatusHandler
.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, e.getMessage(), e));
}
}
}
}
}
}
}
return Collections.emptyList();
}
private List<ICompletionProposal> getProposals(MethodDeclaration methodDecl, String filter, int offset,
ASTNode toBeRemoved, JavaContentAssistInvocationContext context) {
List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
for (Class<?> paramType : PARAM_TYPE_CLASSES) {
if (paramType.getSimpleName().toLowerCase().startsWith(filter.toLowerCase())) {
// proposals
// .add(new
// RequestMappingParamTypeCompletionProposal(methodDecl,
// paramType, toBeRemoved, context));
CompletionProposal proposal = CompletionProposal.create(CompletionProposal.TYPE_REF,
context.getInvocationOffset());
proposal.setCompletion(paramType.getCanonicalName().toCharArray());
proposal.setDeclarationSignature(paramType.getPackage().getName().toCharArray());
proposal.setFlags(paramType.getModifiers());
proposal.setRelevance(Integer.MAX_VALUE);
proposal.setReplaceRange(context.getInvocationOffset() - filter.length(), context.getInvocationOffset());
proposal.setSignature(Signature.createTypeSignature(paramType.getCanonicalName(), true).toCharArray());
LazyJavaTypeCompletionProposal typeProposal = new LazyJavaTypeCompletionProposal(proposal, context);
typeProposal.setRelevance(Integer.MAX_VALUE);
proposals.add(typeProposal);
}
}
return proposals;
}
}