package org.fandev.idea.overrideImplement; import com.intellij.codeInsight.generation.PsiMethodMember; import com.intellij.ide.fileTemplates.FileTemplate; import com.intellij.ide.fileTemplates.FileTemplateManager; import com.intellij.ide.fileTemplates.JavaTemplateUtil; import com.intellij.ide.util.MemberChooser; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.ScrollType; import com.intellij.openapi.project.Project; import com.intellij.psi.*; import com.intellij.psi.impl.compiled.ClsParameterImpl; import com.intellij.psi.infos.CandidateInfo; import com.intellij.psi.util.PsiTypesUtil; import com.intellij.psi.util.MethodSignature; import com.intellij.psi.util.MethodSignatureUtil; import com.intellij.util.IncorrectOperationException; import com.intellij.util.text.CharArrayUtil; import org.fandev.lang.fan.FanBundle; import org.fandev.lang.fan.FanTokenTypes; import org.fandev.lang.fan.psi.FanPsiElementFactory; import org.fandev.lang.fan.psi.api.statements.FanTopLevelDefintion; import org.fandev.lang.fan.psi.api.statements.blocks.FanPsiCodeBlock; import org.fandev.lang.fan.psi.api.statements.typeDefs.FanTypeDefinition; import org.fandev.lang.fan.psi.api.statements.typeDefs.FanTypeDefinitionBody; import org.fandev.lang.fan.psi.api.statements.typeDefs.members.FanMethod; import org.fandev.lang.fan.psi.api.topLevel.FanTopStatement; import org.fandev.utils.PsiUtil; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.util.*; /** * Date: Sep 26, 2009 * Time: 4:08:00 PM * * @author Dror Bereznitsky */ public class FanOverrideImplementUtil { private static final Logger LOG = Logger.getInstance("Override method"); public static void invokeOverrideImplement(final Project project, final Editor editor, final PsiFile file, boolean isImplement) { final int offset = editor.getCaretModel().getOffset(); PsiElement parent = file.findElementAt(offset); if (parent == null) { return; } while (!(parent instanceof FanTypeDefinition)) { parent = parent.getParent(); if (parent == null) { return; } } final FanTypeDefinition aClass = (FanTypeDefinition) parent; if (isImplement && aClass.isInterface()) { return; } final Collection<CandidateInfo> candidates = getMethodsToOverrideImplement(aClass, isImplement); if (candidates.isEmpty()) { return; } final List<PsiMethodMember> classMembers = new ArrayList<PsiMethodMember>(); for (final CandidateInfo candidate : candidates) { classMembers.add(new PsiMethodMember(candidate)); } final MemberChooser<PsiMethodMember> chooser = new MemberChooser<PsiMethodMember>(classMembers.toArray(new PsiMethodMember[0]), false, true, project); chooser.setTitle(isImplement ? FanBundle.message("select.methods.to.implement") : FanBundle.message("select.methods.to.override")); chooser.show(); final List<PsiMethodMember> selectedElements = chooser.getSelectedElements(); if (selectedElements == null || selectedElements.size() == 0) { return; } for (final PsiMethodMember methodMember : selectedElements) { final PsiMethod method = methodMember.getElement(); final PsiSubstitutor substitutor = methodMember.getSubstitutor(); final boolean isAbstract = method.hasModifierProperty(PsiModifier.ABSTRACT); final String templName = isAbstract ? JavaTemplateUtil.TEMPLATE_IMPLEMENTED_METHOD_BODY : JavaTemplateUtil.TEMPLATE_OVERRIDDEN_METHOD_BODY; final FileTemplate template = FileTemplateManager.getInstance().getCodeTemplate(templName); final FanMethod result = createOverrideImplementMethodSignature(project, method, substitutor, aClass); ApplicationManager.getApplication().runWriteAction(new Runnable() { public void run() { try { final PsiModifierList modifierList = result.getModifierList(); modifierList.setModifierProperty(PsiModifier.ABSTRACT, false); modifierList.setModifierProperty("virtual", false); setupOverridingMethodBody(project, method, result, template, substitutor, editor); final PsiElement classBody = aClass.getBodyElement(); final PsiMethod[] methods = aClass.getMethods(); PsiElement anchor = null; final int caretPosition = editor.getCaretModel().getOffset(); final PsiElement thisCaretPsiElement = file.findElementAt(caretPosition); final FanTopLevelDefintion previousTopLevelElement = PsiUtil.findPreviousTopLevelElementByThisElement(thisCaretPsiElement); if (thisCaretPsiElement != null && thisCaretPsiElement.getParent() instanceof FanTypeDefinitionBody) { if (FanTokenTypes.LBRACE.equals(thisCaretPsiElement.getNode().getElementType())) { anchor = thisCaretPsiElement.getNextSibling(); } else if (FanTokenTypes.RBRACE.equals(thisCaretPsiElement.getNode().getElementType())) { anchor = thisCaretPsiElement.getPrevSibling(); } else { anchor = thisCaretPsiElement; } } else if (previousTopLevelElement != null && previousTopLevelElement instanceof FanMethod) { final PsiElement nextElement = previousTopLevelElement.getNextSibling(); if (nextElement != null) { anchor = nextElement; } } else if (methods.length != 0) { final PsiMethod lastMethod = methods[methods.length - 1]; if (lastMethod != null) { final PsiElement nextSibling = lastMethod.getNextSibling(); if (nextSibling != null) { anchor = nextSibling; } } } else { final PsiElement firstChild = classBody.getFirstChild(); assert firstChild != null; final PsiElement nextElement = firstChild.getNextSibling(); assert nextElement != null; anchor = nextElement; } aClass.addMemberDeclaration(result, anchor); //PsiUtil.shortenReferences(result); positionCaret(editor, result); } catch (IncorrectOperationException e) { throw new RuntimeException(e); } } }); } } public static Collection<CandidateInfo> getMethodsToOverrideImplement(PsiClass clazz, boolean flag) { final Map<MethodSignature, CandidateInfo> candidates = new HashMap<MethodSignature, CandidateInfo>(); final PsiMethod[] allMethods = clazz.getAllMethods(); for (final PsiMethod method : allMethods) { if ((method.hasModifierProperty("virtual") || method.hasModifierProperty(PsiModifier.ABSTRACT)) && PsiUtil.isAccessible(method, clazz)) { final MethodSignature signature = MethodSignatureUtil.createMethodSignature(method.getName(), method.getParameterList(), method.getTypeParameterList(), PsiSubstitutor.EMPTY); final CandidateInfo candidateInfo = new CandidateInfo(method, PsiSubstitutor.EMPTY); candidates.put(signature, candidateInfo); } } return candidates.values(); } private static void positionCaret(final Editor editor, final FanMethod result) { final FanPsiCodeBlock body = (FanPsiCodeBlock) result.getBody(); if (body == null) { return; } final PsiElement lBrace = body.getLeftBrace(); assert lBrace != null; final PsiElement l = lBrace.getNextSibling(); assert l != null; final PsiElement rBrace = body.getRightBrace(); assert rBrace != null; final PsiElement r = rBrace.getPrevSibling(); assert r != null; LOG.assertTrue(!PsiDocumentManager.getInstance(result.getProject()).isUncommited(editor.getDocument())); final String text = editor.getDocument().getText(); int start = l.getTextRange().getStartOffset(); start = CharArrayUtil.shiftForward(text, start, "\n\t "); int end = r.getTextRange().getEndOffset(); end = CharArrayUtil.shiftBackward(text, end - 1, "\n\t ") + 1; editor.getCaretModel().moveToOffset(Math.min(start, end)); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); if (start < end) { //Not an empty body editor.getSelectionModel().setSelection(start, end); } } private static boolean writeMethodModifiers(final StringBuffer text, final PsiModifierList modifierList, final String[] modifiers) { boolean wasAddedModifiers = false; for (final String modifierType : modifiers) { if (modifierList.hasModifierProperty(modifierType)) { text.append(modifierType); text.append(" "); wasAddedModifiers = true; } } return wasAddedModifiers; } private static final String[] FAN_MODIFIERS = new String[]{ "new", "internal", PsiModifier.NATIVE, PsiModifier.PUBLIC, PsiModifier.PROTECTED, PsiModifier.STATIC, }; private static FanMethod createOverrideImplementMethodSignature(final Project project, final PsiMethod method, final PsiSubstitutor substitutor, final PsiClass aClass) { final StringBuffer buffer = new StringBuffer(); buffer.append("class foo {"); writeMethodModifiers(buffer, method.getModifierList(), FAN_MODIFIERS); final PsiType returnType = substitutor.substitute(method.getReturnType()); if (method.isConstructor()) { buffer.append(aClass.getName()); } else { if (returnType != null) { buffer.append(returnType.getCanonicalText()); buffer.append(" "); } buffer.append(method.getName()); } buffer.append(" "); buffer.append("("); final PsiParameter[] parameters = method.getParameterList().getParameters(); for (int i = 0; i < parameters.length; i++) { if (i > 0) { buffer.append(", "); } final PsiParameter parameter = parameters[i]; final PsiType parameterType = substitutor.substitute(parameter.getType()); buffer.append(parameterType.getCanonicalText()); buffer.append(" "); final String paramName = parameter.getName(); if (paramName != null) { buffer.append(paramName); } else if (parameter instanceof ClsParameterImpl) { final ClsParameterImpl clsParameter = (ClsParameterImpl) parameter; buffer.append(((PsiParameter) clsParameter.getMirror()).getName()); } } buffer.append(")"); buffer.append(" "); buffer.append("{"); buffer.append("}"); buffer.append("}"); final FanTypeDefinition top = (FanTypeDefinition) FanPsiElementFactory.getInstance(project).createTopElementFromText(buffer.toString()); return top.getMethodByName(method.getName()); } private static void setupOverridingMethodBody(final Project project, final PsiMethod method, final FanMethod resultMethod, final FileTemplate template, final PsiSubstitutor substitutor, final Editor editor) { final PsiType returnType = substitutor.substitute(method.getReturnType()); String returnTypeText = ""; if (returnType != null) { returnTypeText = returnType.getPresentableText(); } final Properties properties = new Properties(); properties.setProperty(FileTemplate.ATTRIBUTE_RETURN_TYPE, returnTypeText); properties.setProperty(FileTemplate.ATTRIBUTE_DEFAULT_RETURN_VALUE, PsiTypesUtil.getDefaultValueOfType(returnType)); properties.setProperty(FileTemplate.ATTRIBUTE_CALL_SUPER, callSuper(method, resultMethod)); JavaTemplateUtil.setClassAndMethodNameProperties(properties, method.getContainingClass(), resultMethod); try { final String bodyText = template.getText(properties); final PsiCodeBlock newBody = FanPsiElementFactory.getInstance(project).createMethodBodyFromText("\n" + bodyText + "\n"); resultMethod.setBlock(newBody); } catch (IOException e) { LOG.error(e); } } @NotNull private static String callSuper(final PsiMethod superMethod, final PsiMethod overriding) { @NonNls final StringBuilder buffer = new StringBuilder(); if (!superMethod.isConstructor() && superMethod.getReturnType() != PsiType.VOID) { buffer.append("return "); } buffer.append("super"); final PsiParameter[] parms = overriding.getParameterList().getParameters(); if (!superMethod.isConstructor()) { buffer.append("."); buffer.append(superMethod.getName()); } buffer.append("("); for (int i = 0; i < parms.length; i++) { final String name = parms[i].getName(); if (i > 0) { buffer.append(","); } buffer.append(name); } buffer.append(")"); return buffer.toString(); } }