672536501f6aeeca428b2bd9b10020a906803ba1
[idea/community.git] / java / java-impl / src / com / intellij / codeInsight / completion / ConstructorInsertHandler.java
1 package com.intellij.codeInsight.completion;
2
3 import com.intellij.codeInsight.ExpectedTypeInfo;
4 import com.intellij.codeInsight.ExpectedTypesProvider;
5 import com.intellij.codeInsight.generation.GenerateMembersUtil;
6 import com.intellij.codeInsight.generation.OverrideImplementUtil;
7 import com.intellij.codeInsight.generation.PsiGenerationInfo;
8 import com.intellij.codeInsight.generation.PsiMethodMember;
9 import com.intellij.codeInsight.lookup.Lookup;
10 import com.intellij.codeInsight.lookup.LookupElementDecorator;
11 import com.intellij.codeInsight.lookup.LookupItem;
12 import com.intellij.codeInsight.lookup.PsiTypeLookupItem;
13 import com.intellij.featureStatistics.FeatureUsageTracker;
14 import com.intellij.ide.util.MemberChooser;
15 import com.intellij.openapi.application.ApplicationManager;
16 import com.intellij.openapi.command.CommandProcessor;
17 import com.intellij.openapi.command.UndoConfirmationPolicy;
18 import com.intellij.openapi.diagnostic.Logger;
19 import com.intellij.openapi.editor.Editor;
20 import com.intellij.openapi.editor.ScrollType;
21 import com.intellij.openapi.project.Project;
22 import com.intellij.psi.*;
23 import com.intellij.psi.codeStyle.CodeStyleManager;
24 import com.intellij.psi.impl.source.PostprocessReformattingAspect;
25 import com.intellij.psi.infos.CandidateInfo;
26 import com.intellij.psi.util.PsiTreeUtil;
27 import com.intellij.psi.util.PsiUtil;
28 import com.intellij.util.IncorrectOperationException;
29
30 import java.util.ArrayList;
31 import java.util.Collection;
32 import java.util.List;
33
34 /**
35 * @author peter
36 */
37 class ConstructorInsertHandler implements InsertHandler<LookupElementDecorator<LookupItem>> {
38   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.ConstructorInsertHandler");
39   public static final ConstructorInsertHandler SMART_INSTANCE = new ConstructorInsertHandler(true);
40   public static final ConstructorInsertHandler BASIC_INSTANCE = new ConstructorInsertHandler(false);
41   static final OffsetKey PARAM_LIST_START = OffsetKey.create("paramListStart");
42   static final OffsetKey PARAM_LIST_END = OffsetKey.create("paramListEnd");
43   private final boolean mySmart;
44
45   private ConstructorInsertHandler(boolean smart) {
46     mySmart = smart;
47   }
48
49   public void handleInsert(InsertionContext context, LookupElementDecorator<LookupItem> item) {
50     @SuppressWarnings({"unchecked"}) final LookupItem<PsiClass> delegate = item.getDelegate();
51
52     final PsiElement position = SmartCompletionDecorator.getPosition(context, delegate);
53     final PsiExpression enclosing = PsiTreeUtil.getContextOfType(position, PsiExpression.class, true);
54     final PsiAnonymousClass anonymousClass = PsiTreeUtil.getParentOfType(position, PsiAnonymousClass.class);
55     final boolean inAnonymous = anonymousClass != null && anonymousClass.getParent() == enclosing;
56     PsiClass psiClass = (PsiClass)item.getObject();
57
58     boolean isAbstract = psiClass.hasModifierProperty(PsiModifier.ABSTRACT);
59
60     if (Lookup.REPLACE_SELECT_CHAR == context.getCompletionChar()) {
61       final int plStart = context.getOffset(PARAM_LIST_START);
62       final int plEnd = context.getOffset(PARAM_LIST_END);
63       if (plStart >= 0 && plEnd >= 0) {
64         context.getDocument().deleteString(plStart, plEnd);
65         PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
66       }
67     }
68
69     OffsetKey insideRef = context.trackOffset(context.getTailOffset(), false);
70
71     boolean fillTypeArgs = false;
72     if (delegate instanceof PsiTypeLookupItem) {
73       fillTypeArgs = !isRawTypeExpected(context, (PsiTypeLookupItem)delegate) &&
74                      psiClass.getTypeParameters().length > 0 &&
75                      ((PsiTypeLookupItem)delegate).calcGenerics(position).isEmpty() &&
76                      context.getCompletionChar() != '(';
77       delegate.handleInsert(context);
78       PostprocessReformattingAspect.getInstance(context.getProject()).doPostponedFormatting(context.getFile().getViewProvider());
79     }
80
81     if (item.getDelegate() instanceof JavaPsiClassReferenceElement) {
82       PsiTypeLookupItem.addImportForItem(context, psiClass);
83     }
84
85     insertParentheses(context, delegate, psiClass, !inAnonymous && isAbstract);
86
87     if (inAnonymous) {
88       return;
89     }
90
91     if (isAbstract) {
92       if (mySmart) {
93         FeatureUsageTracker.getInstance().triggerFeatureUsed(JavaCompletionFeatures.AFTER_NEW_ANONYMOUS);
94       }
95
96       PostprocessReformattingAspect.getInstance(context.getProject()).doPostponedFormatting(context.getFile().getViewProvider());
97
98       final Editor editor = context.getEditor();
99       final int offset = context.getTailOffset();
100       editor.getDocument().insertString(offset, " {}");
101       editor.getCaretModel().moveToOffset(offset + 2);
102
103       if (fillTypeArgs && JavaCompletionUtil.promptTypeArgs(context, context.getOffset(insideRef))) return;
104
105       context.setLaterRunnable(generateAnonymousBody(editor, context.getFile()));
106     }
107     else {
108       PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
109       final PsiNewExpression newExpression =
110         PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiNewExpression.class, false);
111       if (newExpression != null) {
112         final PsiJavaCodeReferenceElement classReference = newExpression.getClassOrAnonymousClassReference();
113         if (classReference != null) {
114           CodeStyleManager.getInstance(context.getProject()).reformat(classReference);
115         }
116       }
117       if (mySmart) {
118         FeatureUsageTracker.getInstance().triggerFeatureUsed(JavaCompletionFeatures.AFTER_NEW);
119       }
120       if (fillTypeArgs && JavaCompletionUtil.promptTypeArgs(context, context.getOffset(insideRef))) return;
121     }
122   }
123
124   static boolean isRawTypeExpected(InsertionContext context, PsiTypeLookupItem delegate) {
125     PsiNewExpression newExpr =
126       PsiTreeUtil.findElementOfClassAtOffset(context.getFile(), context.getStartOffset(), PsiNewExpression.class, false);
127     if (newExpr != null) {
128       for (ExpectedTypeInfo info : ExpectedTypesProvider.getExpectedTypes(newExpr, true)) {
129         PsiType expected = info.getDefaultType();
130         if (expected.isAssignableFrom(delegate.getPsiType())) {
131           if (expected instanceof PsiClassType && ((PsiClassType)expected).isRaw()) {
132             return true;
133           }
134         }
135       }
136     }
137     return false;
138   }
139
140   public static boolean insertParentheses(InsertionContext context,
141                                           LookupItem delegate,
142                                           final PsiClass psiClass,
143                                           final boolean forAnonymous) {
144     if (context.getCompletionChar() == '[') {
145       return false;
146     }
147
148     final PsiElement place = context.getFile().findElementAt(context.getStartOffset());
149     final PsiResolveHelper resolveHelper = JavaPsiFacade.getInstance(context.getProject()).getResolveHelper();
150     assert place != null;
151     boolean hasParams = false;
152     for (PsiMethod constructor : psiClass.getConstructors()) {
153       if (!resolveHelper.isAccessible(constructor, place, null)) continue;
154       if (constructor.getParameterList().getParametersCount() > 0) {
155         hasParams = true;
156         break;
157       }
158     }
159
160     JavaCompletionUtil.insertParentheses(context, delegate, false, hasParams, forAnonymous);
161
162     return true;
163   }
164
165   private static Runnable generateAnonymousBody(final Editor editor, final PsiFile file) {
166     final Project project = file.getProject();
167     PsiDocumentManager.getInstance(project).commitAllDocuments();
168
169     int offset = editor.getCaretModel().getOffset();
170     PsiElement element = file.findElementAt(offset);
171     if (element == null) return null;
172
173     PsiElement parent = element.getParent();
174     if (!(parent instanceof PsiAnonymousClass)) return null;
175
176     try{
177       CodeStyleManager.getInstance(project).reformat(parent);
178     }
179     catch(IncorrectOperationException e){
180       LOG.error(e);
181     }
182     offset = parent.getTextRange().getEndOffset() - 1;
183     editor.getCaretModel().moveToOffset(offset);
184     editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
185     editor.getSelectionModel().removeSelection();
186
187     return new Runnable() {
188       public void run(){
189         CommandProcessor.getInstance().executeCommand(project, new Runnable() {
190           public void run() {
191             PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
192             final PsiAnonymousClass aClass = PsiTreeUtil.findElementOfClassAtOffset(file, editor.getCaretModel().getOffset(), PsiAnonymousClass.class, false);
193             if (aClass == null) return;
194
195             final Collection<CandidateInfo> candidatesToImplement = OverrideImplementUtil.getMethodsToOverrideImplement(aClass, true);
196             boolean invokeOverride = candidatesToImplement.isEmpty();
197             if (invokeOverride){
198               chooseAndOverrideMethodsInAdapter(project, editor, aClass);
199             }
200             else{
201               ApplicationManager.getApplication().runWriteAction(new Runnable() {
202                 public void run() {
203                   try{
204                     List<PsiMethod> methods = OverrideImplementUtil.overrideOrImplementMethodCandidates(aClass, candidatesToImplement, false);
205                     List<PsiGenerationInfo<PsiMethod>> prototypes = OverrideImplementUtil.convert2GenerationInfos(methods);
206                     List<PsiGenerationInfo<PsiMethod>> resultMembers = GenerateMembersUtil.insertMembersBeforeAnchor(aClass, null, prototypes);
207                     GenerateMembersUtil.positionCaret(editor, resultMembers.get(0).getPsiMember(), true);
208                   }
209                   catch(IncorrectOperationException ioe){
210                     LOG.error(ioe);
211                   }
212                 }
213               });
214             }
215
216           }
217         }, CompletionBundle.message("completion.smart.type.generate.anonymous.body"), null, UndoConfirmationPolicy.DEFAULT, editor.getDocument());
218       }
219     };
220   }
221
222   private static void chooseAndOverrideMethodsInAdapter(final Project project, final Editor editor, final PsiAnonymousClass aClass) {
223     PsiClass baseClass = aClass.getBaseClassType().resolve();
224     if (baseClass == null) return;
225     PsiMethod[] allBaseMethods = baseClass.getMethods();
226     if(allBaseMethods.length == 0) return;
227
228     List<PsiMethodMember> methods = new ArrayList<PsiMethodMember>();
229     for (final PsiMethod method : allBaseMethods) {
230       if (OverrideImplementUtil.isOverridable(method)) {
231         methods.add(new PsiMethodMember(method, PsiSubstitutor.UNKNOWN));
232       }
233     }
234
235     boolean canInsertOverride = PsiUtil.isLanguageLevel5OrHigher(aClass) && (PsiUtil.isLanguageLevel6OrHigher(aClass) || !aClass.isInterface());
236     final PsiMethodMember[] array = methods.toArray(new PsiMethodMember[methods.size()]);
237     final MemberChooser<PsiMethodMember> chooser = new MemberChooser<PsiMethodMember>(array, false, true, project, canInsertOverride);
238     chooser.setTitle(CompletionBundle.message("completion.smarttype.select.methods.to.override"));
239     chooser.setCopyJavadocVisible(true);
240
241     chooser.show();
242     List<PsiMethodMember> selected = chooser.getSelectedElements();
243     if (selected == null || selected.isEmpty()) return;
244
245
246     try{
247       final List<PsiGenerationInfo<PsiMethod>> prototypes = OverrideImplementUtil.overrideOrImplementMethods(aClass, selected, chooser.isCopyJavadoc(), chooser.isInsertOverrideAnnotation());
248
249       final int offset = editor.getCaretModel().getOffset();
250
251       ApplicationManager.getApplication().runWriteAction(new Runnable() {
252         public void run() {
253           try{
254             for (PsiGenerationInfo<PsiMethod> prototype : prototypes) {
255               PsiStatement[] statements = prototype.getPsiMember().getBody().getStatements();
256               if (statements.length > 0 && PsiType.VOID.equals(prototype.getPsiMember().getReturnType())) {
257                 statements[0].delete(); // remove "super(..)" call
258               }
259             }
260
261             List<PsiGenerationInfo<PsiMethod>> resultMembers = GenerateMembersUtil.insertMembersAtOffset(aClass.getContainingFile(), offset, prototypes);
262             GenerateMembersUtil.positionCaret(editor, resultMembers.get(0).getPsiMember(), true);
263           }
264           catch(IncorrectOperationException e){
265             LOG.error(e);
266           }
267         }
268       });
269     }
270     catch(IncorrectOperationException ioe){
271       LOG.error(ioe);
272     }
273   }
274
275
276 }