/*
* Copyright 2013 Guidewire Software, Inc.
*/
package gw.plugin.ij.completion;
import com.intellij.codeInsight.AutoPopupController;
import com.intellij.codeInsight.ExpectedTypeInfo;
import com.intellij.codeInsight.ExpectedTypesProvider;
import com.intellij.codeInsight.completion.*;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.editor.highlighter.HighlighterIterator;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.javadoc.PsiDocTag;
import com.intellij.psi.util.PsiTreeUtil;
import gw.lang.parser.IGosuParser;
import gw.lang.parser.ITypeUsesMap;
import gw.lang.parser.statements.IClassFileStatement;
import gw.lang.reflect.IErrorType;
import gw.lang.reflect.IType;
import gw.lang.reflect.TypeSystem;
import gw.lang.reflect.gs.IGosuClass;
import gw.plugin.ij.completion.handlers.filter.CompletionFilter;
import gw.plugin.ij.completion.handlers.filter.CompletionFilterExtensionPointBean;
import gw.plugin.ij.lang.psi.impl.AbstractGosuClassFileImpl;
import gw.plugin.ij.lang.psi.impl.GosuClassParseData;
import org.jetbrains.annotations.NotNull;
import java.util.List;
public class GosuClassNameInsertHandler implements InsertHandler<JavaPsiClassReferenceElement> {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.JavaClassNameInsertHandler");
public static final InsertHandler<JavaPsiClassReferenceElement> GOSU_CLASS_INSERT_HANDLER = new GosuClassNameInsertHandler();
public void handleInsert(@NotNull final InsertionContext context, @NotNull final JavaPsiClassReferenceElement item) {
if (!canBeInserted(item.getQualifiedName())) {
return;
}
final char c = context.getCompletionChar();
if (c == '\t' || c == '\n') {
context.setAddCompletionChar(false);
}
int offset = context.getTailOffset() - 1;
final PsiFile file = context.getFile();
if (PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiImportStatementBase.class, false) != null) {
final PsiJavaCodeReferenceElement ref = PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiJavaCodeReferenceElement.class, false);
final String qname = item.getQualifiedName();
if (qname != null && (ref == null || !qname.equals(ref.getCanonicalText()))) {
AllClassesGetter.INSERT_FQN.handleInsert(context, item);
}
return;
}
PsiElement position = file.findElementAt(offset);
PsiClass psiClass = item.getObject();
final Project project = context.getProject();
final boolean annotation = insertingAnnotation(context, item);
final Editor editor = context.getEditor();
if (c == '#') {
context.setLaterRunnable(new Runnable() {
public void run() {
new CodeCompletionHandlerBase(CompletionType.BASIC).invokeCompletion(project, editor);
}
});
}
if (position != null) {
PsiElement parent = position.getParent();
if (parent instanceof PsiJavaCodeReferenceElement) {
final PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement) parent;
if (PsiTreeUtil.getParentOfType(position, PsiDocTag.class) != null) {
if (ref.isReferenceTo(psiClass)) {
return;
}
}
final PsiReferenceParameterList parameterList = ref.getParameterList();
if (parameterList != null && parameterList.getTextLength() > 0) {
return;
}
}
}
if (completingRawConstructor(file.findElementAt(context.getTailOffset() - 1), item)) {
JavaCompletionUtil.insertParentheses(context, item, false, true);
//TODO-dp
// if (ConstructorInsertHandler.insertParentheses(context, item, psiClass)) {
// AutoPopupController.getInstance(project).autoPopupParameterInfo(editor, null);
// }
} else if (insertingAnnotationWithParameters(context, item)) {
JavaCompletionUtil.insertParentheses(context, item, false, true);
AutoPopupController.getInstance(project).autoPopupParameterInfo(editor, null);
}
// Added a commit to to avoid an exception -> ERROR - impl.PsiToDocumentSynchronizer - Attempt to modify PSI for non-committed Document!
PsiDocumentManager.getInstance(project).commitDocument(context.getDocument());
PsiFile psiFile = context.getFile();
if( psiFile instanceof AbstractGosuClassFileImpl ) {
((AbstractGosuClassFileImpl)psiFile).reparsePsiFromContent();
}
addImportForItem( psiFile, item.getQualifiedName(), item.getLookupString());
if (annotation) {
// Check if someone inserts annotation class that require @
PsiElement elementAt = file.findElementAt(context.getStartOffset());
final PsiElement parentElement = elementAt != null ? elementAt.getParent() : null;
if (elementAt instanceof PsiIdentifier &&
(PsiTreeUtil.getParentOfType(elementAt, PsiAnnotationParameterList.class) != null ||
parentElement instanceof PsiErrorElement && parentElement.getParent() instanceof PsiJavaFile // top level annotation without @
)
&& isAtTokenNeeded(context)) {
int expectedOffsetForAtToken = elementAt.getTextRange().getStartOffset();
context.getDocument().insertString(expectedOffsetForAtToken, "@");
}
}
}
public boolean canBeInserted(String fqn) {
List<CompletionFilter> filters = CompletionFilterExtensionPointBean.getFilters();
for (CompletionFilter filter : filters) {
if (!filter.allowsImportInsertion(fqn)) {
return false;
}
}
return true;
}
public static boolean addImportForItem( @NotNull PsiFile file, @NotNull String strQName, String strRelativeName ) {
if( !(file instanceof AbstractGosuClassFileImpl) ) {
return false;
}
PsiClass psiClass = ((AbstractGosuClassFileImpl) file).getPsiClass();
if( psiClass != null && strQName.startsWith(psiClass.getQualifiedName()) ) {
// The item to import is an inner class of the file
return false;
}
AbstractGosuClassFileImpl gosuClassFile = (AbstractGosuClassFileImpl) file;
GosuClassParseData parseData = gosuClassFile.getParseData();
if (parseData != null) {
IClassFileStatement classFileStatement = parseData.getClassFileStatement();
if (classFileStatement != null) {
IGosuClass gosuClass = classFileStatement.getGosuClass();
if (gosuClass != null) {
ITypeUsesMap typeUsesMap = gosuClass.getTypeUsesMap();
strQName = maybeRemoveBrackets(strQName);
strRelativeName = maybeRemoveBrackets(strRelativeName);
if (typeUsesMap != null && !typeUsesMap.containsType(strQName)) {
if( resolveRelativeTypeInParser(strQName, strRelativeName, gosuClass ) ) {
// The type can be resolved in the parser's context, so no need to import
return false;
}
gosuClassFile.addImport( strQName );
return true;
}
}
}
}
return false;
}
private static String maybeRemoveBrackets(String s) {
int i = s.indexOf("<");
if(i != -1) {
s = s.substring(0, i);
}
i = s.indexOf("[");
if(i != -1) {
s = s.substring(0, i);
}
return s;
}
public static boolean resolveRelativeTypeInParser(String strQName, String strRelativeName, @NotNull IGosuClass gosuClass) {
IGosuParser parser = gosuClass.getParser();
if( parser != null ) {
TypeSystem.pushModule( gosuClass.getTypeLoader().getModule() );
try {
IType type = parser.resolveTypeLiteral(strRelativeName).getType().getType();
if( !(type instanceof IErrorType) && strQName.equals(type.getName()) ) {
return true;
}
}
finally {
TypeSystem.popModule( gosuClass.getTypeLoader().getModule() );
}
}
return false;
}
private static boolean completingRawConstructor(@NotNull PsiElement position, @NotNull JavaPsiClassReferenceElement item) {
PsiNewExpression newExpression = PsiTreeUtil.getParentOfType(position, PsiNewExpression.class);
if (newExpression != null && newExpression instanceof PsiNewExpression) {
PsiTypeParameter[] typeParameters = item.getObject().getTypeParameters();
for (ExpectedTypeInfo info : ExpectedTypesProvider.getExpectedTypes(newExpression, true)) {
final PsiType type = info.getType();
if (info.isArrayTypeInfo()) {
return false;
}
if (typeParameters.length > 0 && type instanceof PsiClassType && !((PsiClassType) type).isRaw()) {
return false;
}
}
return true;
}
return false;
}
private static boolean insertingAnnotationWithParameters(@NotNull InsertionContext context, @NotNull LookupElement item) {
if (insertingAnnotation(context, item)) {
final Document document = context.getEditor().getDocument();
PsiDocumentManager.getInstance(context.getProject()).commitDocument(document);
PsiElement elementAt = context.getFile().findElementAt(context.getStartOffset());
if (elementAt instanceof PsiIdentifier) {
final PsiModifierListOwner parent = PsiTreeUtil.getParentOfType(elementAt, PsiModifierListOwner.class, false, PsiCodeBlock.class);
if (parent != null) {
for (PsiMethod m : ((PsiClass) item.getObject()).getMethods()) {
if (!(m instanceof PsiAnnotationMethod)) continue;
final PsiAnnotationMemberValue defaultValue = ((PsiAnnotationMethod) m).getDefaultValue();
if (defaultValue == null) return true;
}
}
}
}
return false;
}
private static boolean insertingAnnotation(@NotNull InsertionContext context, @NotNull LookupElement item) {
final Object obj = item.getObject();
if (!(obj instanceof PsiClass) || !((PsiClass) obj).isAnnotationType()) return false;
final Document document = context.getEditor().getDocument();
PsiDocumentManager.getInstance(context.getProject()).commitDocument(document);
final int offset = context.getStartOffset();
final PsiFile file = context.getFile();
if (PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiImportStatement.class, false) != null) return false;
//outside of any class: we are surely inserting an annotation
if (PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiClass.class, false) == null) return true;
//the easiest check that there's a @ before the identifier
return PsiTreeUtil.findElementOfClassAtOffset(file, offset, PsiAnnotation.class, false) != null;
}
private static boolean isAtTokenNeeded(@NotNull InsertionContext myContext) {
HighlighterIterator iterator = ((EditorEx) myContext.getEditor()).getHighlighter().createIterator(myContext.getStartOffset());
LOG.assertTrue(iterator.getTokenType() == JavaTokenType.IDENTIFIER);
iterator.retreat();
if (iterator.getTokenType() == TokenType.WHITE_SPACE) iterator.retreat();
return iterator.getTokenType() != JavaTokenType.AT && iterator.getTokenType() != JavaTokenType.DOT;
}
}