/* * Copyright 2000-2016 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 com.intellij.codeInspection; import com.intellij.codeInsight.daemon.impl.AnnotationHolderImpl; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.lang.annotation.Annotation; import com.intellij.lang.annotation.AnnotationSession; import com.intellij.lang.annotation.ExternalAnnotator; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Iconable; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiElementVisitor; import com.intellij.psi.PsiFile; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.IdentityHashMap; import java.util.List; public class ExternalAnnotatorInspectionVisitor extends PsiElementVisitor { private static final Logger LOG = Logger.getInstance(ExternalAnnotatorInspectionVisitor.class); private final ProblemsHolder myHolder; private final ExternalAnnotator myAnnotator; private final boolean myIsOnTheFly; public ExternalAnnotatorInspectionVisitor(ProblemsHolder holder, ExternalAnnotator annotator, boolean isOnTheFly) { myHolder = holder; myAnnotator = annotator; myIsOnTheFly = isOnTheFly; } @Override public void visitFile(PsiFile file) { ProblemDescriptor[] descriptors = checkFileWithExternalAnnotator(file, myHolder.getManager(), myIsOnTheFly, myAnnotator); addDescriptors(descriptors); } @NotNull public static <Init,Result> ProblemDescriptor[] checkFileWithExternalAnnotator(@NotNull PsiFile file, @NotNull InspectionManager manager, boolean isOnTheFly, @NotNull ExternalAnnotator<Init, Result> annotator) { if (isOnTheFly) { // ExternalAnnotator does this work return ProblemDescriptor.EMPTY_ARRAY; } Init info = ReadAction.compute(() -> annotator.collectInformation(file)); if (info != null) { Result annotationResult = annotator.doAnnotate(info); if (annotationResult == null) { return ProblemDescriptor.EMPTY_ARRAY; } return ReadAction.compute(() -> { AnnotationHolderImpl annotationHolder = new AnnotationHolderImpl(new AnnotationSession(file), true); annotator.apply(file, annotationResult, annotationHolder); return convertToProblemDescriptors(annotationHolder, manager, file); }); } return ProblemDescriptor.EMPTY_ARRAY; } @NotNull private static ProblemDescriptor[] convertToProblemDescriptors(@NotNull final List<Annotation> annotations, @NotNull final InspectionManager manager, @NotNull final PsiFile file) { if (annotations.isEmpty()) { return ProblemDescriptor.EMPTY_ARRAY; } List<ProblemDescriptor> problems = ContainerUtil.newArrayListWithCapacity(annotations.size()); IdentityHashMap<IntentionAction, LocalQuickFix> quickFixMappingCache = ContainerUtil.newIdentityHashMap(); for (Annotation annotation : annotations) { if (annotation.getSeverity() == HighlightSeverity.INFORMATION || annotation.getStartOffset() == annotation.getEndOffset() && !annotation.isAfterEndOfLine()) { continue; } final PsiElement startElement; final PsiElement endElement; if (annotation.getStartOffset() == annotation.getEndOffset() && annotation.isAfterEndOfLine()) { startElement = endElement = file.findElementAt(annotation.getEndOffset() - 1); } else { startElement = file.findElementAt(annotation.getStartOffset()); endElement = file.findElementAt(annotation.getEndOffset() - 1); } if (startElement == null || endElement == null) { continue; } LocalQuickFix[] quickFixes = toLocalQuickFixes(annotation.getQuickFixes(), quickFixMappingCache); ProblemDescriptor descriptor = new ProblemDescriptorBase(startElement, endElement, annotation.getMessage(), quickFixes, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, annotation.isAfterEndOfLine(), null, true, false); problems.add(descriptor); } return problems.toArray(new ProblemDescriptor[problems.size()]); } @NotNull private static LocalQuickFix[] toLocalQuickFixes(@Nullable List<Annotation.QuickFixInfo> fixInfos, @NotNull IdentityHashMap<IntentionAction, LocalQuickFix> quickFixMappingCache) { if (fixInfos == null || fixInfos.isEmpty()) { return LocalQuickFix.EMPTY_ARRAY; } LocalQuickFix[] result = new LocalQuickFix[fixInfos.size()]; int i = 0; for (Annotation.QuickFixInfo fixInfo : fixInfos) { IntentionAction intentionAction = fixInfo.quickFix; final LocalQuickFix fix; if (intentionAction instanceof LocalQuickFix) { fix = (LocalQuickFix) intentionAction; } else { LocalQuickFix lqf = quickFixMappingCache.get(intentionAction); if (lqf == null) { lqf = new LocalQuickFixBackedByIntentionAction(intentionAction); quickFixMappingCache.put(intentionAction, lqf); } fix = lqf; } result[i++] = fix; } return result; } private void addDescriptors(@NotNull ProblemDescriptor[] descriptors) { for (ProblemDescriptor descriptor : descriptors) { LOG.assertTrue(descriptor != null, getClass().getName()); myHolder.registerProblem(descriptor); } } public static class LocalQuickFixBackedByIntentionAction implements LocalQuickFix, Iconable { private final IntentionAction myAction; public LocalQuickFixBackedByIntentionAction(@NotNull IntentionAction action) { myAction = action; } @NotNull @Override public String getName() { return myAction.getText(); } @NotNull @Override public String getFamilyName() { return myAction.getFamilyName(); } @Override public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { myAction.invoke(project, null, getPsiFile(descriptor)); } @Nullable @Override public PsiElement getElementToMakeWritable(@NotNull PsiFile file) { return myAction.getElementToMakeWritable(file); } @Nullable private static PsiFile getPsiFile(@NotNull ProblemDescriptor descriptor) { PsiElement startElement = descriptor.getStartElement(); if (startElement != null) { return startElement.getContainingFile(); } PsiElement endElement = descriptor.getEndElement(); if (endElement != null) { return endElement.getContainingFile(); } return null; } @Override public Icon getIcon(@IconFlags int flags) { if (myAction instanceof Iconable) { return ((Iconable) myAction).getIcon(flags); } return null; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; LocalQuickFixBackedByIntentionAction action = (LocalQuickFixBackedByIntentionAction)o; return myAction.equals(action.myAction); } @Override public int hashCode() { return myAction.hashCode(); } } }