| /* |
| * Copyright 2000-2014 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.plugins.groovy.codeInsight; |
| |
| import com.intellij.codeHighlighting.Pass; |
| import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings; |
| import com.intellij.codeInsight.daemon.LineMarkerInfo; |
| import com.intellij.codeInsight.daemon.LineMarkerProvider; |
| import com.intellij.codeInsight.daemon.impl.JavaLineMarkerProvider; |
| import com.intellij.codeInsight.daemon.impl.MarkerType; |
| import com.intellij.icons.AllIcons; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.colors.CodeInsightColors; |
| import com.intellij.openapi.editor.colors.EditorColorsManager; |
| import com.intellij.openapi.editor.colors.EditorColorsScheme; |
| import com.intellij.openapi.editor.markup.GutterIconRenderer; |
| import com.intellij.openapi.editor.markup.SeparatorPlacement; |
| import com.intellij.openapi.progress.ProgressManager; |
| import com.intellij.openapi.project.DumbAware; |
| import com.intellij.openapi.project.DumbService; |
| import com.intellij.openapi.project.IndexNotReadyException; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.PsiImplUtil; |
| import com.intellij.psi.search.searches.AllOverridingMethodsSearch; |
| import com.intellij.psi.search.searches.SuperMethodsSearch; |
| import com.intellij.psi.util.MethodSignatureBackedByPsiMethod; |
| import com.intellij.util.FunctionUtil; |
| import com.intellij.util.Processor; |
| import com.intellij.util.containers.HashSet; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocComment; |
| import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocCommentOwner; |
| import org.jetbrains.plugins.groovy.lang.psi.GrNamedElement; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrClassInitializer; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.GrTypeDefinition; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrAccessorMethod; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrReflectedMethod; |
| import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeParameter; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.statements.GrVariableDeclarationImpl; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GrTraitMethod; |
| import org.jetbrains.plugins.groovy.lang.psi.util.GrTraitUtil; |
| import org.jetbrains.plugins.groovy.lang.psi.util.GroovyPropertyUtils; |
| |
| import javax.swing.*; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * @author ilyas |
| * Same logic as for Java LMP |
| */ |
| public class GroovyLineMarkerProvider implements LineMarkerProvider, DumbAware { |
| protected final DaemonCodeAnalyzerSettings myDaemonSettings; |
| protected final EditorColorsManager myColorsManager; |
| |
| public GroovyLineMarkerProvider(DaemonCodeAnalyzerSettings daemonSettings, EditorColorsManager colorsManager) { |
| myDaemonSettings = daemonSettings; |
| myColorsManager = colorsManager; |
| } |
| |
| @Override |
| public LineMarkerInfo getLineMarkerInfo(@NotNull final PsiElement element) { |
| final PsiElement parent = element.getParent(); |
| if (parent instanceof PsiNameIdentifierOwner) { |
| if (parent instanceof GrField && element == ((GrField)parent).getNameIdentifierGroovy()) { |
| for (GrAccessorMethod method : GroovyPropertyUtils.getFieldAccessors((GrField)parent)) { |
| MethodSignatureBackedByPsiMethod superSignature = null; |
| try { |
| superSignature = SuperMethodsSearch.search(method, null, true, false).findFirst(); |
| } |
| catch (IndexNotReadyException e) { |
| //some searchers (EJB) require indices. What shall we do? |
| } |
| if (superSignature != null) { |
| PsiMethod superMethod = superSignature.getMethod(); |
| boolean overrides = method.hasModifierProperty(PsiModifier.ABSTRACT) == superMethod.hasModifierProperty(PsiModifier.ABSTRACT) || |
| superMethod.getBody() != null && GrTraitUtil.isTrait(superMethod.getContainingClass()); |
| final Icon icon = overrides ? AllIcons.Gutter.OverridingMethod : AllIcons.Gutter.ImplementingMethod; |
| final MarkerType type = GroovyMarkerTypes.OVERRIDING_PROPERTY_TYPE; |
| return new LineMarkerInfo<PsiElement>(element, element.getTextRange(), icon, Pass.UPDATE_ALL, type.getTooltip(), type.getNavigationHandler(), |
| GutterIconRenderer.Alignment.LEFT); |
| } |
| } |
| } |
| else if (parent instanceof GrMethod && |
| element == ((GrMethod)parent).getNameIdentifierGroovy() && |
| hasSuperMethods((GrMethod)element.getParent())) { |
| final Icon icon = AllIcons.Gutter.OverridingMethod; |
| final MarkerType type = GroovyMarkerTypes.OVERRIDING_METHOD; |
| return new LineMarkerInfo<PsiElement>(element, element.getTextRange(), icon, Pass.UPDATE_ALL, type.getTooltip(), |
| type.getNavigationHandler(), GutterIconRenderer.Alignment.LEFT); |
| } |
| } |
| //need to draw method separator above docComment |
| if (myDaemonSettings.SHOW_METHOD_SEPARATORS && element.getFirstChild() == null) { |
| PsiElement element1 = element; |
| boolean isMember = false; |
| while (element1 != null && !(element1 instanceof PsiFile) && element1.getPrevSibling() == null) { |
| element1 = element1.getParent(); |
| if (element1 instanceof PsiMember || element1 instanceof GrVariableDeclarationImpl) { |
| isMember = true; |
| break; |
| } |
| } |
| if (isMember && !(element1 instanceof PsiAnonymousClass || element1.getParent() instanceof PsiAnonymousClass)) { |
| PsiFile file = element1.getContainingFile(); |
| Document document = file == null ? null : PsiDocumentManager.getInstance(file.getProject()).getDocument(file); |
| boolean drawSeparator = false; |
| if (document != null) { |
| CharSequence documentChars = document.getCharsSequence(); |
| |
| int category = getGroovyCategory(element1, documentChars); |
| for (PsiElement child = element1.getPrevSibling(); child != null; child = child.getPrevSibling()) { |
| int category1 = getGroovyCategory(child, documentChars); |
| if (category1 == 0) continue; |
| drawSeparator = category != 1 || category1 != 1; |
| break; |
| } |
| } |
| |
| if (drawSeparator) { |
| GrDocComment comment = null; |
| if (element1 instanceof GrDocCommentOwner) { |
| comment = ((GrDocCommentOwner)element1).getDocComment(); |
| } |
| LineMarkerInfo info = |
| new LineMarkerInfo<PsiElement>(element, comment != null ? comment.getTextRange() : element.getTextRange(), null, |
| Pass.UPDATE_ALL, FunctionUtil.<Object, String>nullConstant(), null, |
| GutterIconRenderer.Alignment.RIGHT); |
| EditorColorsScheme scheme = myColorsManager.getGlobalScheme(); |
| info.separatorColor = scheme.getColor(CodeInsightColors.METHOD_SEPARATORS_COLOR); |
| info.separatorPlacement = SeparatorPlacement.TOP; |
| return info; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| private static boolean hasSuperMethods(GrMethod method) { |
| final GrReflectedMethod[] reflectedMethods = method.getReflectedMethods(); |
| if (reflectedMethods.length > 0) { |
| for (GrReflectedMethod reflectedMethod : reflectedMethods) { |
| final MethodSignatureBackedByPsiMethod first = SuperMethodsSearch.search(reflectedMethod, null, true, false).findFirst(); |
| if (first != null) return true; |
| } |
| return false; |
| } |
| else { |
| return SuperMethodsSearch.search(method, null, true, false).findFirst() != null; |
| } |
| } |
| |
| private static int getGroovyCategory(PsiElement element, CharSequence documentChars) { |
| if (element instanceof GrVariableDeclarationImpl) { |
| GrVariable[] variables = ((GrVariableDeclarationImpl)element).getVariables(); |
| if (variables.length == 1 && variables[0] instanceof GrField && variables[0].getInitializerGroovy() instanceof GrClosableBlock) { |
| return 2; |
| } |
| } |
| |
| if (element instanceof GrField || element instanceof GrTypeParameter) return 1; |
| if (element instanceof GrTypeDefinition || element instanceof GrClassInitializer) return 2; |
| if (element instanceof GrMethod) { |
| if (((GrMethod)element).hasModifierProperty(PsiModifier.ABSTRACT) && !(((GrMethod)element).getBlock() != null && |
| GrTraitUtil.isTrait(((GrMethod)element).getContainingClass()))) { |
| return 1; |
| } |
| TextRange textRange = element.getTextRange(); |
| int start = textRange.getStartOffset(); |
| int end = Math.min(documentChars.length(), textRange.getEndOffset()); |
| int crlf = StringUtil.getLineBreakCount(documentChars.subSequence(start, end)); |
| return crlf == 0 ? 1 : 2; |
| } |
| return 0; |
| } |
| |
| @Override |
| public void collectSlowLineMarkers(@NotNull final List<PsiElement> elements, @NotNull final Collection<LineMarkerInfo> result) { |
| if (elements.isEmpty() || DumbService.getInstance(elements.get(0).getProject()).isDumb()) { |
| return; |
| } |
| |
| Set<PsiMethod> methods = new HashSet<PsiMethod>(); |
| for (PsiElement element : elements) { |
| ProgressManager.checkCanceled(); |
| if (element instanceof GrField) { |
| methods.addAll(GroovyPropertyUtils.getFieldAccessors((GrField)element)); |
| } |
| else if (element instanceof GrMethod) { |
| GrReflectedMethod[] reflected = ((GrMethod)element).getReflectedMethods(); |
| if (reflected.length != 0) { |
| Collections.addAll(methods, reflected); |
| } |
| else { |
| methods.add((PsiMethod)element); |
| } |
| } |
| else if (element instanceof PsiClass && !(element instanceof PsiTypeParameter)) { |
| JavaLineMarkerProvider.collectInheritingClasses((PsiClass)element, result); |
| } |
| } |
| collectOverridingMethods(methods, result); |
| } |
| |
| private static void collectOverridingMethods(final Set<PsiMethod> methods, Collection<LineMarkerInfo> result) { |
| final Set<PsiElement> overridden = new HashSet<PsiElement>(); |
| |
| Set<PsiClass> classes = new THashSet<PsiClass>(); |
| for (PsiMethod method : methods) { |
| ProgressManager.checkCanceled(); |
| final PsiClass parentClass = method.getContainingClass(); |
| if (parentClass != null && !CommonClassNames.JAVA_LANG_OBJECT.equals(parentClass.getQualifiedName())) { |
| classes.add(parentClass); |
| } |
| } |
| |
| for (final PsiClass aClass : classes) { |
| try { |
| AllOverridingMethodsSearch.search(aClass).forEach(new Processor<Pair<PsiMethod, PsiMethod>>() { |
| @Override |
| public boolean process(final Pair<PsiMethod, PsiMethod> pair) { |
| ProgressManager.checkCanceled(); |
| |
| final PsiMethod superMethod = pair.getFirst(); |
| if (isCorrectTarget(superMethod) && isCorrectTarget(pair.getSecond())) { |
| if (methods.remove(superMethod)) { |
| overridden.add(PsiImplUtil.handleMirror(superMethod)); |
| } |
| } |
| return !methods.isEmpty(); |
| } |
| }); |
| } |
| catch (IndexNotReadyException ignored) { |
| } |
| } |
| |
| for (PsiElement element : overridden) { |
| final Icon icon = AllIcons.Gutter.OverridenMethod; |
| |
| element = PsiImplUtil.handleMirror(element); |
| |
| PsiElement range; |
| if (element instanceof GrNamedElement) { |
| range = ((GrNamedElement)element).getNameIdentifierGroovy(); |
| } |
| else { |
| range = element; |
| } |
| |
| final MarkerType type = element instanceof GrField ? GroovyMarkerTypes.OVERRIDEN_PROPERTY_TYPE |
| : GroovyMarkerTypes.OVERRIDEN_METHOD; |
| LineMarkerInfo info = new LineMarkerInfo<PsiElement>(range, range.getTextRange(), icon, Pass.UPDATE_OVERRIDEN_MARKERS, type.getTooltip(), |
| type.getNavigationHandler(), GutterIconRenderer.Alignment.RIGHT); |
| result.add(info); |
| } |
| } |
| |
| private static boolean isCorrectTarget(PsiMethod method) { |
| if (method instanceof GrTraitMethod) return false; |
| |
| final PsiElement navigationElement = method.getNavigationElement(); |
| return method.isPhysical() || navigationElement.isPhysical() && !(navigationElement instanceof PsiClass); |
| } |
| } |