| /* |
| * Copyright 2000-2009 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| /* |
| * Created by IntelliJ IDEA. |
| * User: mike |
| * Date: Aug 26, 2002 |
| * Time: 2:33:58 PM |
| * To change template for new class use |
| * Code Style | Class Templates options (Tools | IDE Options). |
| */ |
| package com.intellij.codeInsight.intention.impl; |
| |
| import com.intellij.codeInsight.CodeInsightBundle; |
| import com.intellij.codeInsight.CodeInsightUtil; |
| import com.intellij.codeInsight.daemon.impl.analysis.HighlightNamesUtil; |
| import com.intellij.codeInsight.daemon.impl.quickfix.CreateClassKind; |
| import com.intellij.codeInsight.daemon.impl.quickfix.CreateConstructorMatchingSuperFix; |
| import com.intellij.codeInsight.daemon.impl.quickfix.CreateFromUsageBaseFix; |
| import com.intellij.codeInsight.generation.OverrideImplementUtil; |
| import com.intellij.codeInsight.generation.PsiMethodMember; |
| import com.intellij.codeInsight.template.Template; |
| import com.intellij.codeInsight.template.TemplateBuilderFactory; |
| import com.intellij.codeInsight.template.TemplateBuilderImpl; |
| import com.intellij.codeInsight.template.TemplateEditingAdapter; |
| import com.intellij.lang.java.JavaLanguage; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.application.Result; |
| import com.intellij.openapi.command.WriteCommandAction; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.CaretModel; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.RangeMarker; |
| import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; |
| import com.intellij.openapi.module.ModuleUtilCore; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.ui.Messages; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.*; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public class CreateSubclassAction extends BaseIntentionAction { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.intention.impl.ImplementAbstractClassAction"); |
| private String myText = CodeInsightBundle.message("intention.implement.abstract.class.default.text"); |
| @NonNls private static final String IMPL_SUFFIX = "Impl"; |
| |
| @Override |
| @NotNull |
| public String getText() { |
| return myText; |
| } |
| |
| @Override |
| @NotNull |
| public String getFamilyName() { |
| return CodeInsightBundle.message("intention.implement.abstract.class.family"); |
| } |
| |
| @Override |
| public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { |
| final CaretModel caretModel = editor.getCaretModel(); |
| final int position = caretModel.getOffset(); |
| PsiElement element = file.findElementAt(position); |
| PsiClass psiClass = PsiTreeUtil.getParentOfType(element, PsiClass.class); |
| if (psiClass == null || psiClass.isAnnotationType() || psiClass.isEnum() || psiClass instanceof PsiAnonymousClass || |
| psiClass.hasModifierProperty(PsiModifier.FINAL)) { |
| return false; |
| } |
| if (!isSupportedLanguage(psiClass)) return false; |
| final PsiMethod[] constructors = psiClass.getConstructors(); |
| if (constructors.length > 0) { |
| boolean hasNonPrivateConstructor = false; |
| for (PsiMethod constructor : constructors) { |
| if (!constructor.hasModifierProperty(PsiModifier.PRIVATE)) { |
| hasNonPrivateConstructor = true; |
| break; |
| } |
| } |
| if (!hasNonPrivateConstructor) return false; |
| } |
| PsiElement lBrace = psiClass.getLBrace(); |
| if (lBrace == null) return false; |
| if (element.getTextOffset() >= lBrace.getTextOffset()) return false; |
| |
| TextRange declarationRange = HighlightNamesUtil.getClassDeclarationTextRange(psiClass); |
| final TextRange elementTextRange = element.getTextRange(); |
| if (!declarationRange.contains(elementTextRange)) { |
| if (!(element instanceof PsiWhiteSpace) || (declarationRange.getStartOffset() != elementTextRange.getEndOffset() && |
| declarationRange.getEndOffset() != elementTextRange.getStartOffset())) { |
| return false; |
| } |
| } |
| |
| myText = getTitle(psiClass); |
| return true; |
| } |
| |
| protected boolean isSupportedLanguage(PsiClass aClass) { |
| return aClass.getLanguage() == JavaLanguage.INSTANCE; |
| } |
| |
| protected static String getTitle(PsiClass psiClass) { |
| return psiClass.isInterface() |
| ? CodeInsightBundle.message("intention.implement.abstract.class.interface.text") |
| : psiClass.hasModifierProperty(PsiModifier.ABSTRACT) |
| ? CodeInsightBundle.message("intention.implement.abstract.class.default.text") |
| : CodeInsightBundle.message("intention.implement.abstract.class.subclass.text"); |
| } |
| |
| @Override |
| public void invoke(@NotNull final Project project, Editor editor, final PsiFile file) throws IncorrectOperationException { |
| PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); |
| final PsiClass psiClass = PsiTreeUtil.getParentOfType(element, PsiClass.class); |
| |
| LOG.assertTrue(psiClass != null); |
| if (psiClass.hasModifierProperty(PsiModifier.PRIVATE) && psiClass.getContainingClass() != null) { |
| createInnerClass(psiClass); |
| return; |
| } |
| createTopLevelClass(psiClass); |
| } |
| |
| public static void createInnerClass(final PsiClass aClass) { |
| new WriteCommandAction(aClass.getProject(), getTitle(aClass), getTitle(aClass)) { |
| @Override |
| protected void run(Result result) throws Throwable { |
| final PsiClass containingClass = aClass.getContainingClass(); |
| LOG.assertTrue(containingClass != null); |
| |
| final PsiTypeParameterList oldTypeParameterList = aClass.getTypeParameterList(); |
| PsiClass classFromText = JavaPsiFacade.getElementFactory(aClass.getProject()).createClass(aClass.getName() + IMPL_SUFFIX); |
| classFromText = (PsiClass)containingClass.addAfter(classFromText, aClass); |
| startTemplate(oldTypeParameterList, aClass.getProject(), aClass, classFromText, true); |
| } |
| }.execute(); |
| } |
| |
| protected void createTopLevelClass(PsiClass psiClass) { |
| final CreateClassDialog dlg = chooseSubclassToCreate(psiClass); |
| if (dlg != null) { |
| createSubclass(psiClass, dlg.getTargetDirectory(), dlg.getClassName()); |
| } |
| } |
| |
| @Nullable |
| public static CreateClassDialog chooseSubclassToCreate(PsiClass psiClass) { |
| final PsiDirectory sourceDir = psiClass.getContainingFile().getContainingDirectory(); |
| |
| final PsiPackage aPackage = JavaDirectoryService.getInstance().getPackage(sourceDir); |
| final CreateClassDialog dialog = new CreateClassDialog( |
| psiClass.getProject(), getTitle(psiClass), |
| psiClass.getName() + IMPL_SUFFIX, |
| aPackage != null ? aPackage.getQualifiedName() : "", |
| CreateClassKind.CLASS, true, ModuleUtilCore.findModuleForPsiElement(psiClass)){ |
| @Override |
| protected PsiDirectory getBaseDir(String packageName) { |
| return sourceDir; |
| } |
| |
| @Override |
| protected boolean reportBaseInTestSelectionInSource() { |
| return true; |
| } |
| }; |
| dialog.show(); |
| if (!dialog.isOK()) return null; |
| final PsiDirectory targetDirectory = dialog.getTargetDirectory(); |
| if (targetDirectory == null) return null; |
| return dialog; |
| } |
| |
| public static PsiClass createSubclass(final PsiClass psiClass, final PsiDirectory targetDirectory, final String className) { |
| final Project project = psiClass.getProject(); |
| final PsiClass[] targetClass = new PsiClass[1]; |
| new WriteCommandAction(project, getTitle(psiClass), getTitle(psiClass)) { |
| @Override |
| protected void run(Result result) throws Throwable { |
| IdeDocumentHistory.getInstance(project).includeCurrentPlaceAsChangePlace(); |
| |
| final PsiTypeParameterList oldTypeParameterList = psiClass.getTypeParameterList(); |
| |
| try { |
| targetClass[0] = JavaDirectoryService.getInstance().createClass(targetDirectory, className); |
| } |
| catch (final IncorrectOperationException e) { |
| ApplicationManager.getApplication().invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| Messages.showErrorDialog(project, CodeInsightBundle.message("intention.error.cannot.create.class.message", className) + |
| "\n"+e.getLocalizedMessage(), |
| CodeInsightBundle.message("intention.error.cannot.create.class.title")); |
| } |
| }); |
| return; |
| } |
| startTemplate(oldTypeParameterList, project, psiClass, targetClass[0], false); |
| } |
| }.execute(); |
| if (targetClass[0] == null) return null; |
| if (!ApplicationManager.getApplication().isUnitTestMode() && !psiClass.hasTypeParameters()) { |
| |
| final Editor editor = CodeInsightUtil.positionCursor(project, targetClass[0].getContainingFile(), targetClass[0].getLBrace()); |
| if (editor == null) return targetClass[0]; |
| |
| chooseAndImplement(psiClass, project, targetClass[0], editor); |
| } |
| return targetClass[0]; |
| } |
| |
| private static void startTemplate(PsiTypeParameterList oldTypeParameterList, |
| final Project project, |
| final PsiClass psiClass, |
| final PsiClass targetClass, |
| final boolean includeClassName) { |
| final PsiElementFactory elementFactory = JavaPsiFacade.getInstance(project).getElementFactory(); |
| PsiJavaCodeReferenceElement ref = elementFactory.createClassReferenceElement(psiClass); |
| try { |
| if (psiClass.isInterface()) { |
| ref = (PsiJavaCodeReferenceElement)targetClass.getImplementsList().add(ref); |
| } |
| else { |
| ref = (PsiJavaCodeReferenceElement)targetClass.getExtendsList().add(ref); |
| } |
| if (psiClass.hasTypeParameters() || includeClassName) { |
| final Editor editor = CodeInsightUtil.positionCursor(project, targetClass.getContainingFile(), targetClass.getLBrace()); |
| final TemplateBuilderImpl templateBuilder = editor != null |
| ? (TemplateBuilderImpl)TemplateBuilderFactory.getInstance().createTemplateBuilder(targetClass) : null; |
| |
| if (includeClassName && templateBuilder != null) { |
| templateBuilder.replaceElement(targetClass.getNameIdentifier(), targetClass.getName()); |
| } |
| |
| if (oldTypeParameterList != null) { |
| for (PsiTypeParameter parameter : oldTypeParameterList.getTypeParameters()) { |
| final PsiElement param = ref.getParameterList().add(elementFactory.createTypeElement(elementFactory.createType(parameter))); |
| if (templateBuilder != null) { |
| templateBuilder.replaceElement(param, param.getText()); |
| } |
| } |
| } |
| |
| replaceTypeParamsList(targetClass, oldTypeParameterList); |
| if (templateBuilder != null) { |
| templateBuilder.setEndVariableBefore(ref); |
| final Template template = templateBuilder.buildTemplate(); |
| template.addEndVariable(); |
| |
| final PsiFile containingFile = targetClass.getContainingFile(); |
| |
| PsiDocumentManager.getInstance(project).doPostponedOperationsAndUnblockDocument(editor.getDocument()); |
| |
| final TextRange textRange = targetClass.getTextRange(); |
| final RangeMarker startClassOffset = editor.getDocument().createRangeMarker(textRange.getStartOffset(), textRange.getEndOffset()); |
| startClassOffset.setGreedyToLeft(true); |
| startClassOffset.setGreedyToRight(true); |
| editor.getDocument().deleteString(textRange.getStartOffset(), textRange.getEndOffset()); |
| CreateFromUsageBaseFix.startTemplate(editor, template, project, new TemplateEditingAdapter() { |
| @Override |
| public void templateFinished(Template template, boolean brokenOff) { |
| try { |
| LOG.assertTrue(startClassOffset.isValid(), startClassOffset); |
| final PsiElement psiElement = containingFile.findElementAt(startClassOffset.getStartOffset()); |
| final PsiClass aTargetClass = PsiTreeUtil.getParentOfType(psiElement, PsiClass.class); |
| LOG.assertTrue(aTargetClass != null, psiElement); |
| chooseAndImplement(psiClass, project, aTargetClass, editor); |
| } |
| finally { |
| startClassOffset.dispose(); |
| } |
| } |
| }, getTitle(psiClass)); |
| } |
| } |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| } |
| |
| private static PsiElement replaceTypeParamsList(PsiClass psiClass, PsiTypeParameterList oldTypeParameterList) { |
| final PsiTypeParameterList typeParameterList = psiClass.getTypeParameterList(); |
| assert typeParameterList != null; |
| return typeParameterList.replace(oldTypeParameterList); |
| } |
| |
| protected static void chooseAndImplement(PsiClass psiClass, Project project, @NotNull PsiClass targetClass, Editor editor) { |
| boolean hasNonTrivialConstructor = false; |
| final PsiMethod[] constructors = psiClass.getConstructors(); |
| for (PsiMethod constructor : constructors) { |
| if (constructor.getParameterList().getParametersCount() > 0) { |
| hasNonTrivialConstructor = true; |
| break; |
| } |
| } |
| if (hasNonTrivialConstructor) { |
| final PsiSubstitutor substitutor = TypeConversionUtil.getSuperClassSubstitutor(psiClass, targetClass, PsiSubstitutor.EMPTY); |
| final List<PsiMethodMember> baseConstructors = new ArrayList<PsiMethodMember>(); |
| for (PsiMethod baseConstr : constructors) { |
| if (PsiUtil.isAccessible(project, baseConstr, targetClass, targetClass)) { |
| baseConstructors.add(new PsiMethodMember(baseConstr, substitutor)); |
| } |
| } |
| final int offset = editor.getCaretModel().getOffset(); |
| CreateConstructorMatchingSuperFix.chooseConstructor2Delegate(project, editor, |
| substitutor, |
| baseConstructors, constructors, targetClass); |
| editor.getCaretModel().moveToOffset(offset); |
| } |
| |
| OverrideImplementUtil.chooseAndImplementMethods(project, editor, targetClass); |
| } |
| |
| @Override |
| public boolean startInWriteAction() { |
| return false; |
| } |
| |
| } |