Java tutorial
/* * Copyright 2010-2012 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. */ package org.jetbrains.jet.plugin.codeInsight; import com.intellij.codeInsight.hint.HintManager; import com.intellij.ide.util.MemberChooser; import com.intellij.lang.LanguageCodeInsightActionHandler; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.lang.descriptors.*; import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.scopes.receivers.ReceiverDescriptor; import org.jetbrains.jet.lang.types.JetType; import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns; import org.jetbrains.jet.plugin.project.WholeProjectAnalyzerFacade; import org.jetbrains.jet.resolve.DescriptorRenderer; import java.util.*; /** * @author yole */ public abstract class OverrideImplementMethodsHandler implements LanguageCodeInsightActionHandler { public static List<DescriptorClassMember> membersFromDescriptors( Iterable<CallableMemberDescriptor> missingImplementations) { List<DescriptorClassMember> members = new ArrayList<DescriptorClassMember>(); for (CallableMemberDescriptor memberDescriptor : missingImplementations) { members.add(new DescriptorClassMember(memberDescriptor)); } return members; } @NotNull public Set<CallableMemberDescriptor> collectMethodsToGenerate(@NotNull JetClassOrObject classOrObject) { BindingContext bindingContext = WholeProjectAnalyzerFacade .analyzeProjectWithCacheOnAFile((JetFile) classOrObject.getContainingFile()).getBindingContext(); final DeclarationDescriptor descriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, classOrObject); if (descriptor instanceof MutableClassDescriptor) { return collectMethodsToGenerate((MutableClassDescriptor) descriptor); } return Collections.emptySet(); } protected abstract Set<CallableMemberDescriptor> collectMethodsToGenerate(MutableClassDescriptor descriptor); public static void generateMethods(Editor editor, JetClassOrObject classOrObject, List<DescriptorClassMember> selectedElements) { final JetClassBody body = classOrObject.getBody(); if (body == null) { return; } PsiElement afterAnchor = findInsertAfterAnchor(editor, body); if (afterAnchor == null) { return; } List<JetElement> elementsToCompact = new ArrayList<JetElement>(); final JetFile file = (JetFile) classOrObject.getContainingFile(); for (JetElement element : generateOverridingMembers(selectedElements, file)) { PsiElement added = body.addAfter(element, afterAnchor); afterAnchor = added; elementsToCompact.add((JetElement) added); } ReferenceToClassesShortening.compactReferenceToClasses(elementsToCompact); } @Nullable private static PsiElement findInsertAfterAnchor(Editor editor, final JetClassBody body) { PsiElement afterAnchor = body.getLBrace(); if (afterAnchor == null) { return null; } int offset = editor.getCaretModel().getOffset(); PsiElement offsetCursorElement = PsiTreeUtil.findFirstParent(body.getContainingFile().findElementAt(offset), new Condition<PsiElement>() { @Override public boolean value(PsiElement element) { return element.getParent() == body; } }); if (offsetCursorElement != null && offsetCursorElement != body.getRBrace()) { afterAnchor = offsetCursorElement; } return afterAnchor; } private static List<JetElement> generateOverridingMembers(List<DescriptorClassMember> selectedElements, JetFile file) { List<JetElement> overridingMembers = new ArrayList<JetElement>(); for (DescriptorClassMember selectedElement : selectedElements) { final DeclarationDescriptor descriptor = selectedElement.getDescriptor(); if (descriptor instanceof SimpleFunctionDescriptor) { overridingMembers.add(overrideFunction(file.getProject(), (SimpleFunctionDescriptor) descriptor)); } else if (descriptor instanceof PropertyDescriptor) { overridingMembers.add(overrideProperty(file.getProject(), (PropertyDescriptor) descriptor)); } } return overridingMembers; } private static JetElement overrideProperty(Project project, PropertyDescriptor descriptor) { StringBuilder bodyBuilder = new StringBuilder(); bodyBuilder.append(displayableVisibility(descriptor)).append("override "); if (descriptor.isVar()) { bodyBuilder.append("var "); } else { bodyBuilder.append("val "); } addReceiverParameter(descriptor, bodyBuilder); bodyBuilder.append(descriptor.getName()).append(" : ") .append(DescriptorRenderer.COMPACT_WITH_MODIFIERS.renderTypeWithShortNames(descriptor.getType())); String initializer = defaultInitializer(descriptor.getType(), KotlinBuiltIns.getInstance()); if (initializer != null) { bodyBuilder.append(" = ").append(initializer); } else { bodyBuilder.append(" = ?"); } return JetPsiFactory.createProperty(project, bodyBuilder.toString()); } private static String renderType(JetType type) { return DescriptorRenderer.TEXT.renderType(type); } private static JetElement overrideFunction(Project project, SimpleFunctionDescriptor descriptor) { StringBuilder bodyBuilder = new StringBuilder(); bodyBuilder.append(displayableVisibility(descriptor)); bodyBuilder.append("override fun "); List<String> whereRestrictions = new ArrayList<String>(); if (!descriptor.getTypeParameters().isEmpty()) { bodyBuilder.append("<"); boolean first = true; for (TypeParameterDescriptor param : descriptor.getTypeParameters()) { if (!first) { bodyBuilder.append(", "); } bodyBuilder.append(param.getName()); Set<JetType> upperBounds = param.getUpperBounds(); if (!upperBounds.isEmpty()) { boolean firstUpperBound = true; for (JetType upperBound : upperBounds) { String upperBoundText = " : " + renderType(upperBound); if (!KotlinBuiltIns.getInstance().getDefaultBound().equals(upperBound)) { if (firstUpperBound) { bodyBuilder.append(upperBoundText); } else { whereRestrictions.add(param.getName() + upperBoundText); } } firstUpperBound = false; } } first = false; } bodyBuilder.append("> "); } addReceiverParameter(descriptor, bodyBuilder); bodyBuilder.append(descriptor.getName()).append("("); boolean isAbstractFun = descriptor.getModality() == Modality.ABSTRACT; StringBuilder delegationBuilder = new StringBuilder(); if (isAbstractFun) { delegationBuilder.append("throw UnsupportedOperationException()"); } else { delegationBuilder.append("super<").append(descriptor.getContainingDeclaration().getName()); delegationBuilder.append(">.").append(descriptor.getName()).append("("); } boolean first = true; for (ValueParameterDescriptor parameterDescriptor : descriptor.getValueParameters()) { if (!first) { bodyBuilder.append(","); if (!isAbstractFun) { delegationBuilder.append(","); } } first = false; bodyBuilder.append(parameterDescriptor.getName()); bodyBuilder.append(" : "); bodyBuilder.append(renderType(parameterDescriptor.getType())); if (!isAbstractFun) { delegationBuilder.append(parameterDescriptor.getName()); } } bodyBuilder.append(")"); if (!isAbstractFun) { delegationBuilder.append(")"); } final JetType returnType = descriptor.getReturnType(); final KotlinBuiltIns builtIns = KotlinBuiltIns.getInstance(); boolean returnsNotUnit = returnType != null && !builtIns.getUnitType().equals(returnType); if (returnsNotUnit) { bodyBuilder.append(" : ").append(renderType(returnType)); } if (!whereRestrictions.isEmpty()) { bodyBuilder.append("\n").append("where ").append(StringUtil.join(whereRestrictions, ", ")); } bodyBuilder.append("{").append(returnsNotUnit && !isAbstractFun ? "return " : "") .append(delegationBuilder.toString()).append("}"); return JetPsiFactory.createFunction(project, bodyBuilder.toString()); } private static void addReceiverParameter(CallableDescriptor descriptor, StringBuilder bodyBuilder) { ReceiverDescriptor receiverParameter = descriptor.getReceiverParameter(); if (receiverParameter.exists()) { bodyBuilder.append(receiverParameter.getType()).append("."); } } private static String defaultInitializer(JetType returnType, KotlinBuiltIns builtIns) { if (returnType.isNullable()) { return "null"; } else if (returnType.equals(builtIns.getIntType()) || returnType.equals(builtIns.getLongType()) || returnType.equals(builtIns.getShortType()) || returnType.equals(builtIns.getByteType()) || returnType.equals(builtIns.getFloatType()) || returnType.equals(builtIns.getDoubleType())) { return "0"; } else if (returnType.equals(builtIns.getBooleanType())) { return "false"; } return null; } private static String displayableVisibility(MemberDescriptor descriptor) { Visibility visibility = descriptor.getVisibility(); return visibility != Visibilities.INTERNAL ? visibility.toString() + " " : ""; } private MemberChooser<DescriptorClassMember> showOverrideImplementChooser(Project project, DescriptorClassMember[] members) { final MemberChooser<DescriptorClassMember> chooser = new MemberChooser<DescriptorClassMember>(members, true, true, project); chooser.setTitle(getChooserTitle()); chooser.show(); if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) return null; return chooser; } protected abstract String getChooserTitle(); @Override public boolean isValidFor(Editor editor, PsiFile file) { if (!(file instanceof JetFile)) { return false; } final PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset()); final JetClassOrObject classOrObject = PsiTreeUtil.getParentOfType(elementAtCaret, JetClassOrObject.class); return classOrObject != null; } protected abstract String getNoMethodsFoundHint(); public void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull PsiFile file, boolean implementAll) { final PsiElement elementAtCaret = file.findElementAt(editor.getCaretModel().getOffset()); final JetClassOrObject classOrObject = PsiTreeUtil.getParentOfType(elementAtCaret, JetClassOrObject.class); assert classOrObject != null : "ClassObject should be checked in isValidFor method"; Set<CallableMemberDescriptor> missingImplementations = collectMethodsToGenerate(classOrObject); if (missingImplementations.isEmpty() && !implementAll) { HintManager.getInstance().showErrorHint(editor, getNoMethodsFoundHint()); return; } List<DescriptorClassMember> members = membersFromDescriptors(missingImplementations); final List<DescriptorClassMember> selectedElements; if (implementAll) { selectedElements = members; } else { final MemberChooser<DescriptorClassMember> chooser = showOverrideImplementChooser(project, members.toArray(new DescriptorClassMember[members.size()])); if (chooser == null) { return; } selectedElements = chooser.getSelectedElements(); if (selectedElements == null || selectedElements.isEmpty()) return; } ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { generateMethods(editor, classOrObject, selectedElements); } }); } @Override public void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull PsiFile file) { invoke(project, editor, file, false); } @Override public boolean startInWriteAction() { return false; } }